Spring Session 整合 JWT Token

本文介绍了如何将Spring Session与JWT Token进行整合,通过创建自定义会话策略类关联两者,实现JWT token的会话管理。核心组件springSessionRepositoryFilter用于重写会话方法,配置MyHttpSessionStrategy覆盖默认策略。测试显示,即使在不同客户端,JWT token也能与特定会话关联,实现了会话共享和认证访问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考文章:http://blog.youkuaiyun.com/qq351790934/article/details/54930049

依赖库 pom.xml

        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>1.3.1.RELEASE</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.3.4.RELEASE</version>
        </dependency>

Spring Session 默认的会话策略类是 CookieHttpSessionStrategy,其行为方式与 Tomcat 会话管理大同小异(但是存储方式不同,一个是redis远程存储,一个是堆内存),我们可以通过创建自己的会话策略类来干涉 Spring Session 的会话管理(很遗憾,目前Spring Session不支持自定义sessionId,为了让 jwt token 享受到会话管理,需要手动将两者关联起来)

/**
 * 自定义的 Spring Session 会话策略
 */
public class MyHttpSessionStrategy implements HttpSessionStrategy {

    //框架默认的会话策略,之所以选择组合的方式,是因为该类是 final,无法继承
    private static final CookieHttpSessionStrategy strategy = new CookieHttpSessionStrategy();

    //<JWT Token, 会话id>,应改成 redis 远程存储管理,此处仅用于演示
    private static final Map<String, String> token2SessionId = new ConcurrentHashMap<>();

    private static final String TOKEN_NAME = "x-jwt-token";

    //当获取会话时回调此方法,返回的sessionid用于从redis取得对应的会话对象
    @Override
    public String getRequestedSessionId(HttpServletRequest request) {
        //从HTTP头部或参数获取 jwt token
        String token = getJwtToken(request);
        if (null != token && !token.equals("")) {
            //寻找token对应的sessioid
            String sessionId = token2SessionId.get(token);
            if (null != sessionId) {
                System.out.println("发现 JWT(" + token + "),其关联会话: " + sessionId);
                return sessionId;
            }

        }
        //框架默认的会话策略,从Cookie获取sessionid
        return strategy.getRequestedSessionId(request);
    }

    //当新建会话时回调此方法
    @Override
    public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
        String token = getJwtToken(request);
        if (null != token && !token.equals("")) {
            String old = token2SessionId.get(token);
            if (null != old) {
                //会话已存在
            } else {
                //将Token关联新会话,至于JWT的校验流程可由后续处理
                token2SessionId.put(token, session.getId());
            }

        }
        //框架默认的会话策略,将sessionid写入cookie
        strategy.onNewSession(session, request, response);
    }

    //当会话过期时回调此方法
    @Override
    public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) {
        String token = getJwtToken(request);
        if (null != token && !token.equals("")) {
            token2SessionId.remove(token);
        }

        strategy.onInvalidateSession(request, response);
    }

    private String getJwtToken(HttpServletRequest request) {
        return null != request.getHeader(TOKEN_NAME) ? request.getHeader(TOKEN_NAME) : request.getParameter(TOKEN_NAME);
    }

    /**
     *
     * @param token
     * @param sessionId
     * @return true表示新旧会话一致; false表示新旧会话不一样,既然旧会话是有效的,呢么新会话就是多余的,可回收
     */
    public static boolean checkAndSetSessionId(String token, String sessionId) {
        String old = token2SessionId.get(token);
        if (null != old) {
            //会话已存在
            System.out.println("Token(" + token + ") 会话已存在:" + sessionId + "; 选择无视新会话");
            //判断新旧会话是否相同
            return sessionId.equals(old);
        } else {
            //将 JWT Token 与 sessionId 关联起来
            token2SessionId.put(token, sessionId);
            System.out.println("将计算得到的 Token(" + token + ") 关联 会话id(" + sessionId + ")");
        }

        return true;
    }

}

配置Spring Sessio最核心的一个组件,springSessionRepositoryFilter,其位于过滤链最前端,用于重写会话相关的方法;Spring应用只需要如下代码,其他的例如集成Spring Boot、Spring Security可参考官方示例

public class Initializer extends AbstractHttpSessionApplicationInitializer {

    public Initializer() {
        super(Config.class);
    }
}

