SpringBoot+Shiro学习(五):Session会话管理

这节我们讲一下,Shiro的Session会话管理。

会话

所谓会话,即用户访问应用时保持的连接关系,在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。如访问一些网站时登录成功后,网站可以记住用户,且在退出之前都可以识别当前用户是谁。

SessionManager

会话管理器管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。是Shiro的核心组件,顶层组件SecurityManager直接继承了SessionManager,且提供了SessionsSecurityManager实现直接把会话管理委托给相应的SessionManager。Shiro提供了两个实现:DefaultSecurityManager及DefaultWebSecurityManager。

Shiro提供了三个默认实现:

DefaultSessionManager:DefaultSecurityManager使用的默认实现,用于JavaSE环境;

ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话;

DefaultWebSessionManager:用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理。

我们主要使用DefaultWebSessionManager,因为是web应用。列举一下常用的方法:

void validateSessions();//验证所有会话是否过期  
Session getSession(SessionKey key) throws SessionException; //根据会话Key获取会话 

sessionManager.setSessionValidationInterval(10000);//定时查询所有session是否过期的时间
sessionManager.setSessionListeners(List<SessionListener> list);//设置session监听器
sessionManager.setGlobalSessionTimeout(10000);//设置session过期时间
sessionManager.setSessionDAO(redisSessionDao());设置SessionDao
复制代码

我们可以通过继承DefaultWebSessionManager来自定义我们的SessionManager,如下,我们将session存入到会话中的request。这样就不用每次都去缓存中取,提高性能:

public class CustomSessionManager extends DefaultWebSessionManager {

    @Override
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = getSessionId(sessionKey);
        ServletRequest request = null;
        if(sessionId instanceof WebSessionKey){
            request = ((WebSessionKey)sessionKey).getServletRequest();
        }
        if(request != null && sessionId != null){
            return (Session) request.getAttribute(sessionId.toString());
        }

        Session session = super.retrieveSession(sessionKey);
        if(request !=null && sessionId != null){
            request.setAttribute(sessionId.toString(),session);
        }

        return session;
    }
}
复制代码

会话监听器

会话监听器用于监听会话创建、过期及停止事件:

@Component
public class MySessionListener implements SessionListener {

    private final AtomicInteger sessionCount = new AtomicInteger(0);

    @Override
    public void onStart(Session session) {
        sessionCount.incrementAndGet();
        System.out.println("登陆+1=="+sessionCount.get());
    }
    @Override
    public void onStop(Session session) {
        sessionCount.decrementAndGet();
        System.out.println("登陆-1=="+sessionCount.get());
    }
    @Override
    public void onExpiration(Session session) {
        sessionCount.decrementAndGet();
        System.out.println("登陆过期-1=="+sessionCount.get());
    }
}
复制代码

如果只想监听某一个事件,可以继承SessionListenerAdapter实现:

public class MySessionListener2 extends SessionListenerAdapter {  
    @Override  
    public void onStart(Session session) {  
        System.out.println("会话创建:" + session.getId());  
    }  
}  
复制代码

会话存储/持久化

Shiro提供SessionDAO用于会话的CRUD,即DAO(Data Access Object)模式实现:

//如DefaultSessionManager在创建完session后会调用该方法;如保存到关系数据库/文件系统/NoSQL数据库;即可以实现会话的持久化;返回会话ID;主要此处返回的ID.equals(session.getId());  
Serializable create(Session session);  
//根据会话ID获取会话  
Session readSession(Serializable sessionId) throws UnknownSessionException;  
//更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用  
void update(Session session) throws UnknownSessionException;  
//删除会话;当会话过期/会话停止(如用户退出时)会调用  
void delete(Session session);  
//获取当前所有活跃用户,如果用户量多此方法影响性能  
Collection<Session> getActiveSessions();  
复制代码

AbstractSessionDAO提供了SessionDAO的基础实现,如生成会话ID等;
CachingSessionDAO提供了对开发者透明的会话缓存的功能,只需要设置相应的CacheManager即可;MemorySessionDAO直接在内存中进行会话维护;
EnterpriseCacheSessionDAO提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。

我们可以通过自定义SessionDao继承AbstractSessionDAO重写方法将session持久化到redis等缓存中。

public class RedisSessionDao extends AbstractSessionDAO {

    @Resource
    RedisUtil redisUtil;

    private final String SHIRO_SESSIOM_PREFIX = "shiro-session";

    private byte[] getKey(String key){
        return (SHIRO_SESSIOM_PREFIX+key).getBytes();
    }

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session,sessionId);
        saveSession(session);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        if (sessionId == null) {
            return null;
        }
        byte[] key = getKey(sessionId.toString());
        byte[] value = redisUtil.get(key);
        return (Session) SerializationUtils.deserialize(value);
    }


    @Override
    public void update(Session session) throws UnknownSessionException {
        saveSession(session);

    }

    private void saveSession(Session session){
        if(session !=null&& session.getId()!=null) {
            byte[] key = getKey(session.getId().toString());
            byte[] value = SerializationUtils.serialize(session);
            redisUtil.set(key, value);
            redisUtil.expire(key, 600);
        }
    }

    @Override
    public void delete(Session session) {
        if(session == null || session.getId() ==null){
            return;
        }
        byte[] key = getKey(session.getId().toString());
        redisUtil.del(key);
    }

    @Override
    public Collection<Session> getActiveSessions() {
        Set<byte[]> keys = redisUtil.getKeys(SHIRO_SESSIOM_PREFIX);
        Set<Session> sessions = new HashSet<>();
        if(CollectionUtils.isEmpty(keys)){
            return sessions;
        }
        for(byte[] key:keys){
            Session session = (Session)SerializationUtils.deserialize(redisUtil.get(key));
            sessions.add(session);
        }
        return sessions;
    }
}
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值