如何在Spring Boot中同时处理OAuth2与自定义JWT认证?
原有Spring Boot应用使用自定义JWT做用户认证,添加OAuth2 Resource Server配置后,OAuth2请求正常,但自定义JWT请求报错:
An error occurred while attempting to decode the Jwt: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
相关配置及代码如下:
初始Security配置(自定义JWT)
public SecurityFilterChain customFilterChain(HttpSecurity http) throws Exception { return http .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeRequests(auth -> auth .anyRequest().authenticated() ) .userDetailsService(userService) .addFilter(new AuthenticationFilter(authenticationManager(), userService)) .addFilterBefore(new AuthorizationFilter(), UsernamePasswordAuthenticationFilter.class) .build(); }
自定义AuthorizationFilter
public class AuthorizationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (!hasAuthorizationToken(request)) { filterChain.doFilter(request, response); return; } String authorizationHeader = request.getHeader(AUTHORIZATION); try { UsernamePasswordAuthenticationToken authToken = JwtTokenUtil.decodeJwtToken(authorizationHeader); SecurityContextHolder.getContext().setAuthentication(authToken); } catch (Exception ex) { log.error("Authorization error: " + ex.getMessage()); response.sendError(HttpServletResponse.SC_FORBIDDEN); } filterChain.doFilter(request, response); } }
添加OAuth2后的Security配置
public SecurityFilterChain customFilterChain(HttpSecurity http) throws Exception { return http .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeRequests(auth -> auth .anyRequest().authenticated() ) .oauth2ResourceServer(oauth -> { oauth .jwt() .jwtAuthenticationConverter(oAuthJwtAuthenticationConverter); }) .userDetailsService(userService) .addFilter(new AuthenticationFilter(authenticationManager(), userService)) .addFilterBefore(new AuthorizationFilter(), UsernamePasswordAuthenticationFilter.class) .build(); }
报错本质是过滤器冲突:OAuth2 Resource Server的JwtAuthenticationFilter会尝试解析所有Bearer前缀的Token,而它使用的是OAuth2配置的密钥/签名算法,和自定义JWT的不匹配,导致解析失败。
推荐以下三种可行方案,按实现复杂度从低到高排序:
方案一:通过Token前缀区分(最简便)
给自定义JWT设置专属前缀(比如CustomBearer),和OAuth2默认的Bearer前缀区分开,让两个过滤器各自处理对应前缀的Token:
- 修改自定义JWT生成逻辑,将Token前缀设为
CustomBearer(注意末尾空格)。 - 更新
AuthorizationFilter的hasAuthorizationToken方法,只识别该前缀:
private boolean hasAuthorizationToken(HttpServletRequest request) { String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); return authHeader != null && authHeader.startsWith("CustomBearer "); }
- 保持OAuth2配置不变,它会自动处理
Bearer前缀的Token。
这样两种Token的处理逻辑完全隔离,不会互相干扰。
方案二:提前识别自定义JWT特征
如果不想修改Token前缀,可以通过解析JWT的Header部分(比如签名算法、issuer)判断是否为自定义JWT,只对自定义JWT执行自定义解析逻辑,其余交给OAuth2过滤器:
修改AuthorizationFilter的核心逻辑:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); boolean isCustomJwt = false; // 仅处理Bearer前缀的Token,先判断是否为自定义JWT if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); try { // 解析JWT Header,判断是否匹配自定义JWT的特征(比如算法为HS256) JwsHeader header = Jwks.parser().parseClaimsJws(token).getHeader(); isCustomJwt = "HS256".equals(header.getAlgorithm()); // 替换为你的自定义JWT算法 } catch (Exception e) { // 解析失败,不是自定义JWT,直接放行给OAuth2过滤器 filterChain.doFilter(request, response); return; } } if (!isCustomJwt) { filterChain.doFilter(request, response); return; } // 处理自定义JWT try { UsernamePasswordAuthenticationToken authToken = JwtTokenUtil.decodeJwtToken(authHeader); SecurityContextHolder.getContext().setAuthentication(authToken); } catch (Exception ex) { log.error("Authorization error: " + ex.getMessage()); response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } filterChain.doFilter(request, response); }
方案三:自定义AuthenticationProvider统一处理
将自定义JWT的认证逻辑封装为AuthenticationProvider,和OAuth2的JWT Provider一起交给Spring Security,由框架自动选择合适的认证方式:
- 实现自定义JWT的AuthenticationProvider:
@Component public class CustomJwtAuthenticationProvider implements AuthenticationProvider { private final JwtTokenUtil jwtTokenUtil; private final UserDetailsService userDetailsService; public CustomJwtAuthenticationProvider(JwtTokenUtil jwtTokenUtil, UserDetailsService userDetailsService) { this.jwtTokenUtil = jwtTokenUtil; this.userDetailsService = userDetailsService; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { BearerTokenAuthenticationToken bearerToken = (BearerTokenAuthenticationToken) authentication; String token = bearerToken.getToken(); // 解析自定义JWT UsernamePasswordAuthenticationToken authToken = jwtTokenUtil.decodeJwtToken("Bearer " + token); // 验证用户合法性 UserDetails userDetails = userDetailsService.loadUserByUsername(authToken.getName()); if (userDetails == null) { throw new BadCredentialsException("Invalid custom JWT"); } return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); } @Override public boolean supports(Class<?> authentication) { // 适配BearerToken类型 return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication); } }
- 更新Security配置,添加自定义Provider并移除原有的
AuthorizationFilter:
@Autowired private CustomJwtAuthenticationProvider customJwtAuthenticationProvider; public SecurityFilterChain customFilterChain(HttpSecurity http) throws Exception { return http .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeRequests(auth -> auth .anyRequest().authenticated() ) .oauth2ResourceServer(oauth -> { oauth .jwt() .jwtAuthenticationConverter(oAuthJwtAuthenticationConverter); }) .authenticationProvider(customJwtAuthenticationProvider) .userDetailsService(userService) .addFilter(new AuthenticationFilter(authenticationManager(), userService)) .build(); }
这种方式更符合Spring Security的设计规范,适合复杂的多认证场景。
内容的提问来源于stack exchange,提问作者Kamil

