Shiro笔记03-与Spring Boot整合
创始人
2024-02-16 18:57:32
0

框架整合

创建模块

创建一个Maven工程

添加依赖


4.0.0org.exampledemo1.0-SNAPSHOTwar88org.springframework.bootspring-boot-starter-parent2.2.1.RELEASEorg.apache.shiroshiro-spring-boot-web-starter1.9.0com.baomidoumybatis-plus-boot-starter3.0.5mysqlmysql-connector-java5.1.46org.projectlomboklombokorg.springframework.bootspring-boot-starter-thymeleaf

添加配置文件

添加application.yml配置文件,注意修改数据库的连接参数。

mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath:mapper/*.xml
spring:datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&useSSL=falseusername: rootpassword: rootjackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8
shiro:loginUrl: /login/login

添加启动类

package com.shiro.demo;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("com.shiro.demo.mapper")
public class ShiroApplication {public static void main(String[] args) {SpringApplication.run(ShiroApplication.class, args);}
}

实现登录认证

后端接口服务实现

创建用户表,这个在Shiro笔记02-基本使用里有。
创建实体类User.java,使用Lombok插件简化代码。

package com.shiro.demo.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private Long id;private String username;private String password;private String passwordSalt;
}

创建mapper、service、serviceImpl。

package com.shiro.demo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shiro.demo.entity.User;public interface UserMapper extends BaseMapper {
}
package com.shiro.demo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shiro.demo.entity.User;
import org.springframework.stereotype.Repository;@Repository
public interface UserMapper extends BaseMapper {
}
package com.shiro.demo.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.shiro.demo.entity.User;
import com.shiro.demo.mapper.UserMapper;
import com.shiro.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User getUserByUsername(String username) {QueryWrapper queryWrapper = new QueryWrapper<>();queryWrapper.eq("username", username);return userMapper.selectOne(queryWrapper);}
}

自定义Realm

package com.shiro.demo.realm;import com.shiro.demo.entity.User;
import com.shiro.demo.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class MyRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;/*** 自定义授权方法*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}/*** 自定义登录认证方法*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {String username = authenticationToken.getPrincipal().toString();User user = userService.getUserByUsername(username);if (user != null) {return new SimpleAuthenticationInfo(username, user.getPassword(), ByteSource.Util.bytes("salt"), username);}return null;}
}

编写配置类

package com.shiro.demo.config;import com.shiro.demo.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ShiroConfig {@Autowiredprivate MyRealm myRealm;/*** 配置DefaultWebSecurityManager*/@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager() {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("MD5");hashedCredentialsMatcher.setHashIterations(3);myRealm.setCredentialsMatcher(hashedCredentialsMatcher);defaultWebSecurityManager.setRealm(myRealm);return defaultWebSecurityManager;}/*** 配置DefaultShiroFilterChainDefinition*/@Beanpublic DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition() {DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();defaultShiroFilterChainDefinition.addPathDefinition("/login", "anon");defaultShiroFilterChainDefinition.addPathDefinition("/**", "authc");return defaultShiroFilterChainDefinition;}
}

这里扩展一下:Shiro支持的Filter有哪些,通过官方文档可以看到。
编写Controller

package com.shiro.demo.controller;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
@RequestMapping("/login")
public class LoginController {@GetMapping("/login")@ResponseBodypublic String login(String username, String password) {Subject subject = SecurityUtils.getSubject();AuthenticationToken authenticationToken = new UsernamePasswordToken(username, password);try {subject.login(authenticationToken);return "登录成功";} catch (AuthenticationException e) {e.printStackTrace();return "登录失败";}}
}

启动服务,测试代码,首先把数据库里的密码改成md5加盐加密3次后的结果,浏览器访问:http://localhost:8080/login/login?username=username1&password=password查看结果,可以看到“登录成功”,表示验证通过了。

实现前端页面

下面我们再来添加前端页面,这里使用Thymeleaf,所以要有Thymeleaf的相关依赖。
在resources目录下添加templates文件夹,添加login.html页面和main.html页面。



登录


Shiro 登录认证


用户名:
密码:


Title


Shiro 登录认证后主页面


登录用户为:

修改Controller方法。

