Spring boot + shiro + redis 实现session共享(伪单点登录)
2018-06-06 17:34
1301 查看
为实现Web应用的分布式集群部署,要解决登录session的统一。本文利用shiro做权限控制,redis做session存储,结合spring boot快速配置实现session共享。注意本文未解决跨域的问题。不过对于一般的情况能够很好的起到作用,具体已经在不同端口上进行了测试。
我们先从配置说起:
1.引入相关依赖
引入shiro相关的依赖还有Redis的依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro-version}</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.2</version> </dependency>2.要使用Redis,我们要对Redis进行配置
jedis : pool : host : 127.0.0.1 port : 6379 password: maxTotal: 100 maxIdle: 10 maxWaitMillis : 100000对连接池进行配置:
JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(200); config.setMaxIdle(50); config.setMinIdle(8);//设置最小空闲数 config.setMaxWaitMillis(10000); config.setTestOnBorrow(true); config.setTestOnReturn(true); //Idle时进行连接扫描 config.setTestWhileIdle(true); //表示idle object evitor两次扫描之间要sleep的毫秒数 config.setTimeBetweenEvictionRunsMillis(30000); //表示idle object evitor每次扫描的最多的对象数 config.setNumTestsPerEvictionRun(10); //表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义 config.setMinEvictableIdleTimeMillis(60000); jedisPool = new JedisPool(config, "127.0.0.1", 6379, 10000);
3. 在项目中通过ShiroConfiguration对shiro进行配置,我们知道 shiro本身是自带默认的session管理的,但是如果我们要实现session的共享和缓存的话,首先就需要对SessionDao进行重写,如果说要优化session的存取更新频率的话还可以对DefaultWebSessionManager进行重写。(这个具体在后面讲)
同时我们还需要设置一个cookie的名称,用来与shiro默认的JSessionID 进行区分。
由于我们还要设置缓存管理,所以需要 定义JedisCache 使缓存的方式通过Redis来实现。
import com.xxx.util.shiro_redis.CustomSessionManager; import com.xxx.util.shiro_redis.JedisCacheManager; import com.xxx.util.shiro_redis.RedisSessionDao; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.session.mgt.eis.SessionDAO; 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.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; /** * @Author yunfd */ @Configuration public class ShiroConfiguration { //这里就是会话管理的操作类 @Bean public SessionDAO sessionDAO() { return new RedisSessionDao(); } //这里需要设置一个cookie的名称 原因就是会跟原来的session的id值重复的 @Bean public SimpleCookie simpleCookie() { SimpleCookie simpleCookie = new SimpleCookie("REDISSESSION"); return simpleCookie; } @Bean public JedisCacheManager jedisCacheManager() { return new JedisCacheManager(); } @Bean(name = "sessionManager") public DefaultWebSessionManager configWebSessionManager(){ CustomSessionManager manager = new CustomSessionManager(); manager.setSessionIdCookie(simpleCookie()); manager.setSessionDAO(sessionDAO());// 设置SessionDao manager.setDeleteInvalidSessions(true);// 删除过期的session manager.setSessionValidationSchedulerEnabled(true);// 是否定时检查session return manager; } @Bean(name = "securityManager") public SecurityManager securityManager(ShiroRealm shiroRealm, SessionManager sessionManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(shiroRealm); // 注入缓存管理器; securityManager.setCacheManager(jedisCacheManager()); // 会话管理 securityManager.setSessionManager(sessionManager); return securityManager; } @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); shiroFilter.setLoginUrl("/index.html"); shiroFilter.setUnauthorizedUrl("/404.html"); Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/", "anon"); filterMap.put("/**", "authc"); shiroFilter.setFilterChainDefinitionMap(filterMap); return shiroFilter; } @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator(); proxyCreator.setProxyTargetClass(true); return proxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }
上面提到的要重写的部分:
1.JedisCache
import org.apache.commons.lang3.SerializationUtils; import org.apache.shiro.cache.Cache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import java.io.Serializable; import java.util.*; public class JedisCache<K,V> implements Cache<K, V> ,Serializable{ private static final Logger LOGGER = LoggerFactory.getLogger(JedisCache.class); private static final String PREFIX = "SHIRO_SESSION_ID"; private static JedisPool jedisPool; static { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(200); config.setMaxIdle(50); config.setMinIdle(8);//设置最小空闲数 config.setMaxWaitMillis(10000); config.setTestOnBorrow(true); config.setTestOnReturn(true); //Idle时进行连接扫描 config.setTestWhileIdle(true); //表示idle object evitor两次扫描之间要sleep的毫秒数 config.setTimeBetweenEvictionRunsMillis(30000); //表示idle object evitor每次扫描的最多的对象数 config.setNumTestsPerEvictionRun(10); //表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义 config.setMinEvictableIdleTimeMillis(60000); jedisPool = new JedisPool(config, "127.0.0.1", 6379, 10000); } private byte[] getByteKey(K k){ if(k instanceof String){ String key = PREFIX+k; return key.getBytes(); }else { return SerializationUtils.serialize((Serializable) k); } } @Override public int size() { Jedis jedis = jedisPool.getResource(); Long size = jedis.dbSize(); return size.intValue(); } @Override public Set<K> keys() { Jedis jedis = jedisPool.getResource(); Set<byte[]> bytes = jedis.keys( (PREFIX + new String("*")).getBytes()); Set<K> keys = new HashSet<>(); if(bytes!=null){ for (byte[] b: bytes) { keys.add(SerializationUtils.deserialize(b)); } } JedisUtil.closeJedis(jedis); return keys; } @Override public Collection<V> values() { Set<K> keys = this.keys(); Jedis jedis = jedisPool.getResource(); List<V> lists = new ArrayList<>(); for (K k:keys) { byte[] bytes = jedis.get(getByteKey(k)); lists.add(SerializationUtils.deserialize(bytes)); } JedisUtil.closeJedis(jedis); return lists; } @Override public void clear() { jedisPool.getResource().flushDB(); } @Override public V put(K k, V v) { LOGGER.info("key---->"+k+"value---->"+v); Jedis jedis = jedisPool.getResource(); jedis.set(getByteKey(k), SerializationUtils.serialize((Serializable) v)); jedis.expire(getByteKey(k),10000); byte[] bytes = jedis.get(SerializationUtils.serialize(getByteKey(k))); JedisUtil.closeJedis(jedis); if(bytes==null){ return null; } return SerializationUtils.deserialize(bytes); } @Override public V get(K k) { LOGGER.info("get------>key="+k); if(k==null){ return null; } //System.out.println(k); Jedis jedis = jedisPool.getResource(); byte[] bytes = jedis.get(getByteKey(k)); JedisUtil.closeJedis(jedis); if(bytes==null){ return null; } return SerializationUtils.deserialize(bytes); } @Override public V remove(K k) { Jedis jedis = jedisPool.getResource(); byte[] bytes = jedis.get(getByteKey(k)); jedis.del(getByteKey(k)); JedisUtil.closeJedis(jedis); if(bytes==null){ return null; } return SerializationUtils.deserialize(bytes); } }2.JedisCacheManager 用来生成JedisCache
import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class JedisCacheManager implements CacheManager { private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>(); //cache @Override public <K, V> Cache<K, V> getCache(String s) throws CacheException { Cache cache = caches.get(s); if(cache == null){ cache = new JedisCache(); caches.put(s,cache); } return cache; } }3.重头戏,RedisSessionDao 对SessionDao进行重写
import org.apache.commons.lang3.SerializationUtils; import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.eis.CachingSessionDAO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import java.io.Serializable; public class RedisSessionDao extends CachingSessionDAO { private static final String PREFIX = "SHIRO_SESSION_ID"; private static final int EXPRIE = 10000; private static final Logger LOGGER = LoggerFactory.getLogger(RedisSessionDao.class); private static JedisPool jedisPool; static { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(200); config.setMaxIdle(50); config.setMinIdle(8);//设置最小空闲数 config.setMaxWaitMillis(10000); config.setTestOnBorrow(true); config.setTestOnReturn(true); //Idle时进行连接扫描 config.setTestWhileIdle(true); //表示idle object evitor两次扫描之间要sleep的毫秒数 config.setTimeBetweenEvictionRunsMillis(30000); //表示idle object evitor每次扫描的最多的对象数 config.setNumTestsPerEvictionRun(10); //表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义 config.setMinEvictableIdleTimeMillis(60000); jedisPool = new JedisPool(config, "127.0.0.1", 6379, 10000); } @Override protected Serializable doCreate(Session session) { LOGGER.info("--------doCreate-----"); Serializable serializable = this.generateSessionId(session); assignSessionId(session, serializable); Jedis jedis = jedisPool.getResource(); session.setTimeout(EXPRIE*1000); /*jedis.set(getByteKey(serializable),SerializationUtils.serialize((Serializable)session)); jedis.expire(SerializationUtils.serialize(getByteKey(serializable)),EXPRIE);*/ jedis.setex(getByteKey(serializable),EXPRIE,SerializationUtils.serialize((Serializable)session) ); JedisUtil.closeJedis(jedis); return serializable; } @Override protected Session doReadSession(Serializable serializable) { if(serializable ==null){ return null; } LOGGER.info("--------doReadSession-----"); Jedis jedis = jedisPool.getResource(); Session session = null; byte[] s = jedis.get(getByteKey(serializable)); if (s != null) { session = SerializationUtils.deserialize(s); jedis.expire((PREFIX+serializable).getBytes(),EXPRIE); } //判断是否有会话 没有返回NULL if(session==null){ return null; } JedisUtil.closeJedis(jedis); return session; } private byte[] getByteKey(Object k){ if(k instanceof String){ String key = PREFIX+k; return key.getBytes(); }else { return SerializationUtils.serialize((Serializable) k); } } @Override protected void doUpdate(Session session) { LOGGER.info("--------doUpdate-----"); if(session==null){ return ; } //((WebSessionKey)sessionKey) Jedis jedis = jedisPool.getResource(); session.setTimeout(EXPRIE*1000); /*jedis.set(getByteKey(session.getId()),SerializationUtils.serialize((Serializable)session)); jedis.expire(SerializationUtils.serialize((PREFIX+session.getId())),EXPRIE);*/ jedis.setex(getByteKey(session.getId()),EXPRIE,SerializationUtils.serialize((Serializable)session) ); } @Override protected void doDelete(Session session) { LOGGER.info("--------doDelete-----"); Jedis jedis = jedisPool.getResource(); jedis.del(getByteKey(session.getId())); JedisUtil.closeJedis(jedis); } }4.JedisUtil 用来开关连接池
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class JedisUtil { private static JedisPool jedisPool; static { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(200); config.setMaxIdle(50); config.setMinIdle(8);//设置最小空闲数 config.setMaxWaitMillis(10000); config.setTestOnBorrow(true); config.setTestOnReturn(true); //Idle时进行连接扫描 config.setTestWhileIdle(true); //表示idle object evitor两次扫描之间要sleep的毫秒数 config.setTimeBetweenEvictionRunsMillis(30000); //表示idle object evitor每次扫描的最多的对象数 config.setNumTestsPerEvictionRun(10); //表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义 config.setMinEvictableIdleTimeMillis(60000); jedisPool = new JedisPool(config, "127.0.0.1", 6379, 10000); } public static Jedis getJedis(){ return jedisPool.getResource(); } public static void closeJedis(Jedis jedis){ jedis.close(); } }5.如果想要对 doReadSession 和 doUpdate 次数过于频繁进行优化的话,我们通过shiroConfiguration中的SessionManager进行追踪, 发现是DefaultWebSessionManager类,而它又是继承自DefaultSessionManager,其中retrieveSession方法中进行doReadSession,onChange方法进行doUpdate。对这两个方法进行重写,即可进行优化。在这里retrieveSession方法中我先将SessionKey强转成WebSessionKey,从中取出request。第一次readSession我们把Session写入request中,那么之后如果有重复操作的时候 就可以从request中将最先写入的Session给读出来。从而对 doReadSession的次数进行优化。 doUpdate 次数过多的原因基本来源于 timeout的问题,所以可以在onChange 方法中对timeout进行一个限制 ,从而介绍 doUpdate的次数。
import org.apache.shiro.session.Session; import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.SessionKey; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.apache.shiro.web.session.mgt.WebSessionKey; import javax.servlet.ServletRequest; import java.io.Serializable; public class CustomSessionManager extends DefaultWebSessionManager { @Override protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException { Serializable sessionId = getSessionId(sessionKey); ServletRequest request = null; if(sessionKey instanceof WebSessionKey) { request = ((WebSessionKey)sessionKey).getServletRequest(); } if(request!=null && sessionId!=null){ Session session = (Session)request.getAttribute(sessionId.toString()); if(session !=null){ return session; } } Session session = super.retrieveSession(sessionKey); if(request!=null && sessionId != null) { request.setAttribute(sessionId.toString(),session); } return session; } @Override protected void onChange(Session session) { // if(session.getTimeout()>10000*500) // return ; super.onChange(session); } }
以上就是与Session共享相关的内容 。
相关文章推荐
- SpringBoot+redis 实现shiro集群,共享session。亲测可用
- SpringBoot+Redis+Nginx实现负载均衡以及Session缓存共享
- Spring boot + redis 实现session 共享管理
- springboot(七)redis 实现session共享
- spring boot + redis 实现session共享
- spring boot + redis 实现session共享
- spring boot + redis 实现session共享
- spring+redis+shiro 实现session共享
- springboot 集成redis实现session共享
- spring boot + redis 实现session共享
- 使用springboot+redis实现session共享
- spring boot + redis 实现session共享
- 使用springboot+redis实现session共享
- spring boot + redis 实现session共享
- 单点登录实现(spring session+redis完成session共享)
- spring+shiro+redis实现session共享
- 使用springboot+redis实现session共享
- spring,shiro,redis实现session共享
- springboot整合redis,实现session共享
- Spring Boot系列(七)Spring Boot使用Redis实现session共享