文章目录
- 0、文章简介
- 1、配置session管理
- 2、权限管理—授权
- 2.1、搭建环境(配置信息)
- 2.2、过滤器设置权限
- 2.3、注解设置权限
- 2.4、授权
- 2.5、在HTML页面上基于Thymeleaf的支持
- 2.6、shiro标签
- 3、缓存的使用
- 3.1、为什么使用缓存
- 3.2、导入依赖
- 3.3、在resources下创建ehcache.xml配置文件
- 3.4、在ShiroConfig中配置缓存
- 4、实现 remeberMe 功能
- 4.1、配置ShiroConfig
- 4.2、在页面上添加rememberMe这个功能
- 4.3、在认证的token上设置rememberMe这个属性
- 4.4、配置过滤器
- 4.5、测试功能
0、文章简介
本篇文章使用 springboot整合shiro,完成了:session管理、授权管理(过滤器授权、注解授权)、授权缓存管理、remeberMe等功能。
1、配置session管理
- 在 ShiroConfig.java 中添加如下:
/**
* Session 管理
*/
public DefaultWebSessionManager sessionManager() {
logger.info("Session 管理器初始化ing...");
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 设置 session 的过期时间(单位是毫秒)(默认值是30分钟)
sessionManager.setGlobalSessionTimeout(60000);
// session 到期的时候是否自动删除
sessionManager.setDeleteInvalidSessions(true);
logger.info("Session 管理器初始化完成...");
return sessionManager;
}
- 修改安全管理器配置方法,加上配置session管理器
@Bean("securityManager")
public SecurityManager securityManager(UserRealm userRealm) {
logger.info("securityManager安全管理器初始化ing...");
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
// 设置自定义realm
defaultWebSecurityManager.setRealm(userRealm);
// 设置session管理器
defaultWebSecurityManager.setSessionManager(sessionManager());
logger.info("securityManager安全管理器初始化完成...");
return defaultWebSecurityManager;
}
这里设置的Session过期时间为1分钟,即1分钟之后,再次访问 主页,需要重新登录
2、权限管理—授权
2.1、搭建环境(配置信息)
在SpringBoot整合shiro之基本使用的基础上
给html、Controller、Service、Mapper添加上对用户的 CRUD操作,这里主要是测试权限管理,只演示效果
- 给控制器添加如下方法
@RequestMapping("addUser")
public String addUser() {
// 这里目的是测试shiro的权限管理,就不设置用户注册了
User user = new User("username", "pwd", "salt");
userService.addUser(user);
return "success";
}
@RequestMapping("deleteUser")
public String deleteUser(Integer id) {
userService.deleteUser(id);
return "success";
}
@RequestMapping("updataUser")
public String updateUser() {
userService.updateUser();
return "success";
}
@RequestMapping("toUnAuthorized")
public String toUnAuthorized() {
return "unAuthorized";
}
- 编写页面
2.1、向 index.html 中添加以下内容
<p><a href="/addUser">添加用户(过滤器方式授权)</a></p>
<p><a href="/updateUser">修改用户(无权限即可访问)</a></p>
<p><a href="/deleteUser">删除用户(注解方式授权)</a></p>
2.2、创建 success.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>操作成功</h1>
<h3><a href="/toIndex">返回主页</a></h3>
</body>
</html>
2.3、创建 unAuthorized.html 页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>提示页面</title>
</head>
<body>
<h1>权限不足</h1>
<h3><a href="/toIndex">返回主页</a></h3>
</body>
</html>
- 配置 ShiroConfig.java
3.1、在 ShiroConfig.java 中添加如下两个方法
/**
* 下边两个方法表示 配置AOP对注解的支持(也就是shiro中注解的支持)
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
3.2、配置权限不足页面
在配置 shiro过滤器 的方法(shiroFilterFactoryBean)中添加这一行代码,加到map之前
// 如果没有权限访问 那么就跳转到该页面,提示权限不足
// 该配置只能对 通过过滤器方式设置权限的生效,使用注解方式的需要使用全局异常捕获
shiroFilterFactoryBean.setUnauthorizedUrl("/toUnAuthorized");
- 配置全局异常解析器,捕获 UnauthorizedException,跳转到权限不足页面
不在本文讨论范围内,如需要请自行百度,或参照 【SpringBoot整合Hibernate-invalidate(JSR303校验)】这里关于配置全局异常解析器部分
2.2、过滤器设置权限
在 shiro过滤器方法 的 map中,添加以下内容即可
注:不能加到 "/" 之后**
filterChainDefinitionMap.put("/addUser", "perms[user:add]"); // 必须拥有这个权限,才能访问 addUser
2.3、注解设置权限
修改 Controller,添加注解即可
@RequiresPermissions({"user:delete"}) // 表示必须拥有这个权限才能访问,否则会抛出异常
@RequestMapping("deleteUser")
public String deleteUser(Integer id) {
userService.deleteUser(id);
return "success";
}
/* 常用注解:
* @RequiresRoles() 必须拥有某角色才能执行
* @RequiresUser() 必须是某用户才能执行
* @RequiresPermissions() // 表示必须拥有这个权限才能访问
*/
到这里,可自行测试一下代码:
直接运行项目,点击那三个按钮,看是否达到指定的效果即可
添加:跳转到权限不足页面;修改:正常访问;删除:抛出异常
2.4、授权
通过设置 UserRealm.java 完成授权操作
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 1. 获取当前用户的用户名
User user = (User) principalCollection.getPrimaryPrincipal();
// 2. 通过用户名去数据库查询用户对应的权限信息
// 这里不操作数据库,直接赋值模拟操作
// 3. 将查询到的权限信息封装到一个Set集合中,或是查询到的角色信息
HashSet<String> permissions = new HashSet<>();
permissions.add("user:add");
permissions.add("user:delete");
HashSet<String> roles = new HashSet<>();
roles.add("admin");
// 4. 创建 授权信息对象,把权限集合传到对象中,或是角色集合
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setStringPermissions(permissions);
simpleAuthorizationInfo.setRoles(roles);
// 5. 返回该对象
return simpleAuthorizationInfo;
}
此时再次进行测试,点击所有的按钮,都可以正常访问了
2.5、在HTML页面上基于Thymeleaf的支持
- 导入依赖
<!-- 导入thymeleaf对shiro的支持包 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
- 配置 支持shiro标签
在 ShiroConfig.java 中添加如下方法
/**
* 配置方言: shiro-dialect,是为了能在html页面引用shiro标签
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
// 在页面html标签中加入以下内容
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
2.6、shiro标签
<!-- 自行测试即可
<shiro:authenticated>
<span>
用户的身份验证是成功的
</span>
</shiro:authenticated>
<shiro:guest>
<span>你是游客</span>
</shiro:guest>
<shiro:hasPermission name="user:add">
<span>用户必须具有某一个权限才能访问</span>
</shiro:hasPermission>
<shiro:hasAllRoles name="buyer,seller">
<span>拥有某一个角色下面才显示</span>
</shiro:hasAllRoles>
<shiro:lacksPermission>
<span>没有某一个权限的时候才能访问</span>
</shiro:lacksPermission>
<shiro:lacksRole name="xxx">
<span>没有某一个角色的时候才能访问</span>
</shiro:lacksRole>
<shiro:notAuthenticated>
<span>没有认证通过才能显示</span>
</shiro:notAuthenticated>
-->
3、缓存的使用
3.1、为什么使用缓存
为什么要使用缓存?
测试 授权的时候,每一次都会去访问 realm中的授权的方法
这样的话每次都要从数据库中查询权限,数据库的压力比较大,所以缓存就应运而生了
缓存:缓存的是授权信息
3.2、导入依赖
<!-- 缓存需要的包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
3.3、在resources下创建ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
<diskStore path="D:\mytemp"/>
<cache name="users"
timeToLiveSeconds="300"
maxEntriesLocalHeap="1000"/>
<!--
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
<defaultCache name="defaultCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
3.4、在ShiroConfig中配置缓存
1、注入缓存配置
/**
* 配置 缓存管理器
*/
@Bean
public EhCacheManager ehCacheManager() {
logger.info("ehCacheManager缓存 初始化ing...");
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
logger.info("ehCacheManager缓存 初始化完成...");
return ehCacheManager;
}
2、在安全管理器中配置缓存管理器
@Bean("securityManager")
public SecurityManager securityManager(UserRealm userRealm) {
logger.info("securityManager安全管理器初始化ing...");
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
// 设置自定义realm
defaultWebSecurityManager.setRealm(userRealm);
// 设置session管理器
defaultWebSecurityManager.setSessionManager(sessionManager());
// 设置缓存管理器
defaultWebSecurityManager.setCacheManager(ehCacheManager());
logger.info("securityManager安全管理器初始化完成...");
return defaultWebSecurityManager;
}
至此,缓存已经配置完成,再次测试,点击按钮的时候,无论点击多少次,都只执行了一次授权方法
4、实现 remeberMe 功能
shiro使用了 Session + Cookie的模式来做 记住我 这个功能
4.1、配置ShiroConfig
/**
* 配置 CookieRememberMe管理器,实现登录时记住我的功能
*/
@Bean
public CookieRememberMeManager rememberMeManager(){
logger.info("CookieRememberMeManager 初始化ing...");
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
//设置自定义的Cookie
rememberMeManager.setCookie(simpleCookie());
logger.info("CookieRememberMeManager 初始化完成...");
return rememberMeManager;
}
/**
* 自定义一个Cookie对象,保存rememberMe的cookie信息
*/
@Bean
public SimpleCookie simpleCookie(){
logger.info("SimpleCookie 自定义Cookie对象初始化ing...");
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
// 最大保存时间,单位秒,这里表示保存30天
simpleCookie.setMaxAge(2592000);
logger.info("SimpleCookie 自定义Cookie对象初始化完成...");
return simpleCookie;
}
// 在安全管理器中添加 CookieRememberMeManager
/**
* 配置 SecurityManager(安全管理器)
*/
@Bean("securityManager")
public SecurityManager securityManager(UserRealm userRealm) {
logger.info("securityManager安全管理器初始化ing...");
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
// 设置自定义realm
defaultWebSecurityManager.setRealm(userRealm);
// 设置session管理器
defaultWebSecurityManager.setSessionManager(sessionManager());
// 设置缓存管理器
defaultWebSecurityManager.setCacheManager(ehCacheManager());
// 设置rememberMe管理器
defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
logger.info("securityManager安全管理器初始化完成...");
return defaultWebSecurityManager;
}
4.2、在页面上添加rememberMe这个功能
<form action="/login" method="post">
用户名:<input type="text" name="name" ><span th:text="${session.userNameError}"></span><br>
密码:<input type="text" name="password" ><span th:text="${session.passwordError}"></span><br>
记住我:<input type="checkbox" name="rememberMe"><br>
<input type="submit" value="登录">
</form>
4.3、在认证的token上设置rememberMe这个属性
UserController.java 中 login方法
@PostMapping("login")
public String login(User user, HttpSession session, boolean rememberMe) {
logger.info("接受到前端的数据 rememberMe = " + rememberMe);
// 封装成请求对象
UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPassword());
// 设置记住我这个功能是否开启
token.setRememberMe(rememberMe);
// 获取登录的主体对象
Subject subject = SecurityUtils.getSubject();
// ...
}
4.4、配置过滤器
在过滤器中配置哪些页面在使用了rememberme这个功能之后可以直接访问
直接在 map中添加即可
注意:注意过滤器的顺序,在 /** 之前
// 表示如果使用了rememberMe这个功能,就可以不需要认证直接访问 /updateUser
filterChainDefinitionMap.put("/updateUser", "user");
4.5、测试功能
- 配置完成之后直接启动项目
- 进行登录,登录时勾选 记住我
- 进入主页,测试是否能正常访问权限
- 然后关闭浏览器,目的是消除 session
- 再次打开浏览器,直接访问主页,会直接进入到登录页面
- 不要登录,从地址栏直接访问 /updateUser ,能正常访问证明 rememberMe 功能完成
注:
如果第一次登录选择记住我,第二次登录的时候没有选择记住我,那么再次关闭浏览器,重新访问 /updateUser 则无法访问。
原因:第二次登录时,会接收到前端传递的数据 rememberMe = false,重新设置到了token中,cookie会被清除