基于Redis的Shiro Session共享

基于redis的session共享

由 Redis负责 session 数据的存储,而咱们本身实现的 session manager 将负责 session 生命周期的管理。java

通常的系统架构:web

       此架构存在着当redis master故障时, 虽然能够有一到多个备用slave,可是redis不会主动的进行master切换,这时session服务中断。redis

      为了作到redis的高可用,引入了zookper或者haproxy或者keepalived来解决redis master slave的切换问题。即:apache

 

      此体系结构中, redis master出现故障时, 经过haproxy设置redis slave为临时master, redis master从新恢复后,安全

 再切换回去. 此方案中, redis-master 与redis-slave 是双向同步的, 解决目前redis单点问题. 这样保证了session信息服务器

在redis中的高可用。cookie

Shiro有默认的Session管理机制,经过MemorySessionDAO 进行Session的管理和维护。经过查看 org.apache.shiro.web.session.mgt.DefaultWebSessionManager 类继承的DefaultSessionManager源码,咱们能够看到:session

   public DefaultSessionManager() {
        this.deleteInvalidSessions = true;
        this.sessionFactory = new SimpleSessionFactory();
        this.sessionDAO = new MemorySessionDAO();
    }架构

因为咱们采用redis来管理Session,因此须要配置本身的SessionDao,配置以下:ide

<!-- session管理器 -->
	<bean id="redisSessionDAO" class="com.shiro.session.RedisSessionDAO" />
	
	<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
		<property name="sessionDAO" ref="redisSessionDAO"></property>
		 <!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->  
        <property name="sessionIdCookie" ref="sharesession" />  
        <!-- 定时检查失效的session -->  
        <property name="sessionValidationSchedulerEnabled" value="true" /> 
	</bean>
	
	<!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->  
    <bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie">  
        <!-- cookie的name,对应的默认是 JSESSIONID -->  
        <constructor-arg name="name" value="SHAREJSESSIONID" />  
        <!-- jsessionId的path为 / 用于多个系统共享jsessionId -->  
        <property name="path" value="/" />  
        <property name="httpOnly" value="true"/>  
    </bean>

RedisSessionDao

public class RedisSessionDAO extends AbstractSessionDAO {

	private static Logger log = LoggerFactory.getLogger(RedisSessionDAO.class);
	@Autowired
	private RedisCache redisCache;
	
	private String keyPrefix = "shiro_redis_session:";  
	
	@Override
	protected Serializable doCreate(Session session) {
		Serializable sessionId = generateSessionId(session);    
        assignSessionId(session, sessionId);  
        storeSession(sessionId,session);
        log.debug("generate session id:"+sessionId);
        return sessionId;  
        
        
	}
	private void storeSession(Serializable id, Session session) {
		if(id == null){  
			throw new NullPointerException("id argument cannot be null.");
		}  
		
		byte[] key = getByteKey(session.getId());  
		byte[] value = SerializationUtils.serialize(session);
		int seconds = 1800;
		redisCache.addCache(key,value,seconds);
		
	}
	
	@Override
	protected Session doReadSession(Serializable sessionId) {
		if(sessionId == null){  
			throw new NullPointerException("id argument cannot be null.");
        }  
        Session session = (Session)SerializationUtils.deserialize(redisCache.get(getByteKey(sessionId)));
        return session;  
	}
	
	@Override
	public void update(Session session) throws UnknownSessionException {
		this.storeSession(session.getId(),session);
		
	}

	@Override
	public void delete(Session session) {
	 	if(session == null || session.getId() == null){  
	 		log.error("session or session id is null");  
            return;  
        }  
	 	byte[] key = getByteKey(session.getId());  
		redisCache.del(key);
	}

	@Override
	public Collection<Session> getActiveSessions() {
		Set<Session> sessions = new HashSet<Session>();  
        
        Set<byte[]> keys = redisCache.keys(this.keyPrefix + "*");
        if(keys != null && keys.size()>0){  
            for(byte[] key:keys){
                Session s = (Session)SerializationUtils.deserialize(redisCache.get(key));
                sessions.add(s);
            }
        }  
          
        return sessions;  
	}

	/** 
     * 得到byte[]型的key 
     * @return
     */  
    private byte[] getByteKey(Serializable sessionId){  
        String preKey = this.keyPrefix + sessionId;  
        return preKey.getBytes();  
    }  
}

RedisCache是关于Redis 服务器的一些基本操做(增删改查),能够本身找一个关于这方面的操做类。

有一个关键问题是在 updateSession的时候, RedisCache中set的时候,不管是否这个key存在, 都须要set进去;若是redis中已经存在key ,在updateSession时候没有覆盖原来的session值,会发生登陆失败 的问题;而我翻看shiro自带的SessionDao 是采用 MemorySessionDAO来管理Session的 ,查看源码发现 MemorySessionDAO在updateSession的时候采用的以下的方式:

   private ConcurrentMap<Serializable, Session> sessions;

   public void update(Session session) throws UnknownSessionException {
        storeSession(session.getId(), session);
    }

   protected Session storeSession(Serializable id, Session session) {
        if (id == null) {
            throw new NullPointerException("id argument cannot be null.");
        }
        return sessions.putIfAbsent(id, session);
    }

   注意红色部分,MemorySessionDAO 管理session采用线程安全的ConcurrentMap管理,在更新的时候采用的是 putIfAbsent 方式,意思是:若是key不存在的状况下才put session,存在则不会更新 Session,可是能够登陆成功,研究了半天没想明白。这个问题暂时隔着,不过不影响咱们用redis管理Session。

 详细的配置参考

相关文章
相关标签/搜索