You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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:

  1. 修改自定义JWT生成逻辑,将Token前缀设为CustomBearer (注意末尾空格)。
  2. 更新AuthorizationFilter的hasAuthorizationToken方法,只识别该前缀:
private boolean hasAuthorizationToken(HttpServletRequest request) {
    String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
    return authHeader != null && authHeader.startsWith("CustomBearer ");
}
  1. 保持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,由框架自动选择合适的认证方式:

  1. 实现自定义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);
    }
}
  1. 更新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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 01:57:39