Shiro 分布式Session 使用Mysql 来存储解决思路 ?

Shiro 作为权限控制框架, 现在因要拓展多服务分布式 ,要求使用 Mysql 作为Seesion 存储解决方案 , 网上90%都是Redis , 关于Mysql的很少而且大多都是错误的, 恳请各位大佬给个思路 , 谢谢~~~

阅读 3.4k
4 个回答

创建一个用于存储会话信息的 MySQL 数据库,并创建一个 session 表,用于存储会话 ID 和会话数据。session 表的结构可以如下所示:

CREATE TABLE `session` (
  `session_id` varchar(255) NOT NULL,
  `session_data` mediumtext,
  `session_expire_time` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

实现 SessionDAO 接口,将会话信息存储到 MySQL 数据库中。可以通过 JDBC 或者 ORM 框架(如 MyBatis)来操作 MySQL 数据库。

public class MySQLSessionDAO implements SessionDAO {
    private JdbcTemplate jdbcTemplate;

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

    @Override
    public void update(Session session) throws UnknownSessionException {
        // 将会话信息更新到 MySQL 数据库中
        String sql = "UPDATE session SET session_data = ?, session_expire_time = ? WHERE session_id = ?";
        jdbcTemplate.update(sql, session.getAttributeKeys(), session.getLastAccessTime().getTime(), session.getId());
    }

    @Override
    public void delete(Session session) {
        // 从 MySQL 数据库中删除会话信息
        String sql = "DELETE FROM session WHERE session_id = ?";
        jdbcTemplate.update(sql, session.getId());
    }

    @Override
    public Collection<Session> getActiveSessions() {
        // 获取所有活动会话的 ID
        String sql = "SELECT session_id FROM session WHERE session_expire_time > ?";
        List<String> sessionIds = jdbcTemplate.queryForList(sql, String.class, System.currentTimeMillis());
        List<Session> sessions = new ArrayList<>();
        for (String sessionId : sessionIds) {
            Session session = readSession(sessionId);
            sessions.add(session);
        }
        return sessions;
    }

    @Override
    public Serializable create(Session session) {
        // 将会话信息存储到 MySQL 数据库中,并返回会话 ID
        Serializable sessionId = UUID.randomUUID().toString();
        String sql = "INSERT INTO session (session_id, session_data, session_expire_time) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, sessionId, session.getAttributeKeys(), session.getLastAccessTime().getTime());
        return sessionId;
    }

    @Override
    public Session readSession(Serializable sessionId) throws UnknownSessionException {
        // 从 MySQL 数据库中读取会话信息
        String sql = "SELECT session_data, session_expire_time FROM session WHERE session_id = ?";
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, sessionId);
        if (rows.isEmpty()) {
            throw new UnknownSessionException("Session not found: " + sessionId);
        }
        Map<String, Object> row = rows.get(0);
        SimpleSession session = new SimpleSession(sessionId.toString());
        session.setAttributeKeys((Set<String>) row.get("session_data"));
        session.setLastAccessTime(new Date((Long) row.get("session_expire_time")));
        return session;
    }
}

在 Shiro 配置文件中配置 SessionDAO 实现类和数据源(即 MySQL 数据库连接):

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="username"/>
    <property name="password" value="password"/>
</bean>

<bean id="sessionDAO" class="com.example.MySQLSessionDAO">
    <property name="jdbcTemplate">
        <bean class="org.springframework.jdbc.core.JdbcTemplate">
            <constructor-arg ref="dataSource"/>
        </bean>
    </property>
</bean>

<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <property name="sessionDAO" ref="sessionDAO"/>
</bean>

在上面的配置文件中,我们配置了一个 dataSource bean,用于连接 MySQL 数据库;一个 sessionDAO bean,用于存储和读取会话信息;以及一个 sessionManager bean,用于管理会话。

在 Shiro 过滤器链中添加 SessionFilter 过滤器,用于启用分布式会话管理功能。

<bean id="shiroFilter" class="org.apache.shiro.web.servlet.ShiroFilter">
    <property name="securityManager" ref="securityManager"/>
    <property name="filters">
        <util:map>
            <entry key="session" value-ref="sessionFilter"/>
        </util:map>
    </property>
    <property name="filterChainDefinitions">
        <value>
            /login = anon
            /logout = logout
            /** = session,authc
        </value>
    </property>
</bean>

<bean id="sessionFilter" class="org.apache.shiro.web.filter.session.NoSessionCreationFilter"/>

在上面的配置文件中,我们将 sessionFilter bean 配置为 NoSessionCreationFilter,表示不创建新的会话,而是使用已有的分布式会话。

最后,在 Shiro 的认证和授权操作中,我们可以通过 Subject.getSession() 方法获取当前用户的会话信息,并在需要的地方使用。例如:

Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
String username = (String) session.getAttribute("username");

在上面的示例中,我们获取当前用户的会话信息,并从中读取 username 属性的值。

总的来说,使用 MySQL 存储分布式会话是一种可行的方案,可以实现多台服务器之间的共享。但是,在实际应用中需要注意数据库的性能和可靠性,以及分布式会话的一致性和安全性等问题。

在MySQL中创建一张表,主要包含下面的3个字段

cookie,user_id

用户登录后设置一个cookie
在拦截器中获取浏览器器中的cookie,通过cookie查询表,查询不到user_id,重定向到登录界面

redis怎么做的,mysql就怎么做,一样的方式,只是持久化不同。
将redis的保存、查询接口改为mysql的保存、查询,接口一样,只是具体的实现不一样

mysql可以使用memory存储引擎建表,存储session信息,特点是基于内存读取速度快,但写入并发不高。
memory存储引擎

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