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

SpringBoot集成AUTH0 Token认证返回403错误排查求助

问题

学习Auth0搭建Demo时,Spring Security集成Auth0 Token认证调用/api/private-scoped接口始终返回403错误。操作步骤如下:

  1. 在Auth0创建应用与API(包含read:message权限),创建用户并设置admin角色,为该角色分配API的read:message权限并关联用户,完成API对应用的授权。
  2. Spring Boot项目中配置了SecurityConfig、AudienceValidator,编写了API控制器。
  3. 通过client_credentials模式获取Token,调用http://localhost:8080/app/api/private-scoped接口返回403。

相关代码

SecurityConfig.java

@EnableWebSecurity
public class SecurityConfig {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class.getName());

@Value("${auth0.audience}")
private String audience;
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().csrf().disable();

    http.authorizeRequests()
            .antMatchers("/api/public/**").permitAll()
            .antMatchers("/api/private/**").authenticated()
            .mvcMatchers("/api/private-scoped").hasAuthority("SCOPE_read:message")
            .and()
            .cors()
            .and()
            .oauth2ResourceServer().jwt();

         return http.build();
}
@Bean
JwtDecoder jwtDecoder(){
    NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromIssuerLocation(issuer);

    OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
    OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
    OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
    jwtDecoder.setJwtValidator(withAudience);

    return jwtDecoder;
 }
}

AudienceValidator.java

public class AudienceValidator implements OAuth2TokenValidator<Jwt> {

private final String audience;

public AudienceValidator(String audience){
    this.audience = audience;
}

@Override
public OAuth2TokenValidatorResult validate(Jwt token) {
    OAuth2Error error = new OAuth2Error("invalid_token", "L'audience richiesto è mancante", null);

    if (token.getAudience().contains(audience)) {
        return OAuth2TokenValidatorResult.success();
    }

    return OAuth2TokenValidatorResult.failure(error);
 }
}

APIController.java

@RestController
@RequestMapping(path = "/api", produces = MediaType.APPLICATION_JSON_VALUE)
@CrossOrigin(origins = "*")
public class APIController {
@GetMapping(value = "/public")
public Message publicEndpoint() {
    return new Message("All good. You DO NOT need to be authenticated to call /api/public.");
}

@GetMapping(value = "/private")
public Message privateEndpoint() {
    return new Message("All good. You can see this because you are Authenticated.");
}

@GetMapping(value = "/private-scoped")
public Message privateScopedEndpoint() {
    return new Message("All good. You can see this because you are Authenticated with a Token granted the 'read:messages' scope");
 }
}

获取Token的请求

curl --location 'https://dev-tkjcpdzq3rhabjp1.us.auth0.com/oauth/token' \
--header 'content-type: application/json' \
--header 'Cookie: did=s%3Av0%3A602acbc0-731c-11ee-b98a-95a049e9e317.nODjWjq1tEeLZzXXtNkzFz6p88pwZ3hhvtROw5YLWu4; did_compat=s%3Av0%3A602acbc0-731c-11ee-b98a-95a049e9e317.nODjWjq1tEeLZzXXtNkzFz6p88pwZ3hhvtROw5YLWu4' \
--data '{"client_id":"FlzXFNGz3Hr9F54usq8D3Es9lDAVEueR","client_secret":"hZslREfUgifNU4vqIp36Qo3GttLTRWDCC9TsgC0fIgsyyJ27Iq63C-M3M7MUrXr4","audience":"http://myAPI/api","grant_type":"client_credentials"}'

调用接口的请求

curl --location 'http://localhost:8080/app/api/private-scoped' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Inl5R0V0MHJseDBjUFVRUXFEc3JXdyJ9.eyJpc3MiOiJodHRwczovL2Rldi10a2pjcGR6cTNyaGFianAxLnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJGbHpYRk5HejNIcjlGNTR1c3E4RDNFczlsREFWRXVlUkBjbGllbnRzIiwiYXVkIjoiaHR0cDovL215QVBJL2FwaSIsImlhdCI6MTY5ODIzOTcyMiwiZXhwIjoxNjk4MzI2MTIyLCJhenAiOiJGbHpYRk5HejNIcjlGNTR1c3E4RDNFczlsREFWRXVlUiIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.ljA4UqAuwdBZzEAxcnc8L7Nzt3HDeTQxEDnQKuPhOFEPaJ_LqDsjQ1SOcpz7_tYNuf7aa_5bX4TernyN5o4iFTkg1ECiv2_OnsdKS8u7kNr0rUB9cofL6xXtaxwl4-ofUxUq_uEBnu7NZaRC7TT6X6PzNGdWd_gPgIzAMRAZa99tsM2DGEPYAbRkLZ7cqGyoDivemJnqMH5LUon-bt0otBp64ta0O45LZ8IIDprHcQ9BusRW5v02evBBANN5LT6GKxo6Y0KrugL04ec2mCRvvz-FW318A9djBAE-Lo1gVPHaB4njsFcHdtcxesHe9ja-qbG2x1ccckPOgTrzIciEhw' \
--header 'Cookie: JSESSIONID=FA60C42C23DC34ECAD41D542EB480383'

问题分析与解决方案

核心问题1:client_credentials模式Token无用户权限

你用client_credentials授权模式获取的Token是应用级凭证,不会关联具体用户,因此Auth0不会把用户的admin角色和read:message权限注入到Token中。从你提供的Token内容可以看到,里面没有scope或permissions字段,Spring Security无法识别权限,直接返回403。

核心问题2:权限名称不一致

控制器提示需要的是read:messages(复数),但你在Auth0配置的权限是read:message(单数),同时SecurityConfig里校验的是SCOPE_read:message,名称不统一导致权限校验失败。

核心问题3:接口路径不匹配

SecurityConfig里配置的规则是针对/api/private-scoped,但你调用的路径是/app/api/private-scoped,多了/app前缀,导致Security的规则未匹配,默认拒绝访问。

修复步骤

  1. 调整授权模式或分配应用权限:

    • 若要验证用户级权限:改用authorization_code模式获取Token(需要用户登录),此时Token会包含用户的权限信息。
    • 若坚持用client_credentials:在Auth0的API权限设置中,直接给你的应用分配read:message权限,这样获取的Token会包含scope字段。
  2. 统一权限名称:

    • 要么把Auth0中的权限改为read:messages,要么把SecurityConfig中的hasAuthority("SCOPE_read:message")改为hasAuthority("SCOPE_read:messages"),同时修正控制器中的提示文本,保证三者一致。
  3. 修正接口路径:

    • 要么把调用路径改为http://localhost:8080/api/private-scoped,要么在SecurityConfig中把规则改为.mvcMatchers("/app/api/private-scoped").hasAuthority("SCOPE_read:message"),确保路径匹配。
  4. 验证Token内容:获取Token后用JWT解析工具查看是否包含scope或permissions字段,确认权限已正确注入。

内容的提问来源于stack exchange,提问作者Joe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 22:30:54