配置MyHttpSessionStrategy,以覆盖Spring容器中默认的会话策略(CookieHttpSessionStrategy)

@EnableRedisHttpSession
public class Config {

    @Bean
    public JedisConnectionFactory connectionFactory() {
        JedisConnectionFactory connection = new JedisConnectionFactory();
        connection.setPort(6379);
        connection.setHostName("127.0.0.1");
//        connection.setPassword("password");
        connection.setDatabase(8);
        return connection;
    }

    @Bean
    public HttpSessionStrategy httpSessionStrategy() {
        return new MyHttpSessionStrategy();
    }
}

定义一个登录Servlet,用于测试

@WebServlet("/login")
public class LoginServlet extends HttpServlet {

    private static final long serialVersionUID = 2878267318695777395L;

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String sessionId = req.getSession().getId();
        System.out.println("当前会话id为:" + sessionId);

        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (null != username && username.equals("username") && null != password && password.equals("password")){
            //模拟 JWT 计算得到 token
            String token = "HelloWorld";
            if (!MyHttpSessionStrategy.checkAndSetSessionId(token, sessionId)){
                //表示新旧会话不同,既然旧会话是有效的,呢么新会话就是多余的,可回收
                System.out.println("JWT Token已存在会话,可重复使用,当前新会话是多余的");
                req.getSession().invalidate();
            }
        }
    }
}

测试开始,先普通地访问Servlet,不传任何参数,输出为

当前会话id为:75f67e86-cf3d-4672-be6c-8fb5c7be80ea
当前会话id为:75f67e86-cf3d-4672-be6c-8fb5c7be80ea
当前会话id为:75f67e86-cf3d-4672-be6c-8fb5c7be80ea
当前会话id为:75f67e86-cf3d-4672-be6c-8fb5c7be80ea
当前会话id为:75f67e86-cf3d-4672-be6c-8fb5c7be80ea

可以看到,与普通的浏览器会话管理一样,只要浏览器不关闭,会话能保持30分钟;

传参 username 和 password,尝试第一次登录

当前会话id为:41aa06d2-13d5-4d13-8251-c8be9a74d687
将计算得到的 Token(HelloWorld) 关联 会话id(41aa06d2-13d5-4d13-8251-c8be9a74d687)

不关闭浏览器,继续尝试第二、第三次重复登录

当前会话id为:41aa06d2-13d5-4d13-8251-c8be9a74d687
Token(HelloWorld) 会话已存在:41aa06d2-13d5-4d13-8251-c8be9a74d687; 选择无视新会话
当前会话id为:41aa06d2-13d5-4d13-8251-c8be9a74d687
Token(HelloWorld) 会话已存在:41aa06d2-13d5-4d13-8251-c8be9a74d687; 选择无视新会话

关闭浏览器,重新启动以模拟不同的客户端,尝试第四、第五次重复登录

当前会话id为:b4385394-7553-4de7-9125-a893b840d16b
Token(HelloWorld) 会话已存在:b4385394-7553-4de7-9125-a893b840d16b; 选择无视新会话
JWT Token已存在会话,可重复使用,当前新会话是多余的
当前会话id为:45a66d45-c328-4c1a-a774-f1612c2cd727
Token(HelloWorld) 会话已存在:45a66d45-c328-4c1a-a774-f1612c2cd727; 选择无视新会话
JWT Token已存在会话,可重复使用,当前新会话是多余的

可以看到,每一个 JWT token 都能唯一关联固定的会话,实现了不同客户端共享同一个会话(如果需要其他逻辑,可自定义实现)

最后,传参 x-jwt-token:HelloWorld,实现 token 认证访问

发现 JWT(HelloWorld),其关联会话: 41aa06d2-13d5-4d13-8251-c8be9a74d687
当前会话id为:41aa06d2-13d5-4d13-8251-c8be9a74d687
发现 JWT(HelloWorld),其关联会话: 41aa06d2-13d5-4d13-8251-c8be9a74d687
发现 JWT(HelloWorld),其关联会话: 41aa06d2-13d5-4d13-8251-c8be9a74d687
当前会话id为:41aa06d2-13d5-4d13-8251-c8be9a74d687
发现 JWT(HelloWorld),其关联会话: 41aa06d2-13d5-4d13-8251-c8be9a74d687
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值