为何找不到UsernamePasswordAuthenticationToken对应的AuthenticationProvider?
问题分析与解决方案
错误根源
登录接口/api/auth/login触发了UsernamePasswordAuthenticationToken类型的认证请求,但你的自定义AuthenticationManager仅注册了JwtAuthenticationProvider——该Provider仅支持JwtAuthenticationToken类型,无法处理用户名密码认证的Token,因此抛出ProviderNotFoundException。
同时你的JwtAuthenticationFilter匹配路径为/login,与实际登录接口/api/auth/login不匹配,导致该Filter不会拦截登录请求(它的作用本应是拦截其他需要JWT校验的接口)。
修复步骤
1. 添加用户名密码认证Provider
创建支持UsernamePasswordAuthenticationToken的Provider,这里使用Spring Security内置的DaoAuthenticationProvider:
@Bean public AuthenticationProvider usernamePasswordAuthenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(byIdUserDetailsService); // 注入你的自定义UserDetailsService provider.setPasswordEncoder(passwordEncoder()); // 需提前配置PasswordEncoder Bean return provider; }
2. 更新AuthenticationManager,包含两类Provider
修改authenticationManagerBean方法,同时注册用户名密码和JWT的认证Provider:
@Bean public AuthenticationManager authenticationManagerBean() { return new ProviderManager( List.of( usernamePasswordAuthenticationProvider(), new JwtAuthenticationProvider(jwtTokenProvider, byIdUserDetailsService) ) ); }
3. 调整JwtAuthenticationFilter的拦截路径
让JWT过滤器拦截除公开接口外的所有需要认证的接口,而非登录接口:
public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter { // 排除公开接口,拦截其他/api路径 private static final RequestMatcher DEFAULT_REQUEST_MATCHER = new NegatedRequestMatcher( new OrRequestMatcher( new AntPathRequestMatcher("/api/auth/**"), new AntPathRequestMatcher("/api/public/**"), new AntPathRequestMatcher("/error") ) ); public JwtAuthenticationFilter(AuthenticationManager authenticationManager) { super(DEFAULT_REQUEST_MATCHER, authenticationManager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { return this.getAuthenticationManager().authenticate( new JwtAuthenticationToken(getJwtFromRequest(request)) ); } private String getJwtFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }
4. 确保SecurityFilterChain正确传入AuthenticationManager
在配置中注入AuthenticationManager并传递给JwtAuthenticationFilter:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception { http.csrf(AbstractHttpConfigurer::disable); http.cors(AbstractHttpConfigurer::disable); http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); http.exceptionHandling(exception -> exception .authenticationEntryPoint(unauthorizedHandler)); // 传入AuthenticationManager到Filter http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class); http.authorizeHttpRequests(authorize -> authorize .requestMatchers(HttpMethod.OPTIONS).permitAll() .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/error").permitAll() .requestMatchers("/api/public/**").permitAll() .anyRequest().authenticated()); return http.build(); }
5. 验证AuthController的认证逻辑
确保登录接口中使用UsernamePasswordAuthenticationToken进行认证:
@PostMapping("/api/auth/login") public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginRequest.getUsername(), loginRequest.getPassword() ) ); SecurityContextHolder.getContext().setAuthentication(authentication); String jwt = jwtTokenProvider.generateToken(authentication); return ResponseEntity.ok(new JwtResponse(jwt)); }
内容的提问来源于stack exchange,提问作者maybesomename
相关产品推荐
相关产品推荐