package com.shiro.demo.controller;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpSession;@Controller
@RequestMapping("/login")
public class LoginController {@GetMapping("/toLogin")public String login() {return "login";}@GetMapping("/login")public String login(String username, String password, HttpSession httpSession) {Subject subject = SecurityUtils.getSubject();AuthenticationToken authenticationToken = new UsernamePasswordToken(username, password);try {subject.login(authenticationToken);httpSession.setAttribute("user", authenticationToken.getPrincipal().toString());return "main";} catch (AuthenticationException e) {e.printStackTrace();return "login";}}
}

同时,还要修改defaultShiroFilterChainDefinition里的策略。

@Bean
public DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition() {DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();defaultShiroFilterChainDefinition.addPathDefinition("/login/**", "anon");defaultShiroFilterChainDefinition.addPathDefinition("/**", "authc");return defaultShiroFilterChainDefinition;

启动项目,浏览器访问http://localhost:8080/login/toLogin就来到了登录页面,输入正确的用户名密码,会跳转到main页面,并且在main页面上,可以看到当前登录的用户名信息。

多个Realm的认证策略设置

多个Realm实现原理

当应用程序配置多个Realm时,例如:用户名密码校验、手机号验证码校验等等。
Shiro的ModularRealmAuthenticator会使用内部的AuthenticationStrategy组件判断认证是成功还是失败。
AuthenticationStrategy是一个无状态的组件,它在身份验证尝试中被询问4次(这4次交互所需的任何必要的状态将被作为方法参数):
(1)在所有Realm被调用之前
(2)在调用Realm的getAuthenticationInfo方法之前
(3)在调用Realm的getAuthenticationInfo方法之后
(4)在所有Realm被调用之后
认证策略的另外一项工作就是聚合所有Realm的结果信息封装至一个AuthenticationInfo实例中,并将此信息返回,以此作为Subject的身份信息。
Shiro中定义了 3 种认证策略的实现:

AuthenticationStrategy描述
AtLeastOneSuccessfulStrategy只要有一个(或更多)的Realm验证成功,那么认证将视为成功
FirstSuccessfulStrategy第一个Realm验证成功,整体认证将视为成功,且后续Realm将被忽略
AllSuccessfulStrategy所有Realm成功,认证才视为成功

ModularRealmAuthenticator内置的认证策略默认实现是AtLeastOneSuccessfulStrategy方式。可以通过配置修改策略。

多个Realm代码实现

首先需要定义多个Realm,然后创建DefaultWebSecurityManager的实例,指定AuthenticationStrategy,并设置多个Realm。

@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);List realmList = new ArrayList<>();realmList.add(myRealm1);realmList.add(myRealm2);defaultWebSecurityManager.setRealms(realmList);return defaultWebSecurityManager;
}

Remember Me功能

Shiro提供了记住我(RememberMe)的功能,比如访问一些网站时,关闭了浏览器,下次再打开时还是能记住你是谁,下次访问时无需再登录即可访问。

基本流程

  1. 首先在登录页面选中 RememberMe然后登录成功;如果是浏览器登录,一般会把RememberMe的Cookie写到客户端并保存下来
  2. 关闭浏览器再重新打开,会发现浏览器还是记住你的
  3. 访问一般的网页服务器端,仍然知道你是谁,且能正常访问
  4. 但是,如果我们访问电商平台时,如果要查看我的订单或进行支付时,此时还是需要再进行身份认证的,以确保当前用户还是你

代码实现

修改配置类,增加RememberMe的设置。

package com.shiro.demo.config;import com.shiro.demo.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ShiroConfig {@Autowiredprivate MyRealm myRealm;/*** 配置DefaultWebSecurityManager*/@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager() {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("MD5");hashedCredentialsMatcher.setHashIterations(3);myRealm.setCredentialsMatcher(hashedCredentialsMatcher);defaultWebSecurityManager.setRealm(myRealm);defaultWebSecurityManager.setRememberMeManager(rememberMeManager());return defaultWebSecurityManager;}public RememberMeManager rememberMeManager() {CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();cookieRememberMeManager.setCookie(simpleCookie());cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());return cookieRememberMeManager;}public SimpleCookie simpleCookie() {SimpleCookie simpleCookie = new SimpleCookie("rememberMe");// cookie.setDomain(domain);simpleCookie.setPath("/");simpleCookie.setHttpOnly(true);simpleCookie.setMaxAge(30 * 24 * 60 * 60);return simpleCookie;}/*** 配置DefaultShiroFilterChainDefinition*/@Beanpublic DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition() {DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();defaultShiroFilterChainDefinition.addPathDefinition("/login/toLogin", "anon");defaultShiroFilterChainDefinition.addPathDefinition("/login/login", "anon");defaultShiroFilterChainDefinition.addPathDefinition("/**", "authc");defaultShiroFilterChainDefinition.addPathDefinition("/**", "user");return defaultShiroFilterChainDefinition;}//    @Bean
//    public DefaultWebSecurityManager defaultWebSecurityManager() {
//        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//        ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
//        modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
//        defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);
//        List realmList = new ArrayList<>();
//        realmList.add(myRealm1);
//        realmList.add(myRealm2);
//        defaultWebSecurityManager.setRealms(realmList);
//        return defaultWebSecurityManager;
//    }
}

