不使用Spring Security时如何全局存储已登录用户?(Spring Boot)
嘿,这个需求其实挺常见的,咱们可以用Spring里的ThreadLocal来实现类似SecurityContextHolder的全局用户信息存储,完美适配你用拦截器的场景。下面给你具体的实现思路和代码示例:
核心思路:用ThreadLocal实现线程级用户上下文
ThreadLocal是Java提供的线程本地存储工具,每个请求在Spring Boot里都是由独立线程处理的,用它来存当前请求的用户信息,既能实现“全局调用”的效果,又能保证线程安全。
1. 自定义用户上下文工具类
先写一个类似SecurityContextHolder的工具类,用ThreadLocal维护用户信息:
public class UserContextHolder { // 这里假设你的用户实体类是User,可根据实际情况替换 private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>(); // 设置当前请求的用户信息,在拦截器验证通过后调用 public static void setUser(User user) { userThreadLocal.set(user); } // 全局获取当前用户,业务代码直接调用 public static User getUser() { return userThreadLocal.get(); } // 清理ThreadLocal,必须在请求结束时调用,防止内存泄漏 public static void clear() { userThreadLocal.remove(); } }
2. 在拦截器中完成验证并注入用户上下文
假设你的拦截器负责解析Token、验证用户身份,在验证通过后把用户信息存入上下文:
@Component public class AuthInterceptor implements HandlerInterceptor { @Autowired private UserService userService; // 假设你有查询用户的服务类 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 从请求头获取Token(示例用Authorization Bearer模式) String token = request.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } token = token.substring(7); // 去掉"Bearer "前缀 // 2. 验证Token并查询用户 User user = userService.validateTokenAndGetUser(token); if (user == null) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } // 3. 将用户信息存入上下文 UserContextHolder.setUser(user); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 请求结束后清理上下文,避免线程池复用导致的信息污染和内存泄漏 UserContextHolder.clear(); } }
3. 注册拦截器到Spring Boot
把拦截器加入Spring的拦截器链,配置需要拦截的路径:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private AuthInterceptor authInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns("/api/**") // 拦截所有api路径 .excludePathPatterns("/api/login", "/api/register"); // 排除无需验证的路径 } }
4. 在业务代码中全局获取用户信息
现在你可以在Controller、Service甚至Mapper里直接调用UserContextHolder.getUser(),不用再手动传递用户信息:
@RestController @RequestMapping("/api/user") public class UserController { @GetMapping("/profile") public ResponseEntity<UserProfile> getCurrentUserProfile() { // 直接从上下文获取当前用户 User currentUser = UserContextHolder.getUser(); UserProfile profile = new UserProfile(currentUser.getId(), currentUser.getUsername(), currentUser.getEmail()); return ResponseEntity.ok(profile); } }
关键注意事项
- 内存泄漏问题:务必在
afterCompletion方法里调用clear(),因为Tomcat等容器会复用线程,不清理ThreadLocal会导致旧用户信息被后续请求获取,还可能引发内存泄漏。 - 异步场景适配:如果你的业务用到
@Async异步方法,ThreadLocal的信息不会自动传递到异步线程,这时候需要手动传递用户信息,或者结合Spring的RequestContextHolder处理(同步场景可忽略)。
这样一套实现下来,就完全满足了你想要的“全局对象存储用户信息”的需求,而且完全基于Spring Boot的拦截器体系,不需要依赖Spring Security。
内容的提问来源于stack exchange,提问作者user2137817
相关产品推荐
相关产品推荐

