1. 程式人生 > >spring boot整合redis實現shiro的分散式session共享

spring boot整合redis實現shiro的分散式session共享

我們知道,shiro是通過SessionManager來管理Session的,而對於Session的操作則是通過SessionDao來實現的,預設的情況下,shiro實現了兩種SessionDao,分別為CachingSessionDAO和MemorySessionDAO,當我們使用EhCache快取時,則是使用的CachingSessionDAO,不適用快取的情況下,就會選擇基於記憶體的SessionDao.所以,如果我們想實現基於Redis的分散式Session共享,重點在於重寫SessionManager中的SessionDao。我們的重寫程式碼如下:

package com.chhliu.springboot.shiro.cache;

import java.io.Serializable;
import java.util.Collection;
import java.util.concurrent.TimeUnit;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
@SuppressWarnings({ "rawtypes", "unchecked" })
public class RedisSessionDao extends AbstractSessionDAO {

	// Session超時時間,單位為毫秒
	private long expireTime = 120000;

	@Autowired
	private RedisTemplate redisTemplate;// Redis操作類,對這個使用不熟悉的,可以參考前面的部落格

	public RedisSessionDao() {
		super();
	}

	public RedisSessionDao(long expireTime, RedisTemplate redisTemplate) {
		super();
		this.expireTime = expireTime;
		this.redisTemplate = redisTemplate;
	}

	@Override // 更新session
	public void update(Session session) throws UnknownSessionException {
		System.out.println("===============update================");
		if (session == null || session.getId() == null) {
			return;
		}
		session.setTimeout(expireTime);
		redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
	}

	@Override // 刪除session
	public void delete(Session session) {
		System.out.println("===============delete================");
		if (null == session) {
			return;
		}
		redisTemplate.opsForValue().getOperations().delete(session.getId());
	}

	@Override// 獲取活躍的session,可以用來統計線上人數,如果要實現這個功能,可以在將session加入redis時指定一個session字首,統計的時候則使用keys("session-prefix*")的方式來模糊查詢redis中所有的session集合
	public Collection<Session> getActiveSessions() {
		System.out.println("==============getActiveSessions=================");
		return redisTemplate.keys("*");
	}

	@Override// 加入session
	protected Serializable doCreate(Session session) {
		System.out.println("===============doCreate================");
		Serializable sessionId = this.generateSessionId(session);
		this.assignSessionId(session, sessionId);

		redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
		return sessionId;
	}

	@Override// 讀取session
	protected Session doReadSession(Serializable sessionId) {
		System.out.println("==============doReadSession=================");
		if (sessionId == null) {
			return null;
		}
		return (Session) redisTemplate.opsForValue().get(sessionId);
	}

	public long getExpireTime() {
		return expireTime;
	}

	public void setExpireTime(long expireTime) {
		this.expireTime = expireTime;
	}

	public RedisTemplate getRedisTemplate() {
		return redisTemplate;
	}

	public void setRedisTemplate(RedisTemplate redisTemplate) {
		this.redisTemplate = redisTemplate;
	}
}

SessionDao實現完了之後,我們就需要將SessionDao加入SessionManager中了,程式碼如下:
@Bean
	public DefaultWebSessionManager configWebSessionManager(){
		DefaultWebSessionManager manager = new DefaultWebSessionManager();
		manager.setCacheManager(cacheManager);// 加入快取管理器
		manager.setSessionDAO(sessionDao);// 設定SessionDao
		manager.setDeleteInvalidSessions(true);// 刪除過期的session
		manager.setGlobalSessionTimeout(sessionDao.getExpireTime());// 設定全域性session超時時間
		manager.setSessionValidationSchedulerEnabled(true);// 是否定時檢查session
		
		return manager;
	}

最後一步就是將SessionManager配置到SecurityManager中了
@Bean
	public SecurityManager securityManager(DefaultWebSessionManager webSessionManager) {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		// 設定realm.
		securityManager.setRealm(myShiroRealm());

		// 注入快取管理器;
		securityManager.setCacheManager(cacheManager);// 這個如果執行多次,也是同樣的一個物件;
		
		// session管理器
		securityManager.setSessionManager(webSessionManager);
		
		//注入記住我管理器;
	    securityManager.setRememberMeManager(rememberMeManager());
		return securityManager;
	}

測試結果如下:
==============doReadSession=================
==============doReadSession=================
==============doReadSession=================
==============doReadSession=================
==============doReadSession=================
==============doReadSession=================
==============doReadSession=================
==============doReadSession=================
==============doReadSession=================
==============doReadSession=================
==============doReadSession=================
==============doReadSession=================
===============update================
==============doReadSession=================
==============doReadSession=================
===============update================
==============doReadSession=================
==============doReadSession=================
==============doReadSession=================
許可權配置-->MyShiroRealm.doGetAuthorizationInfo()
==============doReadSession=================

我們會發現,當一個頁面中存在多個資源的時候,會不停的呼叫doReadSession,update方法來讀取和更新session,目前這個問題還沒有想到比較好的解決方案。