参考文章:http://blog.csdn.net//article/details/
依赖库 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(); //
,应改成 redis 远程存储管理,此处仅用于演示
private static final Map
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 = L; @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为:b-7553-4de7-9125-a893b840d16b Token(HelloWorld) 会话已存在:b-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
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/215646.html原文链接:https://javaforall.net
