SpringBoot 3中401 Unauthorized无响应体,如何返回自定义JSON?
解决SpringBoot 3中未认证时返回自定义JSON格式401响应
直接用全局异常处理器(@RestControllerAdvice)大概率行不通,因为Spring Security的认证失败逻辑是在过滤器链中执行的,此时请求还没到达控制器层,所以全局异常处理器无法捕获这类异常。正确的做法是自定义AuthenticationEntryPoint或者配置Spring Security的异常处理逻辑。
方法一:自定义AuthenticationEntryPoint
AuthenticationEntryPoint是Spring Security中处理未认证请求的核心接口,当用户未通过认证时,会调用它的commence方法。我们可以重写这个方法来返回自定义JSON响应。
- 实现
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)); } }
- 在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把认证异常转发到控制器层,再由处理器捕获:
- 实现转发异常的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); } } }
- 编写全局异常处理器:
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); } }
- 在Security配置中注册这个转发EntryPoint即可。
这种方法多了一层转发,效率不如方法一,推荐优先使用方法一。
内容的提问来源于stack exchange,提问作者tpdovu
相关产品推荐
相关产品推荐

