項目中用到了shiro session會話管理機制,今天來總結一下,以下都是在spring boot框架實現。
shiro的session管理機制很完善,也是獨立於j2ee容器且不依賴的,所以我們完全可以使用shiro提供給我們的session會話管理來實現我們的業務邏輯,默認提供的sessionDAO是memorySessionDAO,這里也主要講它的配置和原理。
首先來看下如何在spring boot下配置默認的session會話管理:
ShiroConfig類:
@Configuration
public class ShiroConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.cache.type}")
private String cacheType ;
@Value("${server.session-timeout}")
private int tomcatTimeout;
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//設置realm.
securityManager.setRealm(userRealm());
// 自定義緩存實現 使用redis
if (Constant.CACHE_TYPE_REDIS.equals(cacheType)) {
securityManager.setCacheManager(rediscacheManager());
} else {
securityManager.setCacheManager(ehCacheManager());
}
securityManager.setSessionManager(sessionManager());
return securityManager;
}
@Bean
UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
return userRealm;
}
@Bean
public SessionDAO sessionDAO() {
return new MemorySessionDAO();//使用默認的MemorySessionDAO
}
/**
* shiro session的管理
*/
@Bean
public DefaultWebSessionManager sessionManager() { //配置默認的sesssion管理器
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(tomcatTimeout * 1000);
sessionManager.setSessionDAO(sessionDAO());
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
listeners.add(new BDSessionListener());
sessionManager.setSessionListeners(listeners);
return sessionManager;
}
@Bean
public EhCacheManager ehCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManager(cacheManager());
return em;
}
@Bean("cacheManager2")
CacheManager cacheManager(){
return CacheManager.create();
}
}
DefaultWebSessionManager是我們配置管理器的核心類,里面配置了session的超時時間,sessionDAO和監聽器。
在上面我們向spring boot注入了
@Bean
public SessionDAO sessionDAO() {
return new MemorySessionDAO();//使用默認的MemorySessionDAO
}
MemorySessionDAO是shiro已經實現的Session的CRUD實現類,包括對session的操作,增刪改讀。
下面來看下MemorySessionDAO的源碼實現:
public class MemorySessionDAO extends AbstractSessionDAO {
private static final Logger log = LoggerFactory.getLogger(MemorySessionDAO.class);
private ConcurrentMap<Serializable, Session> sessions = new ConcurrentHashMap();
public MemorySessionDAO() {
}
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
this.storeSession(sessionId, session);
return sessionId;
}
protected Session storeSession(Serializable id, Session session) {
if (id == null) {
throw new NullPointerException("id argument cannot be null.");
} else {
return (Session)this.sessions.putIfAbsent(id, session);
}
}
protected Session doReadSession(Serializable sessionId) {
return (Session)this.sessions.get(sessionId);
}
public void update(Session session) throws UnknownSessionException {
this.storeSession(session.getId(), session);
}
public void delete(Session session) {
if (session == null) {
throw new NullPointerException("session argument cannot be null.");
} else {
Serializable id = session.getId();
if (id != null) {
this.sessions.remove(id);
}
}
}
public Collection<Session> getActiveSessions() {
Collection<Session> values = this.sessions.values();
return (Collection)(CollectionUtils.isEmpty(values) ? Collections.emptySet() : Collections.unmodifiableCollection(values));
}
}
在源碼中我們能看到MemorySessionDAO繼承了AbstractSessionDAO類,這是一個抽象類,里面實現了一部分方法,而AbstractSessionDAO又實現了SessionDAO接口,這才是頂級接口,里面提供了5個方法:
public interface SessionDAO {
Serializable create(Session var1);
Session readSession(Serializable var1) throws UnknownSessionException;
void update(Session var1) throws UnknownSessionException;
void delete(Session var1);
Collection<Session> getActiveSessions();
}
這5個方法也是memorySessionDAO實現的5個方法。
可以看出配置非常簡單,我們幾乎不需要做任何事情,下面來看看怎么來使用吧,
以一個SessionService為例:
@Service
public interface SessionService {
List<UserOnline> list(); //得到系統在線用戶
boolean forceLogout(String sessionId); //強制登出
}
里面有兩個方法,分別是查詢系統當前在線的用戶,以及強制登出某個用戶。
實現如下:
@Service
public class SessionServiceImpl implements SessionService {
private final SessionDAO sessionDAO;
@Autowired
public SessionServiceImpl(SessionDAO sessionDAO) {
this.sessionDAO = sessionDAO;
}
@Override
public List<UserOnline> list() {
List<UserOnline> list = new ArrayList<>();
Collection<Session> sessions = sessionDAO.getActiveSessions();
for (Session session : sessions) {
UserOnline userOnline = new UserOnline();
if (session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {
continue;
} else {
SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) session
.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
UserDO userDO = (UserDO) principalCollection.getPrimaryPrincipal();
userOnline.setUsername(userDO.getUsername());
}
userOnline.setId((String) session.getId());
userOnline.setHost(session.getHost());
userOnline.setStartTimestamp(session.getStartTimestamp());
userOnline.setLastAccessTime(session.getLastAccessTime());
userOnline.setTimeout(session.getTimeout());
list.add(userOnline);
}
return list;
}
@Override
public boolean forceLogout(String sessionId) {
Session session = sessionDAO.readSession(sessionId);
session.setTimeout(0);
return true;
}
}
在代碼中,我們注入了shiro的SessionDAO,
Collection<Session> sessions = sessionDAO.getActiveSessions(); 可以得到當前在線的session集合。
每一個session都封裝了用戶的各種信息,
userOnline.setId((String) session.getId()); //得到session的ID
userOnline.setHost(session.getHost()); //得到用戶的主機
userOnline.setStartTimestamp(session.getStartTimestamp()); //得到用戶的登錄時間
userOnline.setLastAccessTime(session.getLastAccessTime());//得到用戶最后登錄時間
userOnline.setTimeout(session.getTimeout());/得到過期時間
如果我們想在強制讓某用戶登出系統,只需要session.setTimeout(0)即可。
是不是覺得非常簡便,如果系統需求不高,我們完全可以使用shiro為我們實現的這個sessionDAO,
而如果系統有定制需求,我們也可以實現自定義的sessionDAO,比如我們可以將session的CRUD通過redis來實現,同樣的繼承AbstractSessionDAO類,重寫它的方法即可,將session的各種存取修改以redis為載體,基本的邏輯就是這樣,如果有問題歡迎在評論中留言。
