概述
本文基于网上整理,为了实现将Shiro框架的session存储到redis里面,进而实现基于Niginx负载均衡,多站点部署;
maven下shiro依赖
<!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.0</version> </dependency> <!-- shiro+redis缓存插件 --> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>2.4.2.1-RELEASE</version> </dependency>
主要代码编写
1、ShiroConfig配置
package com.chaoqi.springboot_shiro_redis.config; import com.chaoqi.springboot_shiro_redis.secutity.KickoutSessionControlFilter; import com.chaoqi.springboot_shiro_redis.secutity.MyShiroRealm; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisManager; import org.crazycake.shiro.RedisSessionDAO; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig {
@Value("${redis.host}")
private String redisHost;
@Value("${redis.port}")
private String redisPort;
@Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 没有登陆的用户只能访问登陆页面 shiroFilterFactoryBean.setLoginUrl("/auth/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/auth/index"); // 未授权界面; ----这个配置了没卵用,具体原因想深入了解的可以自行百度 //shiroFilterFactoryBean.setUnauthorizedUrl("/auth/403"); //自定义拦截器 Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>(); //限制同一帐号同时在线的个数。 filtersMap.put("kickout", kickoutSessionControlFilter()); shiroFilterFactoryBean.setFilters(filtersMap); // 权限控制map. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/img/**", "anon"); filterChainDefinitionMap.put("/auth/login", "anon"); filterChainDefinitionMap.put("/auth/logout", "logout"); filterChainDefinitionMap.put("/auth/kickout", "anon"); filterChainDefinitionMap.put("/**", "authc,kickout"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(myShiroRealm()); // 自定义缓存实现 使用redis securityManager.setCacheManager(cacheManager()); // 自定义session管理 使用redis securityManager.setSessionManager(sessionManager()); return securityManager; } /** * 身份认证realm; (这个需要自己写,账号密码校验;权限等) * * @return */ @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); return myShiroRealm; } /** * cacheManager 缓存 redis实现 * 使用的是shiro-redis开源插件 * * @return */ public RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; } /** * 配置shiro redisManager * 使用的是shiro-redis开源插件 * * @return */ public RedisManager redisManager() { RedisManager redisManager = new RedisManager(); redisManager.setHost(redisHost); redisManager.setPort(redisPort); redisManager.setExpire(1800);// 配置缓存过期时间 redisManager.setTimeout(0); // redisManager.setPassword(password); return redisManager; } /** * Session Manager * 使用的是shiro-redis开源插件 */ @Bean public DefaultWebSessionManager sessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionDAO(redisSessionDAO()); return sessionManager; } /** * RedisSessionDAO shiro sessionDao层的实现 通过redis * 使用的是shiro-redis开源插件 */ @Bean public RedisSessionDAO redisSessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; } /** * 限制同一账号登录同时登录人数控制 * * @return */ @Bean public KickoutSessionControlFilter kickoutSessionControlFilter() { KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter(); kickoutSessionControlFilter.setCacheManager(cacheManager()); kickoutSessionControlFilter.setSessionManager(sessionManager()); kickoutSessionControlFilter.setKickoutAfter(false); kickoutSessionControlFilter.setMaxSession(1); kickoutSessionControlFilter.setKickoutUrl("/auth/kickout"); return kickoutSessionControlFilter; } /*** * 授权所用配置 * * @return */ @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } /*** * 使授权注解起作用不如不想配置可以在pom文件中加入 * <dependency> *<groupId>org.springframework.boot</groupId> *<artifactId>spring-boot-starter-aop</artifactId> *</dependency> * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * Shiro生命周期处理器 * */ @Bean public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } }
2、自定义Realm
package com.chaoqi.springboot_shiro_redis.secutity; import com.chaoqi.springboot_shiro_redis.service.SysRoleService; import com.chaoqi.springboot_shiro_redis.service.UserService; import com.chaoqi.springboot_shiro_redis.dao.domain.SysUser; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.util.*; public class MyShiroRealm extends AuthorizingRealm { private static org.slf4j.Logger logger = LoggerFactory.getLogger(MyShiroRealm.class); //如果项目中用到了事物,@Autowired注解会使事物失效,可以自己用get方法获取值 @Autowired private SysRoleService roleService; @Autowired private UserService userService; /** * 认证信息.(身份验证) : Authentication 是用来验证用户身份 * */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { logger.info("---------------- 执行 Shiro 凭证认证 ----------------------"); UsernamePasswordToken token = (UsernamePasswordToken) authcToken; String name = token.getUsername(); String password = String.valueOf(token.getPassword()); SysUser user = new SysUser(); user.setUserName(name); user.setPassWord(password); // 从数据库获取对应用户名密码的用户 SysUser userList = userService.getUser(user); if (userList != null) { // 用户为禁用状态 if (userList.getUserEnable() != 1) { throw new DisabledAccountException(); } logger.info("---------------- Shiro 凭证认证成功 ----------------------"); SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( userList, //用户 userList.getPassWord(), //密码 getName() //realm name ); return authenticationInfo; } throw new UnknownAccountException(); } /** * 授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info("---------------- 执行 Shiro 权限获取 ---------------------"); Object principal = principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); if (principal instanceof SysUser) { SysUser userLogin = (SysUser) principal; Set<String> roles = roleService.findRoleNameByUserId(userLogin.getId()); authorizationInfo.addRoles(roles); Set<String> permissions = userService.findPermissionsByUserId(userLogin.getId()); authorizationInfo.addStringPermissions(permissions); } logger.info("---- 获取到以下权限 ----"); logger.info(authorizationInfo.getStringPermissions().toString()); logger.info("---------------- Shiro 权限获取成功 ----------------------"); return authorizationInfo; } }
3、限制并发人数登陆
package com.chaoqi.springboot_shiro_redis.secutity; import com.alibaba.fastjson.JSON; import com.chaoqi.springboot_shiro_redis.dao.domain.SysUser; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.DefaultSessionKey; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.AccessControlFilter; import org.apache.shiro.web.util.WebUtils; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.PrintWriter; import java.io.Serializable; import java.util.Deque; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; public class KickoutSessionControlFilter extends AccessControlFilter { private String kickoutUrl; //踢出后到的地址 private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户 private int maxSession = 1; //同一个帐号最大会话数 默认1 private SessionManager sessionManager; private Cache<String, Deque<Serializable>> cache; public void setKickoutUrl(String kickoutUrl) { this.kickoutUrl = kickoutUrl; } public void setKickoutAfter(boolean kickoutAfter) { this.kickoutAfter = kickoutAfter; } public void setMaxSession(int maxSession) { this.maxSession = maxSession; } public void setSessionManager(SessionManager sessionManager) { this.sessionManager = sessionManager; } //设置Cache的key的前缀 public void setCacheManager(CacheManager cacheManager) { this.cache = cacheManager.getCache("shiro_redis_cache"); } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return false; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { Subject subject = getSubject(request, response); if(!subject.isAuthenticated() && !subject.isRemembered()) { //如果没有登录,直接进行之后的流程 return true; } Session session = subject.getSession(); SysUser user = (SysUser) subject.getPrincipal(); String username = user.getUserName(); Serializable sessionId = session.getId(); //读取缓存 没有就存入 Deque<Serializable> deque = cache.get(username); //如果此用户没有session队列,也就是还没有登录过,缓存中没有 //就new一个空队列,不然deque对象为空,会报空指针 if(deque==null){ deque = new LinkedList<Serializable>(); } //如果队列里没有此sessionId,且用户没有被踢出;放入队列 if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) { //将sessionId存入队列 deque.push(sessionId); //将用户的sessionId队列缓存 cache.put(username, deque); } //如果队列里的sessionId数超出最大会话数,开始踢人 while(deque.size() > maxSession) { Serializable kickoutSessionId = null; if(kickoutAfter) { //如果踢出后者 kickoutSessionId = deque.removeFirst(); //踢出后再更新下缓存队列 cache.put(username, deque); } else { //否则踢出前者 kickoutSessionId = deque.removeLast(); //踢出后再更新下缓存队列 cache.put(username, deque); } try { //获取被踢出的sessionId的session对象 Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId)); if(kickoutSession != null) { //设置会话的kickout属性表示踢出了 kickoutSession.setAttribute("kickout", true); } } catch (Exception e) {//ignore exception } } //如果被踢出了,直接退出,重定向到踢出后的地址 if (session.getAttribute("kickout") != null) { //会话被踢出了 try { //退出登录 subject.logout(); } catch (Exception e) { //ignore } saveRequest(request); Map<String, String> resultMap = new HashMap<String, String>(); //判断是不是Ajax请求 if ("XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) { resultMap.put("user_status", "300"); resultMap.put("message", "您已经在其他地方登录,请重新登录!"); //输出json串 out(response, resultMap); }else{ //重定向 WebUtils.issueRedirect(request, response, kickoutUrl); } return false; } return true; } private void out(ServletResponse hresponse, Map<String, String> resultMap) throws IOException { try { hresponse.setCharacterEncoding("UTF-8"); PrintWriter out = hresponse.getWriter(); out.println(JSON.toJSONString(resultMap)); out.flush(); out.close(); } catch (Exception e) { System.err.println("KickoutSessionFilter.class 输出JSON异常,可以忽略。"); } } }
参考网址
https://www.cnblogs.com/caichaoqi/p/8900677.html
https://www.cnblogs.com/chenxbo/p/11002392.html
原文地址:https://www.cnblogs.com/xibei666/p/12363046.html
时间: 2024-11-07 22:30:26