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

SpringBoot 3中401 Unauthorized无响应体,如何返回自定义JSON?

解决SpringBoot 3中未认证时返回自定义JSON格式401响应

直接用全局异常处理器(@RestControllerAdvice)大概率行不通,因为Spring Security的认证失败逻辑是在过滤器链中执行的,此时请求还没到达控制器层,所以全局异常处理器无法捕获这类异常。正确的做法是自定义AuthenticationEntryPoint或者配置Spring Security的异常处理逻辑。

方法一:自定义AuthenticationEntryPoint

AuthenticationEntryPoint是Spring Security中处理未认证请求的核心接口,当用户未通过认证时,会调用它的commence方法。我们可以重写这个方法来返回自定义JSON响应。

  1. 实现AuthenticationEntryPoint接口:
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;

    // 构造方法注入ObjectMapper
    public CustomAuthenticationEntryPoint(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        // 设置响应状态码为401
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        // 设置响应内容类型为JSON
        response.setContentType("application/json;charset=UTF-8");

        // 构建自定义响应体
        Map<String, String> responseBody = new HashMap<>();
        responseBody.put("message", "Please provide a valid token.");
        // 可选:添加错误码等额外字段
        // responseBody.put("errorCode", "UNAUTHORIZED");

        // 将响应体写入输出流
        response.getWriter().write(objectMapper.writeValueAsString(responseBody));
    }
}
  1. 在Spring Security配置中注册这个自定义EntryPoint:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
    // 假设你有自定义的Token认证过滤器
    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    // 构造方法注入依赖
    public SecurityConfig(CustomAuthenticationEntryPoint customAuthenticationEntryPoint,
                          JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable()) // 根据业务需求决定是否禁用CSRF
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/public/**").permitAll() // 开放无需认证的接口
                        .anyRequest().authenticated() // 其他接口需认证
                )
                // 配置自定义的未认证处理逻辑
                .exceptionHandling(exceptions -> exceptions
                        .authenticationEntryPoint(customAuthenticationEntryPoint)
                )
                // 添加Token认证过滤器到链中
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

方法二:全局异常处理器配合转发(备选)

如果坚持想用全局异常处理器,需要让Spring Security把认证异常转发到控制器层,再由处理器捕获:

  1. 实现转发异常的EntryPoint:
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;

@Component
public class ForwardAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) {
        request.setAttribute("javax.servlet.error.exception", authException);
        try {
            // 转发到指定路径,让全局处理器捕获
            request.getRequestDispatcher("/error/auth").forward(request, response);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
  1. 编写全局异常处理器:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
@RequestMapping("/error")
public class CustomExceptionHandler {

    @GetMapping("/auth")
    public ResponseEntity<Map<String, String>> handleAuthenticationException(AuthenticationException e) {
        Map<String, String> response = new HashMap<>();
        response.put("message", "Please provide a valid token.");
        return new ResponseEntity<>(response, HttpStatus.UNAUTHORIZED);
    }
}
  1. 在Security配置中注册这个转发EntryPoint即可。

这种方法多了一层转发,效率不如方法一,推荐优先使用方法一。

内容的提问来源于stack exchange,提问作者tpdovu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 03:07:38