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/
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有