SpringBoot集成AUTH0 Token认证返回403错误排查求助
问题
学习Auth0搭建Demo时,Spring Security集成Auth0 Token认证调用/api/private-scoped接口始终返回403错误。操作步骤如下:
- 在Auth0创建应用与API(包含
read:message权限),创建用户并设置admin角色,为该角色分配API的read:message权限并关联用户,完成API对应用的授权。 - Spring Boot项目中配置了SecurityConfig、AudienceValidator,编写了API控制器。
- 通过
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的规则未匹配,默认拒绝访问。
修复步骤
调整授权模式或分配应用权限:
- 若要验证用户级权限:改用
authorization_code模式获取Token(需要用户登录),此时Token会包含用户的权限信息。 - 若坚持用
client_credentials:在Auth0的API权限设置中,直接给你的应用分配read:message权限,这样获取的Token会包含scope字段。
- 若要验证用户级权限:改用
统一权限名称:
- 要么把Auth0中的权限改为
read:messages,要么把SecurityConfig中的hasAuthority("SCOPE_read:message")改为hasAuthority("SCOPE_read:messages"),同时修正控制器中的提示文本,保证三者一致。
- 要么把Auth0中的权限改为
修正接口路径:
- 要么把调用路径改为
http://localhost:8080/api/private-scoped,要么在SecurityConfig中把规则改为.mvcMatchers("/app/api/private-scoped").hasAuthority("SCOPE_read:message"),确保路径匹配。
- 要么把调用路径改为
验证Token内容:获取Token后用JWT解析工具查看是否包含
scope或permissions字段,确认权限已正确注入。
内容的提问来源于stack exchange,提问作者Joe
相关产品推荐
相关产品推荐

