Spring Boot自定义Token认证问题:AuthenticationProvider未触发
Spring Boot自定义Token认证问题:AuthenticationProvider未触发排查
嘿,我看了你的代码,问题出在你的认证流程根本没走到AuthenticationProvider那一步,下面给你拆解原因和解决办法:
为什么AuthenticationProvider没启动?
你现在的逻辑是:
/login请求直接permitAll放行,完全没触发Spring Security的认证流程;- 其他请求靠
CredentialsFilter直接从Token里解析信息,手动设置SecurityContext,全程没用到AuthenticationManager——而AuthenticationProvider是被AuthenticationManager调用的,没有这个触发动作,它当然不会跑起来。
简单说:你把AuthenticationProvider定义好了,但从来没调用过它。
正确的流程应该是这样的
- 用户调用
/login接口,传入用户名密码; - 后端调用
AuthenticationManager.authenticate(),触发BokiAuthenticationProvider做用户名密码校验; - 校验通过后,生成Token返回给前端;
- 后续请求前端带Token,
CredentialsFilter解析Token并设置认证信息到SecurityContext。
具体修改步骤
1. 先在SecurityConfig里暴露AuthenticationManager为Bean
默认情况下AuthenticationManager不能直接注入,需要在MyWebSecurityConfigurerAdapter里添加:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled=true) public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { // ... 你的其他代码 ... // 暴露AuthenticationManager为Bean,方便在Controller/Service中注入调用 @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
2. 编写Login接口,触发AuthenticationProvider
新增一个LoginController(或者在现有Controller里加),主动调用认证逻辑:
@RestController public class LoginController { @Autowired private AuthenticationManager authenticationManager; @Autowired private MyCriptoService myCriptoService; @PostMapping("/login") public ResponseEntity<String> login(@RequestBody UserLoginRequest loginRequest) { // 构造用户名密码的认证请求 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()); try { // 这一步会触发BokiAuthenticationProvider的authenticate方法! Authentication authenticated = authenticationManager.authenticate(authRequest); // 生成Token(需要你在MyCriptoService里实现generateToken方法,把用户名、权限存进去) String token = myCriptoService.generateToken(authenticated); return ResponseEntity.ok(token); } catch (AuthenticationException e) { // 认证失败返回错误 return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage()); } } // 定义请求参数的DTO static class UserLoginRequest { private String username; private String password; // 省略getter/setter } }
3. 修正CredentialsFilter的逻辑
你的过滤器里有几个小问题,比如判断URI用contains可能会匹配到其他带login的路径,还有Token解析失败的情况没处理,修改后:
@Component public class CredentialsFilter extends OncePerRequestFilter{ @Autowired private MyCriptoService myCriptoService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 精确匹配/login请求,直接放行 if ("/login".equals(request.getRequestURI())) { chain.doFilter(request, response); return; } String token = request.getHeader("MyTokenHeader"); // 只有Token存在且当前没有认证信息时,才处理 if (token != null && SecurityContextHolder.getContext().getAuthentication() == null) { try { String username = myCriptoService.getUsernameFromToken(token); List<GrantedAuthority> authorities = myCriptoService.getAuthoritiesFromToken(token); // 构造已认证的Authentication对象,密码设为null(因为是Token认证,不需要密码) UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, authorities); // 设置到SecurityContext,后续接口就能拿到认证信息了 SecurityContextHolder.getContext().setAuthentication(authentication); } catch (Exception e) { // Token无效,返回401 response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("Invalid or expired token"); return; } } chain.doFilter(request, response); } }
4. 确保MyCriptoService的Token生成/解析逻辑正确
你需要在MyCriptoService里实现generateToken方法,把认证后的用户名和权限信息加密到Token里,比如用JWT的话,就把这些信息作为Claim存进去,这样getUsernameFromToken和getAuthoritiesFromToken才能正确解析出来。
最后再验证一下
启动项目后,先调用/login接口传入正确的用户名密码,这时候你应该能看到BokiAuthenticationProvider的authenticate方法被触发;拿到Token后,带着MyTokenHeader请求其他需要权限的接口,CredentialsFilter会解析Token并设置认证信息,接口就能正常访问了。
内容的提问来源于stack exchange,提问作者Lazaruss
相关产品推荐
相关产品推荐

