如何让Spring Security返回JSON格式401错误响应而非HTML?
Spring Security:将未授权请求的HTML 401响应改为指定JSON格式
现状与需求
当前使用Spring Security做权限控制时,未授权请求会返回HTML格式的401错误页面,示例如下:
<!doctype html> <html lang="en"> <head> <title>HTTP Status 401 – Unauthorized</title> <style type="text/css"> body { font-family: Tahoma, Arial, sans-serif; } h1, h2, h3, b { color: white; background-color: #525D76; } h1 { font-size: 22px; } h2 { font-size: 16px; } h3 { font-size: 14px; } p { font-size: 12px; } a { color: black; } .line { height: 1px; background-color: #525D76; border: none; } </style> </head> <body> <h1>HTTP Status 401 – Unauthorized</h1> </body> </html>
需要将其改为返回指定结构的JSON响应:
{ "errors": [ { "status": "401", "title": "UNAUTHORIZED", "detail": "xyz ..." } ] }
现有代码
SecurityConfiguration配置类
@Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // @formatter:off httpSecurity .csrf() .disable() .authorizeRequests() .antMatchers(HttpMethod.GET).permitAll() .anyRequest() .authenticated() .and() .httpBasic() .and() .exceptionHandling() .authenticationEntryPoint(new CustomAuthenticationEntryPoint()); // @formatter:on } }
CustomAuthenticationEntryPoint实现类
@Component class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { Collection<String> authorities = response.getHeaders("Authorization"); response.addHeader("access_denied_reason", "authentication_required"); response.sendError(HttpStatus.UNAUTHORIZED.value(), "Unauthorized"); } }
修改方案
问题核心在于原代码中response.sendError()会触发容器默认的错误页面生成逻辑,导致返回HTML。我们需要直接向响应流写入JSON数据,具体修改如下:
1. 基础修改(手动拼接JSON)
直接修改CustomAuthenticationEntryPoint的commence方法,跳过容器默认错误页面流程:
@Component class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { // 设置响应格式与状态码 response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.addHeader("access_denied_reason", "authentication_required"); // 构造符合要求的JSON字符串 String errorJson = "{\"errors\": [{\"status\": \"401\", \"title\": \"UNAUTHORIZED\", \"detail\": \"" + authException.getMessage() + "\"}]}"; // 写入响应体 response.getWriter().write(errorJson); response.getWriter().flush(); } }
2. 优化方案(使用Jackson序列化)
如果项目中已经引入Jackson,建议用实体类序列化的方式生成JSON,避免手动拼接出错:
@Component class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { private final ObjectMapper objectMapper; // 注入Jackson的ObjectMapper public CustomAuthenticationEntryPoint(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.addHeader("access_denied_reason", "authentication_required"); // 构造错误实体 ErrorResponse errorResponse = new ErrorResponse(); ErrorDetail errorDetail = new ErrorDetail(); errorDetail.setStatus("401"); errorDetail.setTitle("UNAUTHORIZED"); errorDetail.setDetail(authException.getMessage()); errorResponse.setErrors(List.of(errorDetail)); // 序列化为JSON并写入响应 objectMapper.writeValue(response.getWriter(), errorResponse); } // 定义对应实体类 static class ErrorResponse { private List<ErrorDetail> errors; public List<ErrorDetail> getErrors() { return errors; } public void setErrors(List<ErrorDetail> errors) { this.errors = errors; } } static class ErrorDetail { private String status; private String title; private String detail; public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDetail() { return detail; } public void setDetail(String detail) { this.detail = detail; } } }
注意事项
- 不需要修改
SecurityConfiguration,原配置已经正确绑定了自定义的AuthenticationEntryPoint - 如果需要自定义
detail字段内容,直接替换authException.getMessage()为指定字符串即可
内容的提问来源于stack exchange,提问作者emoleumassi
相关产品推荐
相关产品推荐

