分布式会话
会话session代表的是客户端与服务器的一次交互过程,这个过程可以是连续也可以是时断时续。曾经在Servlet时代(jsp),一旦用户与服务端交互,服务器tomcat就会为用户创建一个session,同时前段会有一个jsessionid,每次交互都会携带。如此一来,服务器只要在接到用户请求时候,就可以拿到jsessonid,并根据这个ID在内存中找到对应的会话session,当拿到session会话后,那么我们就可以操作会话了。会话存活期间,我们就能认为用户一直处于正在使用着网站的状态,一旦session过期是,那么久可以认为用户已经离开网站,停止交互了。用户的身份信息,我们也是通过session来判断的,在session中可以保存不同用户的信息。
HTTP请求时无状态的,用户想服务器发起多个请求,服务器并不会知道这多次请求都是来自同一个用户的,这个就是无状态的。cookie的出现就是为了有状态的记录用户。常见的,ios与服务器交互,安卓与服务器交互,他们都是用过发起http请求来调用接口数据的,每次及哦啊胡服务器都不会拿到客户端的状态,但是我们可以通过手段去处理,比如每次用户发起请求的时候携带一个userid或者user-token,如此一来,就能让服务器分局用户id或者token来获得相应的数据。每个用户的下一次请求都能被服务器识别来自同一个用户。
tomcat中的会话,就是有状态的,一旦用户和服务器交互,就有会话,会话保存了用户的信息,这样用户就“有状态”了,服务器会和每个客户端都保持这样的一层关系,这个由容器来管理,这个session会话是保存到内存空间中的,如此一来,当不同的用户访问服务器,那么久能通过会话知道谁是谁了。tomcat会话的出现也是为了让http请求变得有状态。如果用户不在和服务器交互,那么会话超时则小时,结束了他的生命周期。
生成token
使用场景,一般在用户注册以及登录时为用户创建一个token,因为一般这都是用户进入交互过程的第一步
public UserVO convertUserVO(Users user) {
//使用uuid生成一个随机的唯一的序列码,然后根据userId保存到redis中去
String uniqueToken = UUID.randomUUID().toString().tirm();
redisTemplate.opsForValue().set(REDIS_USER_TOKEN + ":" + userId, uniqueToken);
//一般在我们完成注册或者登录后,在验证完用户名和密码后都会返回一个包含用户信息的user对象
//而同时我们需要生成一个user——token和user对象一同返回到方法调用方,因为这个token后面是要设置到
//cookie里面的
UserVO userVO = new UserVO();
BeanUtils.copyProperities(user, userVO);
userVO.setUserToken(uniqueToken );
return userVO;
}
会话拦截器
我们在controller包下创建一个interceptor的子包,创建一个类来定义我们的拦截器
public class UserTokenInterceptor implements HandlerInterceptor {
@Autowired
private RedisOperator redisOperator;
public static final String REDIS_USER_TOKEN = "redis_user_token";
//在请求到达controller前的阶段
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//这需要后端和前段协调一下,让前段将cookie中的信息解析到header中在向后端发起请求
String userToken = request.getHeader("headerUserToken");
String userId = request.getHeader("headerUserId");
if (StringUtils.isBlank(userToken) || StringUtils.isBlank(userId)) {
returnErrorResponse(response, JSOMResult.errorMsg("请登录");
return false;
} else {
String token = RedisOperator.get(REDIS_USER_TOKEN + ":" + userId);
//这就等于在我们服务器中没有该用户的session,或者其session已经超时了
if (StringUtils.isBlank(token)) {
returnErrorResponse(response, JSOMResult.errorMsg("请登录");
return false;
} else {
//服务器中的session信息和用户cookie中的不同,这样可能是因为用户异地登录了的原因
if (!token.equals(userToken)) {
returnErrorResponse(response, JSOMResult.errorMsg("请重新登录");
return false;
}
}
}
return true;
}
private void returnErrorResponse(HttpServletResponse response, JSONResult result) {
outputStream = null;
try{
response.setCharacterEncoding("utf-8");
response.setContentType("text/json");
out = response.getOutputStream();
out.write(JsonUtils.objectToJson(result).getBytes("utf-8");
} catch (IOException e) {
e.printStackTrace();
} finally {
try{
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//下面省略了postHandler和afterComplete两个方法的内容,因为我们暂时用不上
}
下面是配置类的内容,如果是使用SpringMVC的话,就直接写一个xml配置文件
在controller包创建一个config的子包,在里面创建一个WebMvcConfig类
/**
* 这个类是要实现WebMvcConfigurer接口的,里面有很多方法,可以自行选择,包括静态资源的映射也是用他
* 因为拦截器相应注解来自动注入容器,所以这里用@Bean来生成
* 在拦截器添加器中用注册器先调用这个Bean方法生成
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public UserTokenInterceptor userTokenInterceptor() {
return new UserTokenInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userTokenInterceptor())
.addPathPattern("/shopcart/add")
.addPathPattern("/shopcart/*")
.addPathPattern("/address/list")
.addPathPattern("/address/add")
.addPathPattern("/address/*")
.addPathPattern("/orders/*")
.addPathPattern("/userInfo/*")
.addPathPattern("/myorders/*")
.excludePathPattern("/myorders/deliver")
.excludePathPattern("/orders/notifOrderPaid");
WebMvcConfigurer.super.addInterceptors(registry);
}
}