Spring Boot Security:未带Bearer Token返回403而非401问题解决
Spring Boot Security权限异常处理问题及解决办法
问题描述
- 携带普通用户的Bearer Token访问标注
@PreAuthorize("hasAnyAuthority('ADMIN')")的接口,返回401 Unauthorized,期望返回403 Forbidden。 - 不携带Bearer Token时,始终返回403 Forbidden而非401 Unauthorized,且全局异常处理器中的
handleAccessDeniedError方法未触发。
相关代码
全局异常处理器片段
@ExceptionHandler({AccessDeniedException.class}) protected ResponseEntity<Object> handleAccessDeniedError(final AccessDeniedException exception) { log.error(exception.getMessage(), exception); Error error = Error.builder() .httpStatus(HttpStatus.FORBIDDEN) .header(Error.Header.AUTH_ERROR.getName()) .build(); return new ResponseEntity<>(error, HttpStatus.FORBIDDEN); }
接口代码片段
@GetMapping @PreAuthorize("hasAnyAuthority('ADMIN')") public Response<?> getUsers(@RequestBody @Valid UserListRequest listRequest) { // 接口逻辑 }
问题原因及解决办法
问题1:权限不足返回401而非403
原因:Spring Security默认异常处理机制中,若认证过滤器(如Bearer Token过滤器)在权限校验前执行,当权限校验失败抛出AccessDeniedException时,可能被认证相关的异常处理器拦截,错误转换为401响应;或Security配置未正确区分认证失败(401)与授权失败(403)的异常处理逻辑。
解决办法:
在Security配置类中,通过exceptionHandling()明确配置授权失败的处理器:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // 其他配置... .exceptionHandling() // 认证失败(未登录/Token无效)返回401 .authenticationEntryPoint((request, response, authException) -> { response.setStatus(HttpStatus.UNAUTHORIZED.value()); // 可自定义响应体格式 }) // 授权失败(权限不足)返回403 .accessDeniedHandler((request, response, accessDeniedException) -> { response.setStatus(HttpStatus.FORBIDDEN.value()); // 可调用全局异常处理器的逻辑输出统一错误格式 }); } }
同时确保@PreAuthorize的权限校验在认证完成后执行,即过滤器链中认证过滤器(如BearerTokenAuthenticationFilter)在权限过滤器(如FilterSecurityInterceptor)之前。
问题2:未携带Token返回403且全局异常处理器未触发
原因:未携带Token时,Spring Security会触发AuthenticationEntryPoint(认证入口点),默认实现可能返回403;此时抛出的是AuthenticationException(如InsufficientAuthenticationException),而非AccessDeniedException,因此全局异常处理器中对应AccessDeniedException的方法不会触发。
解决办法:
- 调整Security配置,让未认证请求返回401:
在Security配置中配置authenticationEntryPoint,明确未认证时返回401:http.exceptionHandling() .authenticationEntryPoint((request, response, authException) -> { // 自定义401响应体 Error error = Error.builder() .httpStatus(HttpStatus.UNAUTHORIZED) .header(Error.Header.AUTH_ERROR.getName()) .build(); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpStatus.UNAUTHORIZED.value()); new ObjectMapper().writeValue(response.getOutputStream(), error); }); - 全局异常处理器添加AuthenticationException处理:
若希望通过全局异常处理器处理认证失败异常,添加对应的@ExceptionHandler方法:
注意:若同时配置了Security的@ExceptionHandler({AuthenticationException.class}) protected ResponseEntity<Object> handleAuthenticationError(final AuthenticationException exception) { log.error(exception.getMessage(), exception); Error error = Error.builder() .httpStatus(HttpStatus.UNAUTHORIZED) .header(Error.Header.AUTH_ERROR.getName()) .build(); return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED); }authenticationEntryPoint和全局异常处理器,需确保过滤器链未提前拦截异常,可通过调整过滤器顺序或禁用默认异常处理让全局处理器生效。
内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu
相关产品推荐
相关产品推荐

