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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 11:40:33