Spring Boot JWT授权异常:有效令牌仍返回401 Unauthorized
核心问题
在Spring Boot项目中配置JWT授权后,即使使用有效令牌,所有接口(含Swagger UI)均返回401 Unauthorized异常。相关配置代码如下:
Web安全配置类
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) @EnableConfigurationProperties @ConfigurationProperties(prefix = "authority") @Profile({"cloud", "development", "release"}) public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { private Map<String, String> data = new LinkedHashMap<>(); private List<String> whiteList = new ArrayList<>(); @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/api/v1/students").authenticated() .and().authorizeRequests().antMatchers("/api/v1/students/*").authenticated() .and().authorizeRequests().antMatchers("/api/v1/students/*").access("hasRole('learning_management_admin_app1000000')") .and().authorizeRequests().antMatchers("/api/v1/students").access("hasRole('learning_management_admin_app1000000')"); http.csrf().disable().authorizeRequests().antMatchers(getWhiteList().toArray(new String[0])) .permitAll(); http.cors(); Okta.configureResourceServer401ResponseBody(http); } public List<String> getWhiteList() { // 省略实现 } }
控制器代码
@Validated @CrossOrigin("*") @RestController @RequestMapping("/api/v1") @SecurityRequirement(name = "BearerAuth") @PreAuthorize("isAuthenticated()") public class StudentsController { @GetMapping("change-requests") @PreAuthorize("hasAuthority('learning_management_admin_app1000000')") public ResponseEntity<List<Student>> getStudents( @RequestParam(required = false) String name) { // 省略实现 } }
排查方向及解决方案
1. 安全规则顺序错误
Spring Security的规则是从上到下匹配,一旦匹配就停止后续规则。你当前先配置了/api/v1/students等路径的认证/授权规则,之后才设置白名单的permitAll,这会导致白名单规则永远无法触发,所有请求都会被前面的规则拦截。
修复方法:调整规则顺序,先配置白名单,再配置需要认证的路径:
@Override protected void configure(HttpSecurity http) throws Exception { // 先放行白名单路径 http.csrf().disable() .authorizeRequests() .antMatchers(getWhiteList().toArray(new String[0])).permitAll() // 再配置需要认证和授权的路径 .antMatchers("/api/v1/students", "/api/v1/students/*") .authenticated() .access("hasAuthority('learning_management_admin_app1000000')") // 其他所有请求默认需要认证 .anyRequest().authenticated(); http.cors(); Okta.configureResourceServer401ResponseBody(http); }
2. 白名单未包含Swagger路径
如果Swagger UI的访问路径不在白名单中,会被拦截返回401。需要确认whiteList是否包含以下Swagger常用路径:
/swagger-ui/**/swagger-resources/**/v3/api-docs/**/webjars/**
修复方法:在getWhiteList方法中补充这些路径:
public List<String> getWhiteList() { if (whiteList.isEmpty()) { whiteList.add("/swagger-ui/**"); whiteList.add("/swagger-resources/**"); whiteList.add("/v3/api-docs/**"); whiteList.add("/webjars/**"); // 其他业务白名单路径 } return whiteList; }
3. hasRole与hasAuthority前缀不匹配
Spring Security中hasRole默认会自动添加ROLE_前缀,而hasAuthority直接使用传入的字符串。如果你的JWT令牌中的角色没有ROLE_前缀,使用hasRole('learning_management_admin_app1000000')会实际检查ROLE_learning_management_admin_app1000000,与控制器中hasAuthority的校验逻辑不一致,导致授权失败。
修复方法:统一校验规则:
- 将配置中的
access("hasRole('xxx')")改为access("hasAuthority('learning_management_admin_app1000000')") - 或者如果令牌中角色确实带
ROLE_前缀,将控制器中的hasAuthority改为hasRole
4. JWT资源服务器配置不完整
仅调用Okta.configureResourceServer401ResponseBody(http)不足以完成JWT的验证配置,需要确保已配置JWT的issuer、audience等核心参数。
修复方法:在配置文件中添加Okta相关配置:
okta.oauth2.issuer=https://你的Okta域名/oauth2/default okta.oauth2.audience=api://default
或者手动配置JWT解码器:
@Bean public JwtDecoder jwtDecoder() { return JwtDecoders.fromIssuerLocation("https://你的Okta域名/oauth2/default"); }
5. 全局方法安全注解生效问题
虽然添加了@EnableGlobalMethodSecurity(prePostEnabled = true),但需确保Spring能扫描到控制器中的@PreAuthorize注解。另外,控制器类上的@PreAuthorize("isAuthenticated()")会强制所有接口需要认证,若令牌解析失败也会返回401。
验证方法:临时移除控制器类上的@PreAuthorize注解,测试单个接口是否能正常访问,排查是否为方法级注解的问题。
内容的提问来源于stack exchange,提问作者MA-Dev

