Loading... ## 前言 Shiro 提供了完整的会话管理功能,可以在不依赖底层容器,不仅可以在 WEB 环境下使用 Session,还可以在 JavaSE 环境下使用,且提供了会话管理,会话事件监听,会话持久化,过期支持。 <!--more--> ## 会话操作 所谓会话,即用户访问应用时保持的连接关系,在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。如访问一些网站时登录成功后,网站可以记住用户,且在退出之前都可以识别当前用户是谁。 获取 Session 方法: ```java Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); ``` Session 常用方法: ```java 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容器的会话管理。 ## 会话监听器 会话监听器用于监听会话创建、过期及停止事件: ```java 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`: ```java 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`: ```xml <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`: ```java 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` 中: ```xml <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/ 最后修改:2022 年 05 月 02 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请我喝杯咖啡吧。