Spring Session 整合 JWT Token

Spring Session 整合 JWT Token依赖库pom.xmldependencygroupIdorg.springframework.session/groupIdartifactIdspring-session-data-redis/artifactIdversion1.3.1.RELEASE/version

参考文章:http://blog.csdn.net/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

今天的文章Spring Session 整合 JWT Token分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/4630.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注