Shiro 集成 Spring 之會話管理

前言

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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章