spring boot+redis+shrio+会话
来源:互联网 发布:mac后台怎么关闭 编辑:程序博客网 时间:2024/06/06 19:00
之前已经在几个项目中使用过shiro,可以说对shiro已经有了一定的了解,但是最近在处理一个shiro项目的问题时却遇到了空前的挑战。
问题描述:用户登录后用户用着用着就突然自动退出了,而且没有任何规律,有的测试同事反馈一天也不会出现1次,但是有的时候却发现经常自动退出,完全没有看出来任何规律。
问题分析处理步骤:
1、检查httpwatch请求记录; 发现有的时候用户的sessionid突然就变了;2、怀疑是nginx配置问题(由于新参与这个项目,环境啥的都不了解) 经和运维沟通了解到当前系统是单点、而且nginx已经配置了会话保持,也就是说不存在乱跳的问题。
3、检查了几遍代码理论上程序不会造成这个问题,苦逼啊。然后就在系统中添加了session监控日志;
在程序入口中添加注解:@ServletComponentScan
@WebListener@Logpublic class SessionListener implements HttpSessionListener { public void sessionCreated(HttpSessionEvent arg0) {// TODO Auto-generated method stub log.info("createSession:"+arg0.getSession().getId()); } public void sessionDestroyed(HttpSessionEvent arg0) { log.info("destoryedSession:"+arg0.getSession().getId()); }}
经检测发现有的会话几分钟就过期了,之前session使用的系统默认的超时时间,虽然感觉不应该是session会话时间配置的问题,但是还是抱着试试看的态度改了下;最后事实证明的确没卵关系。
4、奇怪的问题又发生了,有的时候用户的sessionid虽然没有变,但是session里边的用户信息却丢失了,我靠瞬间凌乱了。后来同事提醒是不是会话污染导致的,然后又是一顿折腾。
在shiroconfig类中配置会话(这里只列出部分代码,后边会给出完整代码):
@Bean(name = "sessionIdCookie") public SimpleCookie getSessionIdCookie() { SimpleCookie cookie = new SimpleCookie("mysid"); cookie.setHttpOnly(true); cookie.setMaxAge(-1); return cookie; }@Bean(name = "sessionManager") public SessionManager getSessionManage() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setGlobalSessionTimeout(3600000); sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler()); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionIdCookieEnabled(true); sessionManager.setSessionIdCookie(getSessionIdCookie()); // EnterpriseCacheSessionDAO cacheSessionDAO = new RedisSessionDAO(); sessionManager.setCacheManager(redisCacheManager()); sessionManager.setSessionDAO(redisSessionDAO()); // -----可以添加session 创建、删除的监听器 return sessionManager; }
经测试然而并没有什么卵用,坑爹啊。
5、和原来集成过shiro的项目比对,发现原来的使用的是war包部署,但是这个项目使用的是jar包部署,因此怀疑是不是spring boot内嵌的tomcat容器有啥问题,当跟领导表示要试试修改下容器的时候直接被否了,因此就没有进行测试【个人感觉这种可能性也是存在的,如果以后有也人遇到这种问题可以试试这个方案】。
6、绞尽乳汁的思考问题的可能性,有那么些时刻感觉自己真是黔驴技穷了,这个问题会不会自己处理不了给领导留下不好的印象【刚入职,很尴尬啊】。到此这个问题我已经出来了将近一周了。
6、仔细思考问题的可能原因,既然会话仍在session里边会有问题,那放在别的地方呢?然后就想到了放在redis中试试,说干就干,以下为完整代码。
spring boot+redis+shrio+会话
ShiroConfiguration2
package com.liao.configuration;import com.liao.shiro.*;import com.liao.util.PasswordHelper;import lombok.Data;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler;import org.apache.shiro.session.mgt.SessionManager;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.CookieRememberMeManager;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.apache.shiro.web.servlet.SimpleCookie;import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.core.RedisTemplate;import java.util.LinkedHashMap;import java.util.Map;@Data@Configurationpublic class ShiroConfiguration2 { @Autowired private RedisTemplate redisTemplate; private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class); public RedisSessionDAO redisSessionDAO(){ RedisSessionDAO sessionDAO=new RedisSessionDAO(); sessionDAO.redisTemplate=redisTemplate; return sessionDAO; } @Bean public RedisCacheManager redisCacheManager() { RedisCacheManager manager=new RedisCacheManager(); manager.setRedisTemplate(redisTemplate); return manager; } @Bean(name = "myShiroRealm") public MyShiroRealm myShiroRealm(RedisCacheManager redisCacheManager) { MyShiroRealm realm = new MyShiroRealm(); realm.setCacheManager(redisCacheManager); return realm; } /** *在此重点说明这个方法,如果不设置为静态方法会导致bean对象无法注入进来, *我被这个问题坑的想死的心都有了,晚上感到4点多 *我是在这篇博客里找到答案的: *http://blog.csdn.net/wuxuyang_7788/article/details/70141812 */ @Bean(name = "lifecycleBeanPostProcessor") public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); daap.setProxyTargetClass(true); return daap; } @Bean(name = "securityManager") public DefaultWebSecurityManager securityManager(MyShiroRealm myShiroRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm); securityManager.setSessionManager(getSessionManage()); securityManager.setCacheManager(redisCacheManager()); //配置记住我 securityManager.setRememberMeManager(getCookieRememberMeManager()); return securityManager; } @Bean(name = "sessionManager") public SessionManager getSessionManage() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setGlobalSessionTimeout(3600000); sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler()); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionIdCookieEnabled(true); sessionManager.setSessionIdCookie(getSessionIdCookie()); // EnterpriseCacheSessionDAO cacheSessionDAO = new RedisSessionDAO(); sessionManager.setCacheManager(redisCacheManager()); sessionManager.setSessionDAO(redisSessionDAO()); // -----可以添加session 创建、删除的监听器 return sessionManager; } @Bean(name = "sessionValidationScheduler") public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler() { ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler(); scheduler.setInterval(900000); return scheduler; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); aasa.setSecurityManager(securityManager); return aasa; } /** * 加载shiroFilter权限控制规则 * */ private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean) { /////////////////////// 下面这些规则配置最好配置到配置文件中 /////////////////////// Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter// // anon:它对应的过滤器里面是空的,什么都没做// logger.info("##################从数据库读取权限规则,加载到shiroFilter中##################");// filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");// 这里为了测试,固定写死的值,也可以从数据库或其他配置中读取 filterChainDefinitionMap.put("/user/login", "anon"); filterChainDefinitionMap.put("/user/logout", "anon"); filterChainDefinitionMap.put("/user/authcode", "anon"); filterChainDefinitionMap.put("/user/admin", "anon"); //filterChainDefinitionMap.put("/user/**", "authc");// 这里为了测试,只限制/user,实际开发中请修改为具体拦截的请求规则 //这个配置可以理解为拦截,authFilter主要用于拦截未登录请求,主动返回规范JSON;user标识如果启用了记住我,则自动登录 filterChainDefinitionMap.put("/**", "authFilter,user");//anon 可以理解为不拦截 //filterChainDefinitionMap.put("/**", "anon");//anon 可以理解为不拦截 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); //shiroFilterFactoryBean.getFilters().put("jCaptchaValidate", getJCaptchaValidateFilter()); } @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new MShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/pyramid/user/nologin"); // 登录成功后要跳转的连接 shiroFilterFactoryBean.setSuccessUrl("/user"); shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.getFilters().put("authFilter",new AuthcFilter()); loadShiroFilterChain(shiroFilterFactoryBean); return shiroFilterFactoryBean; } /** * @return */ @Bean(name = "credentialsMatcher") public HashedCredentialsMatcher getHashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName(PasswordHelper.algorithmName); hashedCredentialsMatcher.setHashIterations(PasswordHelper.hashIterations); hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); return hashedCredentialsMatcher; } @Bean(name = "rememberMeCookie") public SimpleCookie getRememberMeCookie() { SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); simpleCookie.setHttpOnly(true); simpleCookie.setMaxAge(2592000);//30天 return simpleCookie; } @Bean(name = "sessionIdCookie") public SimpleCookie getSessionIdCookie() { SimpleCookie cookie = new SimpleCookie("mysid"); cookie.setHttpOnly(true); cookie.setMaxAge(-1); return cookie; } @Bean(name = "rememberMeManager") public CookieRememberMeManager getCookieRememberMeManager() { CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCipherKey( org.apache.shiro.codec.Base64.decode("4AvVhmFLUs0KTA3Kprsdag==")); cookieRememberMeManager.setCookie(getRememberMeCookie()); return cookieRememberMeManager; }}
MyShiroRealm
package com.liao.shiro;import org.apache.commons.lang3.builder.ReflectionToStringBuilder;import org.apache.commons.lang3.builder.ToStringStyle;import org.apache.shiro.authc.*;import org.apache.shiro.authc.credential.CredentialsMatcher;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;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.apache.shiro.util.ByteSource;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import java.util.HashSet;import java.util.stream.Collectors;@Componentpublic class MyShiroRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class); @Autowired private HashedCredentialsMatcher hashedCredentialsMatcher; /** * 权限认证,为当前登录的Subject授予角色和权限 * * @see {}经测试:本例中该方法的调用时机为需授权资源被访问时 * @see {}经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache * @see {}经测试:如果连续访问同一个URL(比如刷新),该方法不会被重复调用,Shiro有一个时间间隔(也就是cache时间,在ehcache-shiro.xml中配置),超过这个时间间隔再刷新页面,该方法会被执行 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("##################执行Shiro权限认证##################"); //在此加入自己逻辑即可 } /** * 登录认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authenticationToken) throws AuthenticationException { //UsernamePasswordToken对象用来存放提交的登录信息 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //不做过多说明 加入验证逻辑即可 } @PostConstruct public void initCredentialsMatcher() { setCredentialsMatcher(hashedCredentialsMatcher); } @Override public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) { super.setCredentialsMatcher(credentialsMatcher); } @Override public void clearCachedAuthorizationInfo(PrincipalCollection principals) { super.clearCachedAuthorizationInfo(principals); } @Override public void clearCachedAuthenticationInfo(PrincipalCollection principals) { super.clearCachedAuthenticationInfo(principals); } @Override public void clearCache(PrincipalCollection principals) { super.clearCache(principals); } public void clearAllCachedAuthorizationInfo() { getAuthorizationCache().clear(); } public void clearAllCachedAuthenticationInfo() { getAuthenticationCache().clear(); } public void clearAllCache() { clearAllCachedAuthenticationInfo(); clearAllCachedAuthorizationInfo(); }}
ShiroCache
package com.liao.shiro;import org.apache.shiro.cache.Cache;import org.apache.shiro.cache.CacheException;import org.springframework.data.redis.core.RedisTemplate;import java.util.ArrayList;import java.util.Collection;import java.util.List;import java.util.Set;import java.util.concurrent.TimeUnit;/** * @author liao * @Time 2017/8/8 */@SuppressWarnings("unchecked")public class ShiroCache <K, V> implements Cache<K, V> { private static final String REDIS_SHIRO_CACHE = "shiro-cache:"; private String cacheKey; private RedisTemplate<K, V> redisTemplate; private long globExpire = 60; @SuppressWarnings("rawtypes") public ShiroCache(String name, RedisTemplate client) { this.cacheKey = REDIS_SHIRO_CACHE + name + ":"; this.redisTemplate = client; } @Override public V get(K key) throws CacheException {// System.out.println("|"+getCacheKey(key)+"|");// if(redisTemplate.boundValueOps(getCacheKey(key))==null||"".equals(redisTemplate.boundValueOps(getCacheKey(key)))){// return null;// } // System.out.println("|"+redisTemplate.boundValueOps(getCacheKey(key))+"|"); redisTemplate.boundValueOps(getCacheKey(key)).expire(globExpire, TimeUnit.MINUTES); return redisTemplate.boundValueOps(getCacheKey(key)).get(); } @Override public V put(K key, V value) throws CacheException { V old = get(key); redisTemplate.boundValueOps(getCacheKey(key)).set(value); return old; } @Override public V remove(K key) throws CacheException { V old = get(key); redisTemplate.delete(getCacheKey(key)); return old; } @Override public void clear() throws CacheException { redisTemplate.delete(keys()); } @Override public int size() { return keys().size(); } @Override public Set<K> keys() { return redisTemplate.keys(getCacheKey("*")); } @Override public Collection<V> values() { Set<K> set = keys(); List<V> list = new ArrayList<>(); for (K s : set) { list.add(get(s)); } return list; } private K getCacheKey(Object k) { return (K) (this.cacheKey + k); }}
RedisCacheManager
package com.liao.shiro;import org.apache.shiro.cache.Cache;import org.apache.shiro.cache.CacheException;import org.apache.shiro.cache.CacheManager;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component;import javax.annotation.Resource;/** * @author liao * @Time 2017/8/8 */@Componentpublic class RedisCacheManager implements CacheManager { @Resource private RedisTemplate<String, Object> redisTemplate; @Override public <K, V> Cache<K, V> getCache(String name) throws CacheException { return new ShiroCache<K, V>(name, redisTemplate); } public RedisTemplate<String, ?> getRedisTemplate() { return redisTemplate; } public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; }}
RedisSessionDAO
package com.liao.shiro;import org.liao.session.Session;import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component;import javax.annotation.Resource;import java.io.Serializable;import java.util.concurrent.TimeUnit;/** * @author liao * @Time 2017/8/8 */@Componentpublic class RedisSessionDAO extends EnterpriseCacheSessionDAO { private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class); // session 在redis过期时间是60分钟60*60 private static int expireTime = 3600; private static String prefix = "shiro-session:"; @Resource public RedisTemplate<String, Object> redisTemplate; // 创建session,保存到数据库 @Override protected Serializable doCreate(Session session) { Serializable sessionId = super.doCreate(session); logger.debug("创建session:{}", session.getId()); redisTemplate.opsForValue().set(prefix + sessionId.toString(), session); return sessionId; } // 获取session @Override protected Session doReadSession(Serializable sessionId) { logger.debug("获取session:{}", sessionId); // 先从缓存中获取session,如果没有再去数据库中获取 Session session = super.doReadSession(sessionId); if (session == null) { session = (Session) redisTemplate.opsForValue().get(prefix + sessionId.toString()); } return session; } // 更新session的最后一次访问时间 @Override protected void doUpdate(Session session) { super.doUpdate(session); logger.debug("获取session:{}", session.getId()); String key = prefix + session.getId().toString(); if (!redisTemplate.hasKey(key)) { redisTemplate.opsForValue().set(key, session); } redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); } // 删除session @Override protected void doDelete(Session session) { logger.debug("删除session:{}", session.getId()); super.doDelete(session); redisTemplate.delete(prefix + session.getId().toString()); }}
ShiroUser
package com.lenovo.pyramid.shiro;import java.io.Serializable;import java.util.Objects;/** * 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息. */ public class ShiroUser implements Serializable { private static final long serialVersionUID = -1373760761780840081L; public String loginName; public String name; public Long userId; public ShiroUser(String loginName, String name,Long userId) { this.loginName = loginName; this.name = name; this.userId = userId; } public String getName() { return name; } /** * 本函数输出将作为默认的<shiro:principal/>输出. */ @Override public String toString() { return loginName; } /** * 重载hashCode,只计算loginName; */ @Override public int hashCode() { return Objects.hashCode(loginName); } /** * 重载equals,只计算loginName; */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ShiroUser other = (ShiroUser) obj; if (loginName == null) { if (other.loginName != null) { return false; } } else if (!loginName.equals(other.loginName)) { return false; } return true; } }
经过一顿折腾终于完事了,测试了一天问题没有再复现【当然还需要再测试几天】。
重要事情说三遍ShiroConfiguration2 中getLifecycleBeanPostProcessor 方法一定要设置为静态方法
引用领导的一句话做下总结:
没有解决不了的问题,在你尝试走完所有道路之前,问题肯定是能处理的。
- spring boot+redis+shrio+会话
- Redis-shrio集成
- spring boot使用redis
- spring boot session redis
- spring boot使用redis
- spring-boot redis 配置
- Spring boot + redis 项目
- Spring Boot Redis 集成
- redis-10-spring-boot
- spring boot+redis
- [spring-boot] 使用redis
- spring boot整合redis
- Spring Boot集成Redis
- spring-boot 集成 redis
- spring boot之redis
- spring boot 整合 redis
- spring boot 集成redis
- Spring boot Redis 乱码
- Android自定义水平进度条
- timer实现倒计时
- 京东内推:最大连续乘积子数组
- 【算法】树形数组
- window10 GPU版tensorflow安装(一)
- spring boot+redis+shrio+会话
- 概率论和数理统计
- 如何在4.X版本实现Switch在5.X的效果
- Andrdoid自定义View之canvas.clipPath(path);
- storm1.x支持主节点nimbus高可用 多master集群部署
- 安卓实训笔记第三天
- 图片的二次采样
- 数学的坑,一点点来填
- RAM