SpringBoot + Redis + Shiro 实现权限管理(转)

概述

  本文基于网上整理,为了实现将Shiro框架的session存储到redis里面,进而实现基于Niginx负载均衡,多站点部署;

maven下shiro依赖

1<!-- shiro --> 2 <dependency> 3 <groupId>org.apache.shiro</groupId> 4 <artifactId>shiro-spring</artifactId> 5 <version>1.4.0</version> 6 </dependency> 7 <dependency> 8 <groupId>org.apache.shiro</groupId> 9 <artifactId>shiro-ehcache</artifactId> 10 <version>1.4.0</version> 11 </dependency> 12 13 <!-- shiro+redis缓存插件 --> 14 <dependency> 15 <groupId>org.crazycake</groupId> 16 <artifactId>shiro-redis</artifactId> 17 <version>2.4.2.1-RELEASE</version> 18 </dependency>

主要代码编写

1、ShiroConfig配置

1package com.chaoqi.springboot_shiro_redis.config; 2 3import com.chaoqi.springboot_shiro_redis.secutity.KickoutSessionControlFilter; 4import com.chaoqi.springboot_shiro_redis.secutity.MyShiroRealm; 5import org.apache.shiro.mgt.SecurityManager; 6import org.apache.shiro.spring.LifecycleBeanPostProcessor; 7import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; 8import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 9import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 10import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; 11import org.crazycake.shiro.RedisCacheManager; 12import org.crazycake.shiro.RedisManager; 13import org.crazycake.shiro.RedisSessionDAO; 14import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 15import org.springframework.context.annotation.Bean; 16import org.springframework.context.annotation.Configuration; 17 18import javax.servlet.Filter; 19import java.util.LinkedHashMap; 20import java.util.Map; 21 22 23@Configuration 24public class ShiroConfig {

    @Value("${redis.host}")
    private String redisHost;

    @Value("${redis.port}")
    private String redisPort;

1@Bean 2 public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { 3 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 4 shiroFilterFactoryBean.setSecurityManager(securityManager); 5 // 没有登陆的用户只能访问登陆页面 6 shiroFilterFactoryBean.setLoginUrl("/auth/login"); 7 // 登录成功后要跳转的链接 8 shiroFilterFactoryBean.setSuccessUrl("/auth/index"); 9 // 未授权界面; ----这个配置了没卵用,具体原因想深入了解的可以自行百度 10 //shiroFilterFactoryBean.setUnauthorizedUrl("/auth/403"); 11 //自定义拦截器 12 Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>(); 13 //限制同一帐号同时在线的个数。 14 filtersMap.put("kickout", kickoutSessionControlFilter()); 15 shiroFilterFactoryBean.setFilters(filtersMap); 16 // 权限控制map. 17 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); 18 filterChainDefinitionMap.put("/css/**", "anon"); 19 filterChainDefinitionMap.put("/js/**", "anon"); 20 filterChainDefinitionMap.put("/img/**", "anon"); 21 filterChainDefinitionMap.put("/auth/login", "anon"); 22 filterChainDefinitionMap.put("/auth/logout", "logout"); 23 filterChainDefinitionMap.put("/auth/kickout", "anon"); 24 filterChainDefinitionMap.put("/**", "authc,kickout"); 25 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 26 return shiroFilterFactoryBean; 27 } 28 29 @Bean 30 public SecurityManager securityManager() { 31 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 32 // 设置realm. 33 securityManager.setRealm(myShiroRealm()); 34 // 自定义缓存实现 使用redis 35 securityManager.setCacheManager(cacheManager()); 36 // 自定义session管理 使用redis 37 securityManager.setSessionManager(sessionManager()); 38 return securityManager; 39 } 40 41 /** 42 * 身份认证realm; (这个需要自己写,账号密码校验;权限等) 43 * 44 * @return 45 */ 46 @Bean 47 public MyShiroRealm myShiroRealm() { 48 MyShiroRealm myShiroRealm = new MyShiroRealm(); 49 return myShiroRealm; 50 } 51 52 /** 53 * cacheManager 缓存 redis实现 54 * 使用的是shiro-redis开源插件 55 * 56 * @return 57 */ 58 public RedisCacheManager cacheManager() { 59 RedisCacheManager redisCacheManager = new RedisCacheManager(); 60 redisCacheManager.setRedisManager(redisManager()); 61 return redisCacheManager; 62 } 63 64 /** 65 * 配置shiro redisManager 66 * 使用的是shiro-redis开源插件 67 * 68 * @return 69 */ 70 public RedisManager redisManager() { 71 RedisManager redisManager = new RedisManager(); 72 redisManager.setHost(redisHost); 73 redisManager.setPort(redisPort); 74 redisManager.setExpire(1800);// 配置缓存过期时间 75 redisManager.setTimeout(0); 76 // redisManager.setPassword(password); 77 return redisManager; 78 } 79 80 /** 81 * Session Manager 82 * 使用的是shiro-redis开源插件 83 */ 84 @Bean 85 public DefaultWebSessionManager sessionManager() { 86 DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); 87 sessionManager.setSessionDAO(redisSessionDAO()); 88 return sessionManager; 89 } 90 91 /** 92 * RedisSessionDAO shiro sessionDao层的实现 通过redis 93 * 使用的是shiro-redis开源插件 94 */ 95 @Bean 96 public RedisSessionDAO redisSessionDAO() { 97 RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); 98 redisSessionDAO.setRedisManager(redisManager()); 99 return redisSessionDAO; 100 } 101 102 /** 103 * 限制同一账号登录同时登录人数控制 104 * 105 * @return 106 */ 107 @Bean 108 public KickoutSessionControlFilter kickoutSessionControlFilter() { 109 KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter(); 110 kickoutSessionControlFilter.setCacheManager(cacheManager()); 111 kickoutSessionControlFilter.setSessionManager(sessionManager()); 112 kickoutSessionControlFilter.setKickoutAfter(false); 113 kickoutSessionControlFilter.setMaxSession(1); 114 kickoutSessionControlFilter.setKickoutUrl("/auth/kickout"); 115 return kickoutSessionControlFilter; 116 } 117 118 119 /*** 120 * 授权所用配置 121 * 122 * @return 123 */ 124 @Bean 125 public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { 126 DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); 127 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); 128 return defaultAdvisorAutoProxyCreator; 129 } 130 131 /*** 132 * 使授权注解起作用不如不想配置可以在pom文件中加入 133 * <dependency> 134 *<groupId>org.springframework.boot</groupId> 135 *<artifactId>spring-boot-starter-aop</artifactId> 136 *</dependency> 137 * @param securityManager 138 * @return 139 */ 140 @Bean 141 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ 142 AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); 143 authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); 144 return authorizationAttributeSourceAdvisor; 145 } 146 147 /** 148 * Shiro生命周期处理器 149 * 150 */ 151 @Bean 152 public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { 153 return new LifecycleBeanPostProcessor(); 154 } 155 156}

 2、自定义Realm

1package com.chaoqi.springboot_shiro_redis.secutity; 2 3import com.chaoqi.springboot_shiro_redis.service.SysRoleService; 4import com.chaoqi.springboot_shiro_redis.service.UserService; 5import com.chaoqi.springboot_shiro_redis.dao.domain.SysUser; 6import org.apache.shiro.authc.*; 7import org.apache.shiro.authz.AuthorizationInfo; 8import org.apache.shiro.authz.SimpleAuthorizationInfo; 9import org.apache.shiro.realm.AuthorizingRealm; 10import org.apache.shiro.subject.PrincipalCollection; 11import org.slf4j.LoggerFactory; 12import org.springframework.beans.factory.annotation.Autowired; 13 14import java.util.*; 15 16public class MyShiroRealm extends AuthorizingRealm { 17 private static org.slf4j.Logger logger = LoggerFactory.getLogger(MyShiroRealm.class); 18 19 //如果项目中用到了事物,@Autowired注解会使事物失效,可以自己用get方法获取值 20 @Autowired 21 private SysRoleService roleService; 22 @Autowired 23 private UserService userService; 24 25 /** 26 * 认证信息.(身份验证) : Authentication 是用来验证用户身份 27 * 28 */ 29 @Override 30 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { 31 logger.info("---------------- 执行 Shiro 凭证认证 ----------------------"); 32 UsernamePasswordToken token = (UsernamePasswordToken) authcToken; 33 String name = token.getUsername(); 34 String password = String.valueOf(token.getPassword()); 35 SysUser user = new SysUser(); 36 user.setUserName(name); 37 user.setPassWord(password); 38 // 从数据库获取对应用户名密码的用户 39 SysUser userList = userService.getUser(user); 40 if (userList != null) { 41 // 用户为禁用状态 42 if (userList.getUserEnable() != 1) { 43 throw new DisabledAccountException(); 44 } 45 logger.info("---------------- Shiro 凭证认证成功 ----------------------"); 46 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( 47 userList, //用户 48 userList.getPassWord(), //密码 49 getName() //realm name 50 ); 51 return authenticationInfo; 52 } 53 throw new UnknownAccountException(); 54 } 55 56 /** 57 * 授权 58 */ 59 @Override 60 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 61 logger.info("---------------- 执行 Shiro 权限获取 ---------------------"); 62 Object principal = principals.getPrimaryPrincipal(); 63 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 64 if (principal instanceof SysUser) { 65 SysUser userLogin = (SysUser) principal; 66 Set<String> roles = roleService.findRoleNameByUserId(userLogin.getId()); 67 authorizationInfo.addRoles(roles); 68 69 Set<String> permissions = userService.findPermissionsByUserId(userLogin.getId()); 70 authorizationInfo.addStringPermissions(permissions); 71 } 72 logger.info("---- 获取到以下权限 ----"); 73 logger.info(authorizationInfo.getStringPermissions().toString()); 74 logger.info("---------------- Shiro 权限获取成功 ----------------------"); 75 return authorizationInfo; 76 } 77 78}

3、限制并发人数登陆

1package com.chaoqi.springboot_shiro_redis.secutity; 2 3import com.alibaba.fastjson.JSON; 4import com.chaoqi.springboot_shiro_redis.dao.domain.SysUser; 5import org.apache.shiro.cache.Cache; 6import org.apache.shiro.cache.CacheManager; 7import org.apache.shiro.session.Session; 8import org.apache.shiro.session.mgt.DefaultSessionKey; 9import org.apache.shiro.session.mgt.SessionManager; 10import org.apache.shiro.subject.Subject; 11import org.apache.shiro.web.filter.AccessControlFilter; 12import org.apache.shiro.web.util.WebUtils; 13 14import javax.servlet.ServletRequest; 15import javax.servlet.ServletResponse; 16import javax.servlet.http.HttpServletRequest; 17import java.io.IOException; 18import java.io.PrintWriter; 19import java.io.Serializable; 20import java.util.Deque; 21import java.util.HashMap; 22import java.util.LinkedList; 23import java.util.Map; 24 25public class KickoutSessionControlFilter extends AccessControlFilter { 26 27 private String kickoutUrl; //踢出后到的地址 28 private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户 29 private int maxSession = 1; //同一个帐号最大会话数 默认1 30 31 private SessionManager sessionManager; 32 private Cache<String, Deque<Serializable>> cache; 33 34 public void setKickoutUrl(String kickoutUrl) { 35 this.kickoutUrl = kickoutUrl; 36 } 37 38 public void setKickoutAfter(boolean kickoutAfter) { 39 this.kickoutAfter = kickoutAfter; 40 } 41 42 public void setMaxSession(int maxSession) { 43 this.maxSession = maxSession; 44 } 45 46 public void setSessionManager(SessionManager sessionManager) { 47 this.sessionManager = sessionManager; 48 } 49 //设置Cache的key的前缀 50 public void setCacheManager(CacheManager cacheManager) { 51 this.cache = cacheManager.getCache("shiro_redis_cache"); 52 } 53 54 @Override 55 protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { 56 return false; 57 } 58 59 @Override 60 protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { 61 Subject subject = getSubject(request, response); 62 if(!subject.isAuthenticated() && !subject.isRemembered()) { 63 //如果没有登录,直接进行之后的流程 64 return true; 65 } 66 67 68 Session session = subject.getSession(); 69 SysUser user = (SysUser) subject.getPrincipal(); 70 String username = user.getUserName(); 71 Serializable sessionId = session.getId(); 72 73 //读取缓存 没有就存入 74 Deque<Serializable> deque = cache.get(username); 75 76 //如果此用户没有session队列,也就是还没有登录过,缓存中没有 77 //就new一个空队列,不然deque对象为空,会报空指针 78 if(deque==null){ 79 deque = new LinkedList<Serializable>(); 80 } 81 82 //如果队列里没有此sessionId,且用户没有被踢出;放入队列 83 if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) { 84 //将sessionId存入队列 85 deque.push(sessionId); 86 //将用户的sessionId队列缓存 87 cache.put(username, deque); 88 } 89 90 //如果队列里的sessionId数超出最大会话数,开始踢人 91 while(deque.size() > maxSession) { 92 Serializable kickoutSessionId = null; 93 if(kickoutAfter) { //如果踢出后者 94 kickoutSessionId = deque.removeFirst(); 95 //踢出后再更新下缓存队列 96 cache.put(username, deque); 97 } else { //否则踢出前者 98 kickoutSessionId = deque.removeLast(); 99 //踢出后再更新下缓存队列 100 cache.put(username, deque); 101 } 102 103 104 105 try { 106 //获取被踢出的sessionId的session对象 107 Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId)); 108 if(kickoutSession != null) { 109 //设置会话的kickout属性表示踢出了 110 kickoutSession.setAttribute("kickout", true); 111 } 112 } catch (Exception e) {//ignore exception 113 } 114 } 115 116 //如果被踢出了,直接退出,重定向到踢出后的地址 117 if (session.getAttribute("kickout") != null) { 118 //会话被踢出了 119 try { 120 //退出登录 121 subject.logout(); 122 } catch (Exception e) { //ignore 123 } 124 saveRequest(request); 125 126 Map<String, String> resultMap = new HashMap<String, String>(); 127 //判断是不是Ajax请求 128 if ("XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) { 129 resultMap.put("user_status", "300"); 130 resultMap.put("message", "您已经在其他地方登录,请重新登录!"); 131 //输出json串 132 out(response, resultMap); 133 }else{ 134 //重定向 135 WebUtils.issueRedirect(request, response, kickoutUrl); 136 } 137 return false; 138 } 139 return true; 140 } 141 private void out(ServletResponse hresponse, Map<String, String> resultMap) 142 throws IOException { 143 try { 144 hresponse.setCharacterEncoding("UTF-8"); 145 PrintWriter out = hresponse.getWriter(); 146 out.println(JSON.toJSONString(resultMap)); 147 out.flush(); 148 out.close(); 149 } catch (Exception e) { 150 System.err.println("KickoutSessionFilter.class 输出JSON异常,可以忽略。"); 151 } 152 } 153}

参考网址

https://www.cnblogs.com/caichaoqi/p/8900677.html

https://www.cnblogs.com/chenxbo/p/11002392.html

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

KVM调整cpu和内存

一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid

Nginx + lua +[memcached,redis]

精品案例1、Nginxluamemcached,redis实现网站灰度发布2、分库分表/基于Leaf组件实现的全球唯一ID(非UUID)3、Redis独立数据监控,实现订单超时操作/MQ死信操作SelectPollEpollReactor模型4、分布式任务调试Quartz应用