Spring Security 5.x 整合 JWT 登录流程:手动登录模式 vs 自动拦截模式

📅 2026/6/27 8:05:04
Spring Security 5.x 整合 JWT 登录流程:手动登录模式 vs 自动拦截模式
目录① 导读卡片② 背景与目标为什么你该看懂这个学完你能干什么③ 概念与原理3.1 什么是 Spring Security 的认证流程3.2 JWT 是什么为什么需要它3.3 注册过滤器的两种关键位置④ 逻辑与对比两种登录模式模式一自动拦截登录过滤器版模式二手动登录Controller 版⭐ 当前主流⑤ 核心详解5.1 配置放行登录接口5.2 Controller手动触发认证5.3 JWT 过滤器处理非登录请求5.4 两种模式的终极对比⑥ 案例实战场景完整的手动登录 JWT 鉴权⑦ 避坑 最佳实践⚠️ 避坑 1忘记注册 AuthenticationManager Bean⚠️ 避坑 2JWT 过滤器中忘记判断上下文是否已有认证⚠️ 避坑 3JWT 过滤器异常无法被全局 RestControllerAdvice 捕获✅ 最佳实践清单⑧ 总结 路线图一句话记住知识地图下一步推荐阅读① 导读卡片项目内容一句话定位彻底搞懂 Spring Security 5.x 整合 JWT 时「手动登录」与「自动拦截登录」两种模式的完整流程与核心区别适合人群正在使用 Spring Boot Security JWT 做项目但对登录认证流程感到困惑的 Java 后端开发者难度⭐⭐⭐需要了解 Security 基础配置和过滤器概念阅读时长10 分钟前置知识Spring Boot 基础、Spring Security 基本概念、JWT 基础认知② 背景与目标为什么你该看懂这个很多人在项目中用 Spring Security JWT 做登录认证但看到两种不同的写法就开始迷糊有的项目登录请求直接被 Security 过滤器拦截你连 Controller 都进不去有的项目登录请求能进到 Controller然后在方法里手动调用认证这两种写法到底哪个对核心区别在哪学完你能干什么一眼看穿项目中的登录模式是「自动拦截」还是「手动登录」在手动登录模式中能清晰说出从请求到响应的完整数据流向手写 Security 配置时知道怎么放行登录接口、怎么配过滤器顺序③ 概念与原理3.1 什么是 Spring Security 的认证流程Spring Security 本质上是一个过滤器链Filter Chain。请求到达后端后会依次经过多个过滤器最后到达你的 Controller。对于登录认证Security 提供了一个内置过滤器UsernamePasswordAuthenticationFilter。它的工作很简单从请求中提取username和password封装成「未认证」的UsernamePasswordAuthenticationToken交给AuthenticationManager去查库验密认证成功 → 生成「已认证」的Authentication对象放入SecurityContextHolder认证失败 → 触发AuthenticationEntryPoint3.2 JWT 是什么为什么需要它JWTJSON Web Token是一种无状态认证令牌。服务器验证完用户身份后生成一个加密的 Token 返回给前端。前端后续每次请求都带上这个 Token服务器不再依赖 Session每次请求独立验证 Token 即可。关键点JWT 本身只是一个字符串Security 不认识它。你需要写一个自定义过滤器JwtAuthenticationFilter把 Token 解析后「翻译」成 Security 能理解的Authentication对象。3.3 注册过滤器的两种关键位置// 写法 A放在 UsernamePasswordAuthenticationFilter 之前 http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); ​ // 写法 B其他位置 http.addFilterBefore(jwtFilter, FilterSecurityInterceptor.class);不同的注册位置直接影响异常能否被正确处理这一点我们在后文详细展开。④ 逻辑与对比两种登录模式模式一自动拦截登录过滤器版流程图请求 /login → UsernamePasswordAuthenticationFilter 拦截 ↓ AuthenticationManager 认证查库验密 ↓ 认证成功/失败 → 返回结果 ↓ Controller 根本进不去特点登录由 Security 过滤器自动完成开发者不需要写登录接口的 Controller 代码灵活度低返回格式不自由模式二手动登录Controller 版⭐ 当前主流流程图请求 /login → Security 看到 permitAll → 放行 ↓ 进入 LoginController ↓ 手动调用 authenticationManager.authenticate() ↓ 认证成功 → 生成 JWT → 返回特点登录接口直接放行不走 Security 登录过滤器在 Controller 里手动触发认证开发者完全控制登录逻辑、参数校验、返回格式目前行业最主流的用法⑤ 核心详解5.1 配置放行登录接口Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { ​ Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() // 关键登录接口直接放行 .antMatchers(/login).permitAll() .anyRequest().authenticated() .and() // JWT 过滤器放在 UsernamePasswordAuthenticationFilter 之前 .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } }配置的核心意图/login→放行不拦截其他请求 →必须认证。5.2 Controller手动触发认证RestController public class LoginController { ​ Autowired private AuthenticationManager authenticationManager; ​ Autowired private JwtUtil jwtUtil; ​ PostMapping(/login) public R login(RequestBody LoginRequest loginRequest) { // 第 1 步手动触发 Security 认证 // 这个方法会调用 UserDetailsService.loadUserByUsername() 查库 // 然后比对密码失败直接抛 AuthenticationException Authentication authenticate authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginRequest.getUsername(), loginRequest.getPassword() ) ); ​ // 第 2 步认证成功从这个对象中取用户信息 LoginUser loginUser (LoginUser) authenticate.getPrincipal(); String userId loginUser.getUser().getId(); ​ // 第 3 步生成 JWT MapString, Object claims new HashMap(); claims.put(userId, userId); claims.put(username, loginUser.getUsername()); String token jwtUtil.createToken(claims); ​ // 第 4 步返回 Token 给前端 return R.ok(token); } }这里最核心的一行代码就是authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));翻译成人话Security我不让你过滤器自动处理了我手动把账号密码交给你你帮我查库比对对了返回已认证对象错了直接抛异常。5.3 JWT 过滤器处理非登录请求Component public class JwtAuthenticationFilter extends OncePerRequestFilter { ​ Autowired private JwtUtil jwtUtil; ​ Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 从请求头获取 Token String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } ​ // 放行登录请求没有 Token 的请求 if (token null || SecurityContextHolder.getContext().getAuthentication() ! null) { filterChain.doFilter(request, response); return; } ​ try { // 解析 Token Claims claims jwtUtil.parseJwt(token); String username claims.get(username, String.class); ListString authorities claims.get(perms, List.class); ​ // 封装成 Security 能识别的认证对象 UsernamePasswordAuthenticationToken authToken new UsernamePasswordAuthenticationToken( username, null, AuthorityUtils.commaSeparatedStringToAuthorityList( String.join(,, authorities) ) ); ​ // 放入 Security 上下文 SecurityContextHolder.getContext().setAuthentication(authToken); } catch (Exception e) { // Token 解析失败交给 Security 异常处理器 SecurityContextHolder.clearContext(); throw new BadCredentialsException(Token 无效或已过期); } ​ filterChain.doFilter(request, response); } }5.4 两种模式的终极对比维度自动拦截登录过滤器版手动登录Controller 版是否进 Controller❌ 不进✅进谁触发认证Security 内置过滤器你自己的代码登录接口是否放行不放行被拦截permitAll 放行灵活性低极高返回格式控制受限于 Security 配置完全自由适用场景纯表单登录的老项目RESTful API JWT 的现代项目⑥ 案例实战场景完整的手动登录 JWT 鉴权第 1 步SecurityConfig 配置Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { ​ Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; ​ Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(/login).permitAll() .antMatchers(/admin/**).hasRole(admin) .anyRequest().authenticated() .and() // JWT 过滤器先执行 .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // 异常处理 .exceptionHandling() .authenticationEntryPoint(jwtAuthenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler); } ​ Bean Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }第 2 步UserDetailsService 实现Service public class UserDetailsServiceImpl implements UserDetailsService { Autowired private UserMapper userMapper; Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user userMapper.selectByUsername(username); if (user null) { throw new UsernameNotFoundException(用户不存在); } // 返回 LoginUser实现了 UserDetails 的自定义类 return new LoginUser(user); } }第 3 步完整请求流程【登录请求】POST /login 前端 → {username: admin, password: 123456} ↓ Security 放行permitAll ↓ LoginController.login() ↓ authenticationManager.authenticate(...) ↓ UserDetailsService.loadUserByUsername(admin) ↓ 密码比对成功 → 生成 JWT → 返回 Token 【业务请求】GET /api/users 请求头: Authorization: Bearer eyJhbGci... ↓ JwtAuthenticationFilter 拦截 ↓ 解析 Token → 提取用户信息 → 塞入 SecurityContext ↓ FilterSecurityInterceptor 权限校验 ↓ 到达 Controller → 执行业务逻辑 ↓ 返回数据⑦ 避坑 最佳实践⚠️ 避坑 1忘记注册 AuthenticationManager Bean// 必须在 SecurityConfig 中添加 Bean Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }不写的话Autowired AuthenticationManager会报错因为你没有把它暴露为 Spring Bean。⚠️ 避坑 2JWT 过滤器中忘记判断上下文是否已有认证// 错误写法 - 每次都解析 Token if (token ! null) { doParseToken(token); // 即使已经认证过又重复解析 } // 正确写法 - 先检查上下文 if (token ! null SecurityContextHolder.getContext().getAuthentication() null) { doParseToken(token); // 只有未认证时才解析 }不判断的话已经被其他过滤器认证过的请求又会被你的 JWT 过滤器再处理一次可能导致认证信息被覆盖。⚠️ 避坑 3JWT 过滤器异常无法被全局RestControllerAdvice捕获JWT Filter 抛出的异常 →不进 Controller 层→ 全局RestControllerAdvice抓不到。解决方案在 SecurityConfig 中配置AuthenticationEntryPoint统一处理认证异常。✅ 最佳实践清单手动登录模式是企业级项目的主流选择直接学这个JWT 过滤器的addFilterBefore位置决定了异常能否被统一处理登录接口务必配置permitAll否则请求无法进入 Controller每次请求只解析一次 Token通过SecurityContextHolder.getAuthentication() null来判断JWT 过滤器中抛出的异常配AuthenticationEntryPoint统一处理⑧ 总结 路线图一句话记住手动登录模式 登录接口放行 → 进 Controller → 手动调authenticationManager.authenticate()→ 认证通过生成 JWT 返回这是目前 Spring Boot Security JWT 最标准、最主流的写法。知识地图手动登录模式本文 ↓ Security 过滤器链执行顺序 ↓ ExceptionTranslationFilter 异常捕获机制 ↓ AuthenticationException vs AccessDeniedException 两类异常 ↓ JwtAuthenticationFilter 逐行详解下一步推荐阅读✅Spring Security 过滤链流程— 搞清楚 7 大核心过滤器的执行顺序✅Spring Security 的两类异常— 分清 AuthenticationException 和 AccessDeniedException✅JwtAuthenticationFilter 详解— 逐行拆解 JWT 过滤器代码