在传统的单tomcat容器中,用户访问服务器,tomcat会为该用户创建一个session,并将该session的sessionid通过cookie传送给用户,当用户再次访问时如果cookie中携带sessionid,则会到tomcat中寻找指定session。如下图所示:
但是当tomcat容器不止一台时,这种模式下就会出现问题。在分布式环境下,后台服务器往往有多台,用户在访问后台资源时往往会通过如nginx等应用服务器进行负载均衡。比如说你此次登录的时候被分配到tomcat A,那你的session将会存储到tomcat A,但是你接下来的某一次操作中需要在session中获取用户认证信息,而负责这个操作的是tomcat B,tomcat B根据你的sessionid在tomcat B中找不到你的session,这个时候你就无法完成这些需要身份认证才能执行的操作。即便你重新登录,但每次访问的tomcat都有可能不同,这有可能导致你永远执行不了某一些操作。如下图所示:
为了解决上面所说的问题,就需要统一对session进行管理,而基于redis本身的特性,使得redis在做分布式会话管理的时候有很大的优势,因为redis本身存储的数据就是key-value结构,同时redis对于数据的存储还可以设置过期时间。我们可以将session迁移到redis进行统一管理,只需要存入redis中的key(sessionid)返回给用户即可。而对于返回的形式,这里介绍两种:基于cookie传输sessionid、基于token传输sessionid。
以下操作基于SpringBoot2.0.5版本。
传统模式
登录操作:
/**
* @author: Charviki
* @create: 2019-09-20 9:40
**/
@Controller
@RequestMapping("user")
public class UserController {
@Autowired
private HttpServletRequest request;
@PostMapping
public void login(@RequestParam("username")String username,
@RequestParam("password")String password){
// 这里省略用户名密码校验过程
// 模拟session存储用户校验信息
request.getSession().setAttribute("user","用户已登录");
}
}
比如在下单环境下,需要校验用户是否登录:
/**
* @author: Charviki
* @create: 2019-09-20 10:39
**/
@Controller
@RequestMapping("order")
public class OrderController {
@Autowired
private HttpServletRequest request;
@PostMapping
public void createOrder(){
// 获取session中的用户信息
Object user = request.getSession().getAttribute("user");
if (user == null){
// 校验不通过
}
// 这里省略后端校验用户信息和下单过程
}
}
分布式环境下
以下两种方式的实现都是基于传统模式的代码。
基于cookie传输sessionid
在springboot中引入依赖:
<!-- 这里使用的是SpringBoot2.0.5 -->
<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>
<version>2.0.5.RELEASE</version>
</dependency>
在SpringBoot配置文件中指定redis地址(默认为localhost),这里使用的是application.yml:
spring:
redis:
host: 192.168.127.200
新增以下配置类:
@Component
// session过期时间
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
public class RedisConfig {
}
这是SpringBoot自身的一套实现,在不改变现有代码的情况下,通过标准的servlet api调用session,在底层就会通过Spring Session得到的,并且会存储到Redis或其他你所选择的数据源中进行集中管理。
基于token传输sessionid
这里先说明一个问题,基于token传输sessionid和基于cookie传输sessionid其实实现效果差不多,但为什么还要有基于token的实现呢?因为对于现在的企业级应用来说,可能有小程序、安卓、网页端等共用同一套服务器程序,其他设备可能不存在cookie域的说法,所有需要基于token传输sessionid的实现。
需要在SpringBoot配置文件中指定redis地址:
spring:
redis:
host: 192.168.127.200
修改登录代码:
@RestController
public class UserController {
@Autowired
private RedisTemplate redisTemplate;
@PostMapping("/user")
public String login(@RequestParam("username")String username,
@RequestParam("password")String password){
// 这里省略用户名密码校验过程
// 生成token
String uuidToken = UUID.randomUUID().toString().replace("-","");
// 将用户信息存入redis
redisTemplate.opsForValue().set("user","用户已登录");
// 设置过期时间为一小时
redisTemplate.expire(uuidToken,1, TimeUnit.HOURS);
// 返回给前端token(sessionid)信息
return uuidToken;
}
}
在下单环境中,更改校验代码如下:
@Controller
@RequestMapping("order")
public class OrderController {
@Autowired
private RedisTemplate redisTemplate;
@PostMapping
public void createOrder(@RequestParam("token")String token){
// 获取用户信息
Object user = redisTemplate.opsForValue().get("token");
if (user == null){
// 校验不通过
}
// 这里省略后端校验用户信息和下单过程
}
}