修改登录方法,传递rememberMe参数。

package com.shiro.demo.controller;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;import javax.servlet.http.HttpSession;@Controller
@RequestMapping("/login")
public class LoginController {@GetMapping("/toLogin")public String login() {return "login";}@GetMapping("/login")public String login(String username, String password, @RequestParam(defaultValue = "false") boolean rememberMe, HttpSession httpSession) {Subject subject = SecurityUtils.getSubject();AuthenticationToken authenticationToken = new UsernamePasswordToken(username, password, rememberMe);try {subject.login(authenticationToken);httpSession.setAttribute("user", authenticationToken.getPrincipal().toString());return "main";} catch (AuthenticationException e) {e.printStackTrace();return "login";}}@GetMapping("/loginRememberMe")public String loginRememberMe(HttpSession httpSession) {httpSession.setAttribute("user", "rememberMe");return "main";}
}

修改login.html页面,添加rememberMe的checkbox。



登录


Shiro 登录认证


用户名:
密码:
记住用户:

修改application.yml里的shiro.loginUrl为:/login/toLogin。
启动项目进行测试,访问:http://localhost:8080/login/loginRemeberMe,页面会自动跳转到登录页:/login/toLogin。
然后,我们输入账户密码,并勾选“记住用户”的checkbox选项框,点击登录,然后在新标签页打开/login/loginRememberMe请求,可以看到页面也跳到了main.html里。
关掉浏览器,再次访问http://localhost:8080/login/loginRememberMe,可以发现,并没有进到登录页,而是直接进到了main.html,说明rememberMe效果实现了。
观察两次登录(勾选和不勾选RememberMe)请求里cookie的值,可以发现,不勾选的时候,rememberMe的cookie值是deleteMe,勾选的时候,rememberMe的cookie值是一个加密字符串,这就是区别,Shiro通过这个加密字符串可以解析出来用户信息,就没有跳转到登录页面。

用户登录认证后登出

用户登录后,配套的有登出操作。直接通过Shiro过滤器即可实现登出。
修改mian.html



Title


Shiro 登录认证后主页面


登录用户为:
登出

修改配置类,添加logout过滤器。

defaultShiroFilterChainDefinition.addPathDefinition("/logout", "logout");

登录后,点击”登出“超链接,就回到了登录页面,观察/logout请求里cookie的rememberMe值,可以发现是deleteMe,也就是把rememberMe的信息给清除掉。

授权、角色认证

授权

用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判断。
这个工具就是Realm的doGetAuthorizationInfo方法进行判断。触发权限判断的有两种方式:

  1. 在页面中通过shiro:****属性判断
  2. 在接口服务中通过注解@Requires****进行判断

后端接口服务注解

通过给接口服务方法添加注解可以实现权限校验,可以加在控制器方法上,也可以加在业务方法上,一般加在控制器方法上。常用注解如下:

  1. @RequiresAuthentication:验证用户是否登录,等同于subject.isAuthenticated()
  2. @RequiresUser:验证用户是否被记忆,等同于subject.isAuthenticated() && subject.isRemembered()
  3. @RequiresGuest:验证是否是一个guest请求,是否是游客请求,subject.getPrinciple()的值是null
  4. @RequiresRoles:验证subject是否有相应角色,如果没有,抛出AuthorizationException异常,如果有,执行方法
  5. @RequiresPermissions:验证subject是否有相应权限,如果没有,抛出AuthorizationException异常,如果有,执行方法

授权验证

没有角色无法访问

