配置Spring Security多重叠AuthenticationProvider遇认证问题求助
解决Spring Security多认证流程冲突问题
针对请求头携带两种不同token(webAuth/hardwareAuth)的多认证场景,现有配置存在认证提供者交叉执行、流程触发逻辑混乱的问题,可通过以下步骤修复:
1. 定义专属Authentication Token类
为两种认证流程创建独立的Token类型,避免不同Provider处理同一Token导致的冲突:
// Web认证专属Token public class WebAuthToken extends AbstractAuthenticationToken { private final String token; public WebAuthToken(String token) { super(null); this.token = token; setAuthenticated(false); } @Override public Object getCredentials() { return token; } @Override public Object getPrincipal() { return null; } } // 硬件认证专属Token public class HardwareAuthToken extends AbstractAuthenticationToken { private final String token; public HardwareAuthToken(String token) { super(null); this.token = token; setAuthenticated(false); } @Override public Object getCredentials() { return token; } @Override public Object getPrincipal() { return null; } }
2. 修改AuthenticationProvider的支持逻辑
让每个Provider仅处理对应类型的Token,杜绝交叉执行:
硬件认证Provider调整
@Component public class HardwareAuthenticationProvider implements AuthenticationProvider { private final HardwareTokenService hardwareTokenService; // 构造注入依赖 public HardwareAuthenticationProvider(HardwareTokenService hardwareTokenService) { this.hardwareTokenService = hardwareTokenService; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { HardwareAuthToken authToken = (HardwareAuthToken) authentication; String vToken = authToken.getCredentials().toString(); if (hardwareTokenService.auth(vToken)) { UserDetails userDetails = new User("hw", "", new ArrayList<>()); // 返回已认证的Token return new UsernamePasswordAuthenticationToken(userDetails, vToken, userDetails.getAuthorities()); } else { throw new BadCredentialsException("Invalid v-token"); } } @Override public boolean supports(Class<?> authentication) { // 仅支持HardwareAuthToken return HardwareAuthToken.class.isAssignableFrom(authentication); } }
Web认证Provider调整
@RequiredArgsConstructor @Component public class TokenAuthenticationProvider implements AuthenticationProvider { private final AuthenticationService authenticationService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { WebAuthToken authToken = (WebAuthToken) authentication; String token = authToken.getCredentials().toString(); UserDetails userDetails = Optional.ofNullable(token) .map(authenticationService::loadUserByToken) .orElseThrow(() -> new BadCredentialsException("Invalid authentication token=" + token)); return new UsernamePasswordAuthenticationToken(userDetails, token, userDetails.getAuthorities()); } @Override public boolean supports(Class<?> authentication) { // 仅支持WebAuthToken return WebAuthToken.class.isAssignableFrom(authentication); } }
注:将原继承AbstractUserDetailsAuthenticationProvider改为直接实现AuthenticationProvider,逻辑更简洁,避免不必要的适配。
3. 调整ProcessingFilter的触发逻辑
每个Filter仅在请求头存在对应token时才启动认证流程,无对应头则直接跳过;同时检查SecurityContext,避免重复认证:
Web认证Filter
@Component public class WebAuthenticationFilter extends OncePerRequestFilter { private final AuthenticationManager authenticationManager; public WebAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String webToken = request.getHeader("webAuth"); // 仅当存在webAuth头且未认证时执行 if (webToken != null && !webToken.isBlank() && SecurityContextHolder.getContext().getAuthentication() == null) { Authentication authToken = new WebAuthToken(webToken); Authentication authenticated = authenticationManager.authenticate(authToken); SecurityContextHolder.getContext().setAuthentication(authenticated); } filterChain.doFilter(request, response); } }
硬件认证Filter
@Component public class HardwareAuthenticationFilter extends OncePerRequestFilter { private final AuthenticationManager authenticationManager; public HardwareAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String hardwareToken = request.getHeader("hardwareAuth"); // 仅当存在hardwareAuth头且未认证时执行 if (hardwareToken != null && !hardwareToken.isBlank() && SecurityContextHolder.getContext().getAuthentication() == null) { Authentication authToken = new HardwareAuthToken(hardwareToken); Authentication authenticated = authenticationManager.authenticate(authToken); SecurityContextHolder.getContext().setAuthentication(authenticated); } filterChain.doFilter(request, response); } }
4. 更新HttpSecurity配置
保持原有配置结构,无需额外调整,确保Provider和Filter正确注入即可:
@Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.cors() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .exceptionHandling() .defaultAuthenticationEntryPointFor(unauthorizedEntryPoint(), PROTECTED_URLS) .and() .authenticationProvider(webAuthenticationProvider) .authenticationProvider(hardwareAuthenticationProvider) .addFilterBefore(webAuthenticationFilter(), AnonymousAuthenticationFilter.class) .addFilterBefore(hardwareAuthenticationFilter(), AnonymousAuthenticationFilter.class) .authorizeRequests() .antMatchers(ANT_MATCHES).permitAll() .anyRequest().authenticated() .and() .csrf().disable() .formLogin().disable() .httpBasic().disable(); }
最终效果
- 携带
webAuth头的请求:仅Web认证流程执行,成功则通过,失败返回401。 - 携带
hardwareAuth头的请求:仅硬件认证流程执行,成功则通过,失败返回401。 - 不会出现认证成功后后续Provider执行失败的情况,也不会出现第一个Provider失败后第二个不执行的问题。
内容的提问来源于stack exchange,提问作者Kaiak
相关产品推荐
相关产品推荐

