权限控制
自定义Shiro Filter过滤器
-
背景知识:
- /admin/order= roles["admin, root"] ,表示 /admin/order 这个接口需要用户同时具备 admin 与 root 角色才可访问, 相当于hasAllRoles() 这个判断方法
-
我们的需求:
-
订单信息,可以由角色 普通管理员 admin 或者 超级管理员 root 查看
-
只要用户具备其中一个角色即可(shiro不能满足该条件)
-
// mappedValue -》roles[admin,root]
public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception {
Subject subject = getSubject(req, resp);
//获取当前访问路径所需要的角色集合
String[] rolesArray = (String[]) mappedValue;
//没有角色限制,有权限访问
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
//当前subject是rolesArray中的任何一个,则有权限访问
for (int i = 0; i < rolesArray.length; i++) {
if (subject.hasRole(rolesArray[i])) {
return true;
}
}
return false;
}
}
FactoryBean配置
Map<String, Filter> filtersMap = new LinkedHashMap<>();
filtersMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin,root]");
shiroFilterFactoryBean.setFilters(filtersMap);
自定义Shiro Filter过滤器验证
-
配置不同的角色,验证自定义过滤器是否有效
登录(post) localhost:8080/pub/login 管理员查看后台信息(get) localhost:8080/admin/video/order
存在问题:每次权限校验都会查询DB
性能提升之Redis整合CacheManager
-
使用原因?
- 授权的时候每次都去查询数据库,对于频繁访问的接口,性能和响应速度比较慢,所以使用缓存
1、业务方法中加缓存
2、本实例的方法
-
步骤
-
- 加依赖
<!-- shiro+redis缓存插件 --> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.1.0</version> </dependency>
- 配置bean
//使用自定义的cacheManager securityManager.setCacheManager(cacheManager()); /** * 配置redisManager * */ public RedisManager getRedisManager(){ RedisManager redisManager = new RedisManager(); redisManager.setHost("localhost"); redisManager.setPort(6379); return redisManager; } /** * 配置具体cache实现类 * @return */ public RedisCacheManager cacheManager(){ RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(getRedisManager()); //设置过期时间,单位是秒,20s, redisCacheManager.setExpire(20); return redisCacheManager; }
-
安装redis (如何不会使用redis,则参考网上的博客文章)
-
建议本地安装,默认是不能外网访问的,然后不是守护进程方式
-
原有的问题
-
redis缓存key 要知名redis的唯一标示key
class java.lang.String must has getter for field: authCacheKey or id\nWe need a field to identify this Cache Object in Redis. So you need to defined an id field which you can get unique id to identify this principal. For example, if you use UserInfo as Principal class, the id field maybe userId, userName, email, etc. For example, getUserId(), getUserName(), getEmail(), etc.\nDefault value is authCacheKey or id, that means your principal object has a method called \"getAuthCacheKey()\" or \"getId()\""
-
改造原有的逻辑,修改缓存的唯一key
-
doGetAuthorizationInfo 方法 原有: String username = (String)principals.getPrimaryPrincipal(); User user = userService.findAllUserInfoByUsername(username); 改为 User newUser = (User)principals.getPrimaryPrincipal(); User user = userService.findAllUserInfoByUsername(newUser.getUsername()); doGetAuthenticationInfo方法 原有: return new SimpleAuthenticationInfo(username, user.getPassword(), this.getClass().getName()); 改为 return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
修改redis缓存中key的过期时间
public RedisCacheManager redisCacheManager(){
RedisCacheManager redisCacheManager=new RedisCacheManager();
redisCacheManager.setRedisManager(getRedisManager());
//设置过期时间 单位秒
redisCacheManager.setExpire(30);
return redisCacheManager;
}
Redis整合SessionManager,管理Session会话
-
为啥session也要持久化?
- 重启应用,用户无感知,可以继续以原先的状态继续访问(通过sessionId的有无来判断是否登录过(服务端内存中存储))
session不能那样,权限校验的时候能在redis中存储,但是session要持久化
重启之后内存中但数据丢失(session要持久化 持久化到redis/mysql)
定时任务实现sessionId的保活
-
怎么持久化?
//配置session持久化 customSessionManager.setSessionDAO(redisSessionDAO()); /** * 自定义session持久化 * @return */ public RedisSessionDAO redisSessionDAO(){ RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(getRedisManager()); return redisSessionDAO; }
注意点:
-
DO对象需要实现序列化接口 Serializable domain
-
logout接口和以前一样调用,请求logout后会删除redis里面的对应的key,即删除对应的token
-
ShiroConfig常用bean 介绍
-
LifecycleBeanPostProcessor
- 作用:管理shiro一些bean的生命周期 即bean初始化 与销毁
@Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); }
-
AuthorizationAttributeSourceAdvisor
- 作用:加入注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)类似于@RequeststParam
@Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; }
*/
@PostMapping("/login")
@RequiresGuest
public JsonData login(@RequestBody UserQuery userQuery, HttpServletRequest request, HttpServletResponse response){
Subject subject=SecurityUtils.getSubject();
Map<String,Object> info=new HashMap<>();
-
DefaultAdvisorAutoProxyCreator
- 作用: 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,需要在LifecycleBeanPostProcessor创建后才可以创建
@Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setUsePrefix(true); return defaultAdvisorAutoProxyCreator; }