前言
Shiro 提供了完整的會話管理功能,可以在不依賴底層容器,不僅可以在 WEB 環境下使用 Session,還可以在 JavaSE 環境下使用,且提供了會話管理,會話事件監聽,會話持久化,過期支持。
會話操作
所謂會話,即用戶訪問應用時保持的連接關係,在多次交互中應用能夠識別出當前訪問的用戶是誰,且可以在多次交互中保存一些數據。如訪問一些網站時登錄成功後,網站可以記住用戶,且在退出之前都可以識別當前用戶是誰。
獲取 Session 方法:
Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession();
Session 常用方法:
session.getId(); // 會話 ID, 唯一標識 session.getHost(); // 獲取當前 Subject 的主機地址 session.getTimeout(); // 獲取 Session 超時時間 session.setTimeout(long time); // 設置 Session 超時時間 session.getStartTimestamp(); // 會話創建時間 session.getLastAccessTime(); // 最後活躍時間 session.touch(); // 更新會話 session.stop(); // 銷燬會話 // 當然也支持 getAttribute() 和 setAttribute() 方法
會話管理器
會話管理器管理應用中所有 Subject 的會話的創建、維護、刪除、失效、驗證等工作。
Shiro提供了三個默認實現:
DefaultSessionManager:DefaultSecurityManager 使用的默認實現,用於JavaSE環境;
ServletContainerSessionManager:DefaultWebSecurityManager使用的默認實現,用於Web環境,其直接使用Servlet容器的會話;
DefaultWebSessionManager:用於Web環境的實現,可以替代ServletContainerSessionManager,自己維護着會話,直接廢棄了Servlet容器的會話管理。
會話監聽器
會話監聽器用於監聽會話創建、過期及停止事件:
package im.zhaojun.session.listener; import org.apache.log4j.Logger; import org.apache.shiro.session.Session; import org.apache.shiro.session.SessionListener; import org.springframework.stereotype.Component; /** * Shiro 會話監聽器 */ @Component public class MySessionListener implements SessionListener { private static final Logger logger = Logger.getLogger(MySessionListener.class); @Override public void onStart(Session session) { logger.info("create session : " + session.getId()); } @Override public void onStop(Session session) { logger.info("session stop : " + session.getId()); } @Override public void onExpiration(Session session) { logger.info("session expiration : " + session.getId()); } }
當然,如果你只想監聽某個事件,可以繼承自 SessionListenerAdapter
:
package im.zhaojun.session.listener; import org.apache.log4j.Logger; import org.apache.shiro.session.Session; import org.apache.shiro.session.SessionListener; import org.apache.shiro.session.SessionListenerAdapter; import org.springframework.stereotype.Component; /** * Shiro 會話監聽器 */ @Component public class MySessionListener2 extends SessionListenerAdapter { private static final Logger logger = Logger.getLogger(MySessionListener2.class); @Override public void onStart(Session session) { logger.info("create session : " + session.getId()); } }
然後將會話監聽器配置到 sessionManager
中,在將 sessionManager
配置到 securityManager
:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm"/> <property name="cacheManager" ref="redisCacheManager"/> <property name="sessionManager" ref="sessionManager"/> </bean> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="sessionListeners" ref="mySessionListener"/> </bean>
會話持久化/存儲
Shiro 提供 SessionDAO 用於會話的 CRUD,我們可以用它來從 Redis 中增刪改查 Session 信息,只需要繼承自 SessionDAO
:
package im.zhaojun.session; import im.zhaojun.util.JedisUtil; import org.apache.log4j.Logger; import org.apache.shiro.session.Session; import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.eis.AbstractSessionDAO; import org.springframework.stereotype.Component; import org.springframework.util.SerializationUtils; import javax.annotation.Resource; import java.io.Serializable; import java.util.Collection; import java.util.HashSet; @Component public class RedisSessionDAO extends AbstractSessionDAO { private static final Logger logger = Logger.getLogger(RedisSessionDAO.class); @Resource private JedisUtil jedisUtil; private final String SHIRO_SESSION_PREFIX = "shiro-session:"; @Override protected Serializable doCreate(Session session) { Serializable sessionId = generateSessionId(session); assignSessionId(session, sessionId); saveSession(session); logger.info("sessionDAO doCreate : " + session.getId()); return sessionId; } @Override protected Session doReadSession(Serializable sessionId) { if (sessionId == null) { return null; } byte[] key = getKeyBytes(sessionId.toString()); byte[] value = jedisUtil.get(key); return (Session) SerializationUtils.deserialize(value); } @Override public void update(Session session) throws UnknownSessionException { saveSession(session); } @Override public void delete(Session session) { logger.info("session delete : " + session.getId()); if (session != null && session.getId() != null) { byte[] key = getKeyBytes(session.getId().toString()); jedisUtil.del(key); } } @Override public Collection<Session> getActiveSessions() { Collection<byte[]> keys = jedisUtil.getKeysByPrefix(SHIRO_SESSION_PREFIX); Collection<Session> sessions = new HashSet<>(); if (sessions.isEmpty()) { return sessions; } for (byte[] key : keys) { Session session = (Session) SerializationUtils.deserialize(jedisUtil.get(key)); sessions.add(session); } return sessions; } private byte[] getKeyBytes(String key) { return (SHIRO_SESSION_PREFIX + key).getBytes(); } private void saveSession(Session session) { if (session != null && session.getId() != null) { byte[] key = getKeyBytes(session.getId().toString()); byte[] value = SerializationUtils.serialize(session); jedisUtil.set(key, value); jedisUtil.expire(key, 600); } } }
這裏和上一章,授權數據的緩存很相像,那裏是對授權數據的增刪改查,這裏是對 Session 數據的增刪改查。
然後將其配置到 sessionManager
中:
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="sessionListeners" ref="mySessionListener"/> <property name="sessionDAO" ref="redisSessionDAO"/> </bean>
小結
我們可以使用 Shiro 提供的這一系列操作會話的工具來完成很多功能,如單點登陸,單設備登陸,踢出用戶,獲取所有登陸用戶等信息。
本章代碼地址 : https://github.com/zhaojun1998/Premission-Study/tree/master/Permission-Shiro-09/