Spring Boot中401未授权异常无法返回自定义消息问题
问题原因
当请求未携带Basic Auth凭证时,Spring Security的ExceptionTranslationFilter会拦截401状态码的响应,并用默认的BasicAuthenticationEntryPoint生成标准401响应,覆盖了你自定义异常返回的消息体。只有当请求携带有效的Basic Auth凭证时,认证流程通过,Security不会介入异常处理,你的自定义异常消息才能正常返回给客户端。
解决方案
方法1:自定义AuthenticationEntryPoint
创建自定义认证入口点,让它在返回401响应时包含自定义异常的消息:
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.util.HashMap; import java.util.Map; @Component public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { // 从请求属性中获取自定义异常 Object exception = request.getAttribute("javax.servlet.error.exception"); String errorMessage = "Unauthorized"; if (exception instanceof BadCredentialsException) { errorMessage = ((BadCredentialsException) exception).getMessage(); } response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); Map<String, Object> errorResponse = new HashMap<>(); errorResponse.put("status", HttpServletResponse.SC_UNAUTHORIZED); errorResponse.put("message", errorMessage); response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); } }
在Security配置中替换默认入口点:
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; // 构造注入自定义入口点 public SecurityConfiguration(CustomAuthenticationEntryPoint customAuthenticationEntryPoint) { this.customAuthenticationEntryPoint = customAuthenticationEntryPoint; } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers(HttpMethod.POST, "/api/v2/user/login/**").permitAll() .antMatchers(HttpMethod.POST, "/api/v2/user/", "/api/v2/user", "/api/v2/user/change-role/**").hasAuthority("ROOT") .antMatchers(HttpMethod.GET, "/api/v2/user/", "/api/v2/user").hasAuthority("ROOT") .antMatchers(HttpMethod.POST, "/api/v1/customers/", "/api/v1/customers").hasAnyAuthority("ADMIN", "ROOT") .antMatchers(HttpMethod.GET, "/api/v1/customers/", "/api/v1/customers").hasAnyAuthority("EMPLOYEE", "ADMIN", "ROOT") .anyRequest().authenticated() .and() .httpBasic() // 配置自定义认证入口点 .authenticationEntryPoint(customAuthenticationEntryPoint); } }
方法2:全局异常处理器+针对性配置
通过@ControllerAdvice定义全局异常处理器,直接处理自定义异常并返回响应,同时配置Security不对登录端点的401异常进行拦截:
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import java.util.HashMap; import java.util.Map; @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BadCredentialsException.class) public ResponseEntity<Map<String, Object>> handleBadCredentials(BadCredentialsException ex) { Map<String, Object> response = new HashMap<>(); response.put("status", HttpStatus.UNAUTHORIZED.value()); response.put("message", ex.getMessage()); return new ResponseEntity<>(response, HttpStatus.UNAUTHORIZED); } }
修改Security配置,为登录端点单独设置自定义入口点:
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() // ... 现有授权配置 ... .and() .httpBasic() .and() .exceptionHandling() // 仅对登录端点使用自定义入口点 .defaultAuthenticationEntryPointFor(customAuthenticationEntryPoint, new AntPathRequestMatcher("/api/v2/user/login/**")); }
额外注意
确保application.properties中的配置保持有效:
server.error.include-message=always
内容的提问来源于stack exchange,提问作者Anant
相关产品推荐
相关产品推荐

