安全/权限框架Shiro(14)之SessionDao

安全/权限框架Shiro(14)之SessionDao

微信搜索 zze_coding 或扫描 👉 二维码关注我的微信公众号获取更多资源推送:

介绍

通过 SessionDao 可以把 Session 保存在我们想要保存的地方,比如 MySQL,也可以是 Redis,甚至是一个文件,我们可以自定义它的 CRUD 操作。下面是 Shiro 提供的几个 SessionDao 管理实现:

  • org.apache.shiro.session.mgt.eis.AbstractSessionDAO :提供了 org.apache.shiro.session.mgt.eis.SessionDAO 的基本实现,如生成会话 ID 等。
  • org.apache.shiro.session.mgt.eis.CachingSessionDAO :提供了对开发者透明的会话缓存功能,需要设置相应的 CacheManager
  • org.apache.shiro.session.mgt.eis.MemorySessionDAO :直接在内存中进行会话维护。
  • org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO :提供了缓存功能的会话维护,默认情况下使用 MapCache 实现,内部使用 ConcurrentHashMap 保存缓存的会话。

使用示例

下面是一个将 Session 保存在 MySQL 中的一个示例。

1、引入对象序列化工具类:

// com.zze.shiro.web.utils.SerializableUtils
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.Session;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableUtils {

    public static String serialize(Session session) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(session);
            return Base64.encodeToString(bos.toByteArray());
        } catch (Exception ex) {
            throw new RuntimeException("serialize session error", ex);
        }
    }

    public static Session deSerialize(String sessionStr) {
        try {
            ByteArrayInputStream bis = new ByteArrayInputStream(Base64.decode(sessionStr));
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (Session) ois.readObject();

        } catch (Exception ex) {
            throw new RuntimeException("deserialize session error", ex);
        }
    }
}

2、自定义 SessionDao 的实现,重写对 Session CRUD 的方法:

// com.zze.shiro.web.utils.MySessionDao
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.springframework.jdbc.core.JdbcTemplate;

import java.io.Serializable;
import java.util.List;

public class MySessionDao extends EnterpriseCacheSessionDAO {
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = super.doCreate(session);
        String sql = "insert into sessions(id,session) values(?,?)";
        jdbcTemplate.update(sql, sessionId, SerializableUtils.serialize(session));
        return session.getId();
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        String sql ="select session from sessions where id=?";
        List<String> sessionStrList = jdbcTemplate.queryForList(sql, String.class, sessionId);
        if(sessionStrList.size()==0){
            return null;
        }
        return SerializableUtils.deSerialize(sessionStrList.get(0));
    }

    @Override
    protected void doUpdate(Session session) {
        if(session instanceof ValidatingSession && !((ValidatingSession)session).isValid()){
            return;
        }
        String sql = "update sessions set session=? where id=?";
        jdbcTemplate.update(sql, SerializableUtils.serialize(session), session.getId());
    }

    @Override
    protected void doDelete(Session session) {
        String sql = "delete from sessions where id=?";
        jdbcTemplate.update(sql, session.getId());
    }
}

3、配置 SessionDao

<!--配置数据源-->
<bean name="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql:///test"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>
<!--JDBC 模板-->
<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--SessionId 生成器-->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>

<!--sessionDao-->
<bean id="sessionDao" class="com.zze.shiro.web.utils.MySessionDao">
    <!--缓存名称,对应 ehcache.xml 中的缓存名称-->
    <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
    <!--指定 SessionId 生成器-->
    <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
    <!--注入 jdbc 模板-->
    <property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>

<!--会话管理器-->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <property name="sessionDAO" ref="sessionDao"/>
    <!-- Session 失效时长,单位毫秒 -->
    <property name="globalSessionTimeout" value="1800000"/>
    <!-- 删除失效的 Session -->
    <property name="deleteInvalidSessions" value="true"/>
    <!-- 是否定期检查 Session 的有效性 -->
    <property name="sessionValidationSchedulerEnabled" value="true"/>
    <property name="sessionListeners">
        <list>
            <ref bean="shiroSessionListener"/>
        </list>
    </property>
</bean>

Shiro 提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将会停止会话。出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的,但如果在 web 环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro 提供了会话验证调度器 org.apache.shiro.session.mgt.SessionValidationScheduler,也提供了使用 Quartz 的会话验证调度器:org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.zze.xyz/archives/shiro14.html

Buy me a cup of coffee ☕.