分布式会话之token解决方案实现
用token代替session,基于redis实现用户会话管理,配置登录拦截器,步骤如下
1、实现接口:HandlerInterceptor,重写 preHandle方法。
public class UserTokenInterceptor implements HandlerInterceptor {
@Autowired
private RedisOperator redisOperator;
public static final String REDIS_USER_TOKEN = "redis_user_token";
/**
* 拦截请求,在访问controller调用之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// System.out.println("进入到拦截器,被拦截。。。");
String userId = request.getHeader("headerUserId");
String userToken = request.getHeader("headerUserToken");
if (StringUtils.isNotBlank(userId) && StringUtils.isNotBlank(userToken)) {
String uniqueToken = redisOperator.get(REDIS_USER_TOKEN + ":" + userId);
if (StringUtils.isBlank(uniqueToken)) {
// System.out.println("请登录...");
returnErrorResponse(response, IMOOCJSONResult.errorMsg("请登录..."));
return false;
} else {
if (!uniqueToken.equals(userToken)) {
// System.out.println("账号在异地登录...");
returnErrorResponse(response, IMOOCJSONResult.errorMsg("账号在异地登录..."));
return false;
}
}
} else {
// System.out.println("请登录...");
returnErrorResponse(response, IMOOCJSONResult.errorMsg("请登录..."));
return false;
}
/**
* false: 请求被拦截,被驳回,验证出现问题
* true: 请求在经过验证校验以后,是OK的,是可以放行的
*/
return true;
}
public void returnErrorResponse(HttpServletResponse response,
IMOOCJSONResult result) {
OutputStream out = null;
try {
response.setCharacterEncoding("utf-8"); // 解决乱码
response.setContentType("text/json");
out = response.getOutputStream(); //printwriter类也可以
out.write(JsonUtils.objectToJson(result).getBytes("utf-8"));
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 请求访问controller之后,渲染视图之前
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 请求访问controller之后,渲染视图之后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
2、配置拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public UserTokenInterceptor userTokenInterceptor() {
return new UserTokenInterceptor();
}
/**
* 注册拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userTokenInterceptor())
.addPathPatterns("/hello")
.addPathPatterns("/shopcart/add")
.addPathPatterns("/shopcart/del")
.addPathPatterns("/address/list")
.addPathPatterns("/address/add")
.addPathPatterns("/address/update")
.addPathPatterns("/address/setDefalut")
.addPathPatterns("/address/delete")
.addPathPatterns("/orders/*")
.addPathPatterns("/center/*")
.addPathPatterns("/userInfo/*")
.addPathPatterns("/myorders/*")
.addPathPatterns("/mycomments/*")
.excludePathPatterns("/myorders/deliver")
.excludePathPatterns("/orders/notifyMerchantOrderPaid");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
3、登录/注册代码
@ApiOperation(value = "用户注册", notes = "用户注册", httpMethod = "POST")
@PostMapping("/regist")
public IMOOCJSONResult regist(@RequestBody UserBo userBo, HttpServletRequest request,
HttpServletResponse response) {
String userName = userBo.getUsername();
String password = userBo.getPassword();
String comfirmPwd = userBo.getConfirmPassword();
if (StringUtils.isBlank(userName) || StringUtils.isBlank(password)
|| StringUtils.isBlank(comfirmPwd)) {
return IMOOCJSONResult.errorMsg("用户名或密码不能为空");
}
boolean isExist = userService.queryUserNameIsExist(userName);
if (isExist) {
return IMOOCJSONResult.errorMsg("用户名已经存在");
}
if (password.length() < 6) {
return IMOOCJSONResult.errorMsg("密码长度不能少于6");
}
if (!password.equals(comfirmPwd)) {
return IMOOCJSONResult.errorMsg("两次密码不一致");
}
Users users = userService.createUser(userBo);
UsersVO usersVO = new UsersVO(); // 展示到前端的用户基本信息,包含token信息
BeanUtils.copyProperties(users,usersVO);
// 实现用户的redis会话
String uniqueToken = UUID.randomUUID().toString().trim();
redisOperator.set(REDIS_USER_TOKEN + ":" + users.getId(), uniqueToken);
usersVO.setUserUniqueToken(uniqueToken);
CookieUtils.setCookie(request, response, "user",
JsonUtils.objectToJson(usersVO), true);
return IMOOCJSONResult.ok();
}
分布式会话之Spring-session解决方案
参考文章:
www.cnblogs.com/toov5/p/990…
blog.csdn.net/qq_43371556…
这两篇文章讲的比较细致。
案例如下:
前提:spring-session,需要依赖spring,这里使用springboot框架
1、依赖包
<!-- 引入 spring-session 依赖 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- 引入 springboot 安全框架,不然会启动报错 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 引入 redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置
spring:
session:
store-type: redis #session 存储类型
redis:
#单节点redis
database: 1
host: 127.0.0.1
port: 6379
3、启动类入口文件
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
// 排除 SecurityAutoConfiguration.class文件,不然会跳到登录页面
@EnableRedisHttpSession //开启使用redis存储session
4、测试
@GetMapping("/setSession")
public Object setSession(HttpServletRequest request){
HttpSession session = request.getSession();
session.setAttribute("userInfo", "new user");
session.setMaxInactiveInterval(3600);
session.getAttribute("userInfo");
return "OK";
}
如果有其他语言调到该redis的session 不适用这种方式,耦合Spring。