SpringBoot登录方案和session失效、信息更新问题
本文探讨了单机服务中登录功能的常见问题及解决方案。主要问题包括Session过期和用户信息更新不及时。通过引入Token机制和拦截器,解决了Session过期问题,确保用户关闭浏览器前保持登录状态。对于用户信息更新问题,建议在Session中仅存储用户ID,每次请求时查询最新用户信息,确保状态实时更新。此外,还讨论了X天免登录功能的实现思路。
文章目录
前言
登录功能可以说是老生常谈了,相信这是每个后端必备的基本功,从最基本的session存储登录信息,到分布式环境下的JWT、redis session,每个方案各有优缺点,即使是单机服务最常用的session存储也有一些不足,本文就从单机服务的登录功能中分析痛点,提出我个人的解决方案。
Session的不足
登录功能最基础的做法就是在验证用户密码之后,将查询到的用户信息都储存在session中,这么做会带来以下问题:
- session有过期时间(tomcat默认是30分钟),用户挂着页面闲置一段时间后就会退出登录状态,影响用户体验
- 用户信息储存在session中,用户信息被他人更改后无法及时更新session中的信息(如管理员封禁用户)
解决思路
Session过期问题
对于session过期的问题,我们可以通过在Cookie中添加额外的token来解决。

Talk is cheap, show me the code.
@PostMapping("/token")public R<?> login(@RequestBody LoginBody loginBody, HttpSession session, HttpServletResponse response) { // 省略:验证用户登录信息
// 随机生成token String token = UUID.randomUUID().toString();
// 省略:将token和用户id储存到redis或其他缓存中
// 构建cookie ResponseCookie.from("Authorization", token) .httpOnly(true) .path("/") .build();
// 将cookie添加到响应头中 response.addHeader("Set-Cookie", token); return R.success("登录成功");}然后我们定义一个拦截器,当用户请求API时,对其进行检查,如果没有用户信息则尝试使用token登录。

public class DynamicAuthInterceptor implements HandlerInterceptor { private UserService userService;
public DynamicAuthInterceptor(UserService userService) { this.userService = userService; }
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { HttpSession session = request.getSession();
// 若session中无用户信息则需动态登录 if (session.getAttribute(AppConstants.SESSION_USER_KEY) == null && request.getCookies() != null) { for (var cookie : request.getCookies()) { if (cookie.getName().equals("Authorization")) { login(request, response, cookie.getValue()); // 进行动态登录 } } }
return true; }
// 动态登录方法 private void login(HttpServletRequest request, HttpServletResponse response, String token) { // 省略:从redis或其他缓存中查找token及用户id String uid = RedisUtils.getValue(token);
// 根据用户id判断用户是否存在,获取用户信息 if (uid != null) { User user = userService.selectById(uid); if (user == null) { throw new RuntimeException("用户不存在"); }
// 设置登录状态 request.getSession().setAttribute("user", user);
// 省略:重新生成token并添加到缓存中、cookie中 } }}然后将拦截器添加到SpringMVC中。
@Configurationpublic class WebMvcConfig implements WebMvcConfigurer { @Autowired private UserService userService;
@Override public void addInterceptors(InterceptorRegistry registry) { // 添加动态登录拦截器 registry.addInterceptor(new DynamicAuthInterceptor(userService)).addPathPatterns("/**"); }}这样,请求进入controller前,拦截器就会尝试获取token并登录,保证了用户关闭浏览器前一直保持登录状态。
用户信息更新问题
试想这么一个场景:
某论坛,管理员将一名乱发广告的用户封禁之后,由于用户还处于登录状态,从session获取到的用户信息是封禁前的,于是用户依旧大摇大摆发着广告❌
于是开发人员着手解决这个问题,想要在封禁时顺便清除该用户的session?恐怕不太可行,不如在登录时session中只储存用户的id,用户访问需要鉴权的API时,由后端查询用户的信息,这样就能实时判断用户的状态。于是画面变成了:
某论坛,管理员将一名乱发广告的用户封禁之后,由于用户还处于登录状态,用户尝试发广告,后端查询到用户被封禁,于是禁止发送,世界清静了✅
这个方案并不是完美的,每次请求API需要多查询一次用户信息,对性能比较敏感的场景可以加上缓存。代码实现比较简单,这里就不给出了。
X天免登录功能
这是一个功能需求,实现思路其实和第一个问题大差不差,主要就是token存活时长不同的区别,以及在刷新token时需要判断token属于X天免登录还是属于临时免登录(用于设置新token的生命周期),我一开始打算通过cookie过期时间来判断,可是cookie的过期时间信息并不会发送到后端,只好从其他方面入手。
最后我选择在生成token时为token加上特殊前缀来判断,如temp表示这是一个临时token,随着浏览器关闭而删除。
后话
由于是第一次写后端相关的教程,在语言组织、文章结构等方面不太熟悉,还请大家见谅,对于文章/代码有疑惑的地方可以在评论区留言,或者在github上查看完整的拦截器代码:BlackHoleStation/DynamicAuthInterceptor.java
下一篇文章应该是写基于Spring AOP的鉴权实现了,对于想要进行简单有效的角色鉴权又不想引入spring security、shiro等框架的人来说值得一看!