添加一个角色验证的方法。

package com.shiro.demo.controller;import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
@RequestMapping("/index")
public class IndexController {@GetMapping("/home")@ResponseBodypublic String home() {return "hello world";}@RequiresRoles("role1")@GetMapping("/testRoles")@ResponseBodypublic String testRoles() {return "角色验证通过";}
}

修改main.html,添加一个超链接,用于触发刚刚的请求。



Title


Shiro 登录认证后主页面


登录用户为:
登出
测试角色

修改MyRealm,重写授权方法。

/*** 自定义授权方法*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("进入自定义授权方法");return null;
}

重启服务,点击超链接进行访问,此时会报错,控制台可以看到,已经走了自定义的授权方法,下面我们就需要再自定义授权方法里写一些代码,实现角色判断和权限判断。

获取角色进行验证

先尝试写死,手动给用户提供一个角色,正常情况下,这个角色是从数据库里获取的,再次通过浏览器访问,查看效果,此时可以看到角色验证通过了。

/*** 自定义授权方法*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();simpleAuthorizationInfo.addRole("role1");return simpleAuthorizationInfo;
}

下面把这块改成活的,也就是从数据库获取角色,相关SQL参考这里。
编写mapper方法,service实现,修改MyRealm。

@Select("SELECT role_name FROM user_role WHERE username=#{username}")
List getRoleListByUsername(String username);@Override
public List getRoleListByUsername(String username) {return userMapper.getRoleListByUsername(username);
}/*** 自定义授权方法*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();List roleList = userService.getRoleListByUsername(principalCollection.getPrimaryPrincipal().toString());simpleAuthorizationInfo.addRoles(roleList);return simpleAuthorizationInfo;
}

进行测试,此时数据就是活的啦。

获取权限进行验证

权限验证同理,相关SQL参考这里。
编写mapper方法,service实现,修改MyRealm。

@Select("SELECT permission FROM role_permission WHERE role_name IN (SELECT role_name FROM user_role WHERE username=#{username});")
List getPermissionListByUsername(String username);@Override
public List getPermissionListByUsername(String username) {return userMapper.getPermissionListByUsername(username);
}@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();List roleList = userService.getRoleListByUsername(principalCollection.getPrimaryPrincipal().toString());simpleAuthorizationInfo.addRoles(roleList);List permissionList = userService.getPermissionListByUsername(principalCollection.getPrimaryPrincipal().toString());simpleAuthorizationInfo.addStringPermissions(permissionList);return simpleAuthorizationInfo;
}

修改controller方法,添加权限验证注解。

@RequiresPermissions("video:get")
@GetMapping("/testPermissions")
@ResponseBody
public String testPermissions() {return "权限验证通过";
}

修改main.html,添加请求入口。

测试权限

通过浏览器访问进行测试。

异常处理

对于没有角色,没有权限这样的情况,会报错误页面,这样不够友好,我们可以创建异常处理类,将错误信息提示出来。
创建GlobalException.java。

package com.shiro.demo.exception;import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice
public class GlobalException {@ResponseBody@ExceptionHandler(UnauthorizedException.class)public String unauthorizedException(Exception e) {return "无权限";}@ResponseBody@ExceptionHandler(AuthorizationException.class)public String authorizationException(Exception e) {return "权限认证失败";}
}

前端页面授权验证

要想让Thymeleaf支持Shiro语法,需要添加依赖。

com.github.theborakompanionithymeleaf-extras-shiro2.0.0

添加配置类用于解析shiro属性。

/*** 配置ShiroDialect解析Shiro相关属性*/
@Bean
public ShiroDialect shiroDialect() {return new ShiroDialect();
}

shiro属性,下面写成了标签的样子,实际是shiro属性。

用户没有身份验证时显示相应信息,即游客访问信息
用户已经身份验证/记住我登录后显示相应的信息
用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的
用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证
相当于((User)Subject.getPrincipals()).getUsername()
如果当前Subject没有权限将显示body体内容
如果当前Subject有角色将显示body体内容
如果当前Subject有任意一个角色(或的关系)将显示body体内容
如果当前Subject没有角色将显示body体内容
如果当前Subject有权限将显示body体内容

修改main.html,可以实现将无权限的标签进行隐藏。

测试角色
测试权限

实现缓存

缓存工具EhCache

