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

Spring 3 OAuth2ResourceServer JWT认证出现"Insufficient scope"错误的问题排查求助

Spring 3 OAuth2ResourceServer JWT认证出现"Insufficient scope"错误的问题排查求助

各位大佬好,我最近在基于Spring 3搭建JWT认证服务,用OAuth2ResourceServer做资源服务器校验,但遇到了个头疼的问题:

我配置了/credit/request端点需要SCOPE_usr权限才能访问,生成的JWT Token里也确实包含scope claim且值为SCOPE_usr,但请求这个端点时始终返回403错误,提示"insufficient scope"。更奇怪的是,如果我在JwtTokenService里把scope硬编码成"SCOPE_usr",所有Token居然就能通过校验。

我现在有点摸不清头绪,想请教大家:OAuth2ResourceServer到底是怎么校验scope的?我该从哪些方向入手排查和修复这个问题?

先贴一下我的核心代码:

Spring Security配置类

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
public class SpringSecurityConfig {

    private final AuthenticationService authenticationService;
    private final PasswordEncoder passwordEncoder;
    private final RsaProperties rsaKeys;

    @Bean
    public AuthenticationManager authManager() {
        var authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(authenticationService);
        authProvider.setPasswordEncoder(passwordEncoder);
        return new ProviderManager(authProvider);
    }

    @Bean
    public JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder(rsaKeys.publicKey()).privateKey(rsaKeys.privateKey()).build();
        JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwkSource);
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(rsaKeys.publicKey()).build();
    }

    @Bean
    public JwtTokenService tokenService() {
        return new JwtTokenService(jwtEncoder());
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .csrf().disable()
                .authorizeHttpRequests()
                .requestMatchers("/authentication/login").permitAll()
                .requestMatchers("/credit/request").hasAuthority("SCOPE_usr")
                .and()
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer :: jwt )
                .build();
    }
}

JWT Token生成服务类

@Component
@RequiredArgsConstructor
public class JwtTokenService {

    private final JwtEncoder jwtEncoder;

    public String generateAccessToken(UserDetailsEntity userDetails) {
        Instant now = Instant.now();
        String scope = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(" "));
        JwtClaimsSet claims = JwtClaimsSet.builder()
                .issuer("self")
                .issuedAt(now)
                .expiresAt(now.plus(2, ChronoUnit.HOURS))
                .subject(userDetails.getUsername())
                .claim("scope", scope)
                .build();
        return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
    }

    public String generateRefreshToken(UserDetailsEntity userDetails) {
        Instant now = Instant.now();
        String scope = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(" "));
        JwtClaimsSet claims = JwtClaimsSet.builder()
                .issuer("self")
                .issuedAt(now)
                .expiresAt(now.plus(10, ChronoUnit.HOURS))
                .subject(userDetails.getUsername())
                .claim("scope", scope)
                .build();
        return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
    }

    public String parseToken(String token) {
        try {
            SignedJWT decodedJwt = SignedJWT.parse(token);
            return decodedJwt.getJWTClaimsSet().getSubject();
        } catch (ParseException e) {
            System.out.println(e.getStackTrace());
        }
        return null;
    }
}

我自己的猜测和尝试:

我怀疑是不是Spring解析scope的方式和我理解的不一样?比如它会给scope的值自动加前缀?但硬编码成SCOPE_usr又能通过,这让我完全搞不懂逻辑了。希望有经验的朋友能给我指条明路,谢谢大家!

备注:内容来源于stack exchange,提问作者iia.m.y

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 11:53:05