为什么需要分布式 session,对于 API 接口是不需要 session 的,对于后台管理页面,基于 session 的认证及授权如 shiro 是需要分布式 session 的。
spring cloud 常用的分布式 session 解决方案 redisSession 是基于 cookie 来存贮 session 的,对于前后端分离的项目,可以在请求中携带 cookie。
1. 搭建一个项目 distribute-session
启动类入口增加 @EnableRedisHttpSession 注解 启用分布式 session,将服务注册到注册中心 nacos.
主要依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
复制代码
增加 redis 配置
spring:
redis:
host: 127.0.0.1
port: 6379
# password: 123456
复制代码
增加测试方法,通过指定不同 port 启动两个服务。
@GetMapping("/index")
public String index(HttpServletRequest request){
String sessionId = request.getSession().getId();
log.info("HomeController index port {} sessionId: {}",port,sessionId);
return sessionId;
}
复制代码
2. 创建网关项目
路由配置
spring:
cloud:
gateway:
#路由配置:参数为一个List
routes:
#唯一标识
- id: paw-distribute-session-route
#转发的地址,写服务名称
uri: lb://paw-distribute-session
#判断匹配条件,即地址带有path的请求
predicates:
- Path=/distribute-session-api/**
filters:
#去掉Path前缀,参数为1代表去掉1ceng
- StripPrefix=1
复制代码
启动服务,访问 http://127.0.0.1:9000/distribute-session-api/index 会发现 不同 port 服务返回的 sessionId 相同,即分布式 session 搭建完成。
3. RedisHttpSession 的探索
从注解中看到配置类 RedisHttpSessionConfiguration,类中注入了 session 持久化的类 RedisIndexedSessionRepository,该类实现了 SessionRepository。
查看 createSession 方法,通过 MapSession 以 UUID 的方式创建了 sessionId,通过 redisTemplate 存储到 redis 中
public RedisSession createSession() {
MapSession cached = new MapSession();
if (this.defaultMaxInactiveInterval != null) {
cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
RedisSession session = new RedisSession(cached, true);
session.flushImmediateIfNecessary();
return session;
}
复制代码
private static String generateId() {
return UUID.randomUUID().toString();
}
复制代码
EnableRedisHttpSession 说明中更多的配置参见 EnableSpringHttpSession,打开配置类 SpringHttpSessionConfiguration,其中有一些 required=false 的配置,程序内部提供了默认实现,即用户可以进行进行自定义配置。如 HttpSessionIdResolver 提供了基于 cookie 的 sessionId 解析器,并默认进行 base64 编码。
private HttpSessionIdResolver httpSessionIdResolver = this.defaultHttpSessionIdResolver;
@Autowired(required = false)
public void setHttpSessionIdResolver(HttpSessionIdResolver httpSessionIdResolver) {
this.httpSessionIdResolver = httpSessionIdResolver;
}
复制代码
支持 redisHttpSession 的大致配置及流程已有脉路。
4. 基于 token 的分布式 session
思考: 对于前后端分离的管理后台项目,是否可以携带 token 的方式进行保持 session 会话。
自定义 HttpSessionIdResolver 并注入到配置中
// 实现接口 HttpSessionIdResolver
public class TokenHttpSessionIdResolver implements HttpSessionIdResolver {
public static final String TOKEN = "token";
public TokenHttpSessionIdResolver(){
}
@Override
public List<String> resolveSessionIds (HttpServletRequest request) {
List<String> list = new ArrayList<>();
// 从header中取出token作为sessionId
String token = request.getHeader(TOKEN);
if(StrUtil.isNotEmpty(token)){
list.add(token);
}
return list;
}
@Override
public void setSessionId (HttpServletRequest request, HttpServletResponse response, String sessionId) {
}
@Override
public void expireSession (HttpServletRequest request, HttpServletResponse response) {
}
}
复制代码
@Configuration
public class WebConfiguration {
// 注入bean
@Bean
public HttpSessionIdResolver httpSessionIdResolver(){
return new TokenHttpSessionIdResolver();
}
}
复制代码
增加获取 token 的接口
@GetMapping("/getToken")
public String getToken(HttpServletRequest request){
String sessionId = request.getSession().getId();
log.info("sessionId: {}",sessionId);
return sessionId;
}
复制代码
获取 token 后,再次请求在 header 中携带 token,sessionId 保持不变。
此种方式仅供娱乐研究。
总结:
RedisHttpSession 通过生成一个 UUID 的 session 存储在 Redis 中,并写入到 cookies 中返回给浏览器,浏览器再次访问时携带 cookies, springHttpSession 解析获取 session,并根据 redis 存贮的对比一致,则是同一个会话。
评论