EhCache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。可以和大部分Java项目无缝整合,例如:Hibernate中的缓存就是基于EhCache实现的。
EhCache支持内存和磁盘存储,默认存储在内存中,如内存不够时把缓存数据同步到磁盘中。EhCache支持基于Filter的Cache实现,也支持Gzip压缩算法。
EhCache直接在JVM虚拟机中缓存,速度快,效率高,EhCache缺点是缓存共享麻烦,集群分布式应用使用不方便。

EhCache搭建使用

给项目加入Ehcache,添加pom依赖。

net.sf.ehcacheehcache2.6.11pom

在resources下添加ehcache.xml配置文件。




创建测试类进行测试。

package com.shiro.demo.test;import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;import java.io.InputStream;public class Test {public static void main(String[] args) {InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("classpath:ehchace.xml");CacheManager cacheManager = new CacheManager(inputStream);Cache cache = cacheManager.getCache("HelloWorldCache");Element element = new Element("name", "value");cache.put(element);Element cacheElement = cache.get("name");System.out.println(cacheElement.getObjectValue());}
}

Shiro整合EhCache

Shiro官方提供了shiro-ehcache,实现了整合EhCache作为Shiro的缓存工具。可以缓存认证执行的Realm方法,减少对数据库的访问,提高认证效率。
添加pom依赖。


org.apache.shiroshiro-ehcache1.4.2

commons-iocommons-io2.6

在resources下添加配置文件ehcache/ehcache-shiro.xml。




修改ShiroConfig类。

/*** 配置DefaultWebSecurityManager*/
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("MD5");hashedCredentialsMatcher.setHashIterations(3);myRealm.setCredentialsMatcher(hashedCredentialsMatcher);defaultWebSecurityManager.setRealm(myRealm);defaultWebSecurityManager.setRememberMeManager(rememberMeManager());defaultWebSecurityManager.setCacheManager(getEhCacheManager());// 设置缓存管理器return defaultWebSecurityManager;
}
private EhCacheManager getEhCacheManager() {EhCacheManager ehCacheManager = new EhCacheManager();try(InputStream inputStream = ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml")) {CacheManager cacheManager = new CacheManager(inputStream);ehCacheManager.setCacheManager(cacheManager);} catch (IOException e) {e.printStackTrace();}return ehCacheManager;
}

启动项目进行测试,查看日志,执行了3条SQL。

SELECT id,username,password,password_salt FROM user WHERE username=?
SELECT role_name FROM user_role WHERE username=?
SELECT permission FROM role_permission WHERE role_name IN (SELECT role_name FROM user_role WHERE username=?)

再次点击main.html页面的权限验证,角色验证,控制台并没有打印内容,说明此时的信息是从cache里获取的,并没有查询数据库。

会话管理

SessionManager

会话管理器,负责创建和管理用户的会话(Session)生命周期,它能够在任何环境中在本地管理用户会话,即使没有Web/Servlet/EJB容器,也一样可以保存会话。默认情况下,Shiro会检测当前环境中现有的会话机制(比如Servlet容器)进行适配,如果没有(比如独立应用程序或者非Web环境),它将会使用内置的企业会话管理器来提供相应的会话管理服务,其中还涉及一个名为SessionDAO的对象。SessionDAO负责Session的持久化操作(CRUD),允许Session数据写入到后端持久化数据库。

会话管理实现

SessionManager由SecurityManager管理。Shiro提供了三种实现。
在这里插入图片描述
顺便说下这个图怎么生成的,先找到SessionManager接口,右键选择Diagrams-Show Diagram…,选择Java class diagram。
右键SessionManager,选择Show Implementations,按住Ctrl+鼠标左键,勾选需要的类,最后按下回车。

  1. DefaultSessionManager:用于JavaSE环境
  2. ServletContainerSessionManager:用于web环境,直接使用Servlet容器的会话
  3. DefaultWebSessionManager:用于web环境,自己维护会话(不使用Servlet容器的会话管理)

获得Session方式

Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("key", "value");
Object value = session.getAttribute("key");

Controller中的request,在Shiro过滤器的doFilterInternal()方法,被包装成ShiroHttpServletRequest,SecurityManager和 SessionManager会话管理器决定session来源于ServletRequest还是由Shiro管理的会话。无论是通过request.getSessionsubject.getSession获取到session,操作session,两者都是等价的。

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...