SpringBoot+SpringSession+Redis实现session共享及唯一登录

转载:https://blog.csdn.net/xjj1040249553/article/details/82658889

一、pom.xml配置 

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.session</groupId>
  7. <artifactId>spring-session-data-redis</artifactId>
  8. </dependency>

二、application.properties的redis配置

  1. #redis
  2. spring.redis.host=127.0.0.1
  3. spring.redis.port=6379
  4. spring.redis.password=123456
  5. spring.redis.pool.max-idle=8
  6. spring.redis.pool.min-idle=0
  7. spring.redis.pool.max-active=8
  8. spring.redis.pool.max-wait=-1
  9. #超时一定要大于0
  10. spring.redis.timeout=3000
  11. spring.session.store-type=redis

在配置redis时需要确保redis安装正确,并且配置notify-keyspace-events Egx,spring.redis.timeout设置为大于0,我当时这里配置为0时springboot时启不起来。

三、编写登录状态拦截器RedisSessionInterceptor

  1. //拦截登录失效的请求
  2. public class RedisSessionInterceptor implements HandlerInterceptor
  3. {
  4. @Autowired
  5. private StringRedisTemplate redisTemplate;
  6. @Override
  7. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
  8. {
  9. //无论访问的地址是不是正确的,都进行登录验证,登录成功后的访问再进行分发,404的访问自然会进入到错误控制器中
  10. HttpSession session = request.getSession();
  11. if (session.getAttribute("loginUserId") != null)
  12. {
  13. try
  14. {
  15. //验证当前请求的session是否是已登录的session
  16. String loginSessionId = redisTemplate.opsForValue().get("loginUser:" + (long) session.getAttribute("loginUserId"));
  17. if (loginSessionId != null && loginSessionId.equals(session.getId()))
  18. {
  19. return true;
  20. }
  21. }
  22. catch (Exception e)
  23. {
  24. e.printStackTrace();
  25. }
  26. }
  27. response401(response);
  28. return false;
  29. }
  30. private void response401(HttpServletResponse response)
  31. {
  32. response.setCharacterEncoding("UTF-8");
  33. response.setContentType("application/json; charset=utf-8");
  34. try
  35. {
  36. response.getWriter().print(JSON.toJSONString(new ReturnData(StatusCode.NEED_LOGIN, "", "用户未登录!")));
  37. }
  38. catch (IOException e)
  39. {
  40. e.printStackTrace();
  41. }
  42. }
  43. @Override
  44. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception
  45. {
  46. }
  47. @Override
  48. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception
  49. {
  50. }
  51. }

四、配置拦截器

  1. @Configuration
  2. public class WebSecurityConfig extends WebMvcConfigurerAdapter
  3. {
  4. @Bean
  5. public RedisSessionInterceptor getSessionInterceptor()
  6. {
  7. return new RedisSessionInterceptor();
  8. }
  9. @Override
  10. public void addInterceptors(InterceptorRegistry registry)
  11. {
  12. //所有已api开头的访问都要进入RedisSessionInterceptor拦截器进行登录验证,并排除login接口(全路径)。必须写成链式,分别设置的话会创建多个拦截器。
  13. //必须写成getSessionInterceptor(),否则SessionInterceptor中的@Autowired会无效
  14. registry.addInterceptor(getSessionInterceptor()).addPathPatterns("/api/**").excludePathPatterns("/api/user/login");
  15. super.addInterceptors(registry);
  16. }
  17. }

五、登录控制器

  1. @RestController
  2. @RequestMapping(value = "/api/user")
  3. public class LoginController
  4. {
  5. @Autowired
  6. private UserService userService;
  7. @Autowired
  8. private StringRedisTemplate redisTemplate;
  9. @RequestMapping("/login")
  10. public ReturnData login(HttpServletRequest request, String account, String password)
  11. {
  12. User user = userService.findUserByAccountAndPassword(account, password);
  13. if (user != null)
  14. {
  15. HttpSession session = request.getSession();
  16. session.setAttribute("loginUserId", user.getUserId());
  17. redisTemplate.opsForValue().set("loginUser:" + user.getUserId(), session.getId());
  18. return new ReturnData(StatusCode.REQUEST_SUCCESS, user, "登录成功!");
  19. }
  20. else
  21. {
  22. throw new MyException(StatusCode.ACCOUNT_OR_PASSWORD_ERROR, "账户名或密码错误!");
  23. }
  24. }
  25. @RequestMapping(value = "/getUserInfo")
  26. public ReturnData get(long userId)
  27. {
  28. User user = userService.findUserByUserId(userId);
  29. if (user != null)
  30. {
  31. return new ReturnData(StatusCode.REQUEST_SUCCESS, user, "查询成功!");
  32. }
  33. else
  34. {
  35. throw new MyException(StatusCode.USER_NOT_EXIST, "用户不存在!");
  36. }
  37. }
  38. }

六、效果

我在浏览器上登录,然后获取用户信息,再在postman上登录相同的账号,浏览器再获取用户信息,就会提示401错误了,浏览器需要重新登录才能获取得到用户信息,同样,postman上登录的账号就失效了。

浏览器:

postman:

七、核心原理详解

分布式session需要解决两个难点:1、正确配置redis让springboot把session托管到redis服务器。2、唯一登录。

1、redis:

redis需要能正确启动到出现如下效果才证明redis正常配置并启动

同时还要保证配置正确

  1. @EnableCaching
  2. @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 30)//session过期时间(秒)
  3. @Configuration
  4. public class RedisSessionConfig
  5. {
  6. @Bean
  7. public static ConfigureRedisAction configureRedisAction()
  8. {
  9. //让springSession不再执行config命令
  10. return ConfigureRedisAction.NO_OP;
  11. }
  12. }

springboot启动后能在redis上查到缓存的session才能说明整个redis+springboot配置成功!

2、唯一登录:

1、用户登录时,在redis中记录该userId对应的sessionId,并将userId保存到session中。

  1. HttpSession session = request.getSession();
  2. session.setAttribute("loginUserId", user.getUserId());
  3. redisTemplate.opsForValue().set("loginUser:" + user.getUserId(), session.getId());

2、访问接口时,会在RedisSessionInterceptor拦截器中的preHandle()中捕获,然后根据该请求发起者的session中保存的userId去redis查当前已登录的sessionId,若查到的sessionId与访问者的sessionId相等,那么说明请求合法,放行。否则抛出401异常给全局异常捕获器去返回给客户端401状态。

唯一登录经过我的验证后满足需求,暂时没有出现问题,也希望大家能看看有没有问题,有的话给我点好的建议!

原文地址:https://www.cnblogs.com/duende99/p/13413265.html