1. 概述
在本篇文章中,我们将探讨一个常见但容易踩坑的问题:“Response for preflight has invalid HTTP status code 401” —— 这个错误通常出现在支持跨域通信、并使用了 Spring Security 的应用中。
我们会先简单介绍跨域请求的基本概念,然后通过一个具体例子来演示这个问题,并给出解决方案。
2. 跨域请求(Cross-Origin Requests)
简单来说,跨域请求是指请求发起方与目标资源所在的域不一致的情况。例如,前端页面部署在 http://localhost:4200
,而后端 API 服务运行在 http://localhost:8080
,这就构成了跨域。
为了允许这种跨域访问,服务端需要启用 CORS(Cross-Origin Resource Sharing,跨源资源共享)机制。
CORS 的第一步是一个 OPTIONS 请求,用于确认目标服务器是否允许当前源发起的实际请求。这个 OPTIONS 请求也被称为 预检请求(Preflight Request)。
服务器可以返回如下几个关键响应头来控制 CORS 行为:
- ✅ Access-Control-Allow-Origin:指定哪些源可以访问该资源,可以用
*
表示任意源 - ✅ Access-Control-Allow-Methods:允许的 HTTP 方法列表
- ✅ Access-Control-Allow-Headers:允许客户端发送的请求头字段
- ✅ Access-Control-Max-Age:预检请求结果的缓存时间(单位秒)
⚠️ 如果预检请求返回的状态码不是 2xx,浏览器就会阻止后续的真实请求,并抛出类似 “Response for preflight has invalid HTTP status code 401” 的错误。
虽然 Spring 提供了方便的方式来开启 CORS 支持,但如果配置不当,很容易导致预检请求失败,从而出现 401 错误。
3. 创建一个支持 CORS 的 REST 接口
我们先创建一个简单的 REST 控制器,它启用了 CORS:
@RestController
@CrossOrigin("http://localhost:4200")
public class ResourceController {
@GetMapping("/user")
public String user(Principal principal) {
return principal.getName();
}
}
这里我们使用了 @CrossOrigin
注解,表示只允许来自 http://localhost:4200
的请求访问这个接口。
4. 使用 Spring Security 加固接口
接下来,我们为这个接口添加 Spring Security 安全控制:
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
这段配置的作用是:所有请求都需要认证,也就是说,未携带有效认证信息的请求都会被拒绝。
5. 发起一个预检请求测试
我们用 curl 来模拟一次预检请求:
curl -v -H "Access-Control-Request-Method: GET" -H "Origin: http://localhost:4200"
-X OPTIONS http://localhost:8080/user
...
< HTTP/1.1 401
...
< WWW-Authenticate: Basic realm="Realm"
...
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Access-Control-Allow-Origin: http://localhost:4200
< Access-Control-Allow-Methods: POST
< Access-Control-Allow-Credentials: true
< Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
...
从输出可以看到,服务器返回了 HTTP 401 Unauthorized。
虽然这是通过 curl 发起的请求,看不到浏览器中的报错信息,但在实际开发中,如果你从另一个域发起 AJAX 请求,就会看到熟悉的错误提示:
❌ “Response for preflight has invalid HTTP status code 401”
6. 解决方案
问题出在哪里?答案很简单:
⚠️ 我们没有显式地将 OPTIONS 请求排除在 Spring Security 的权限校验之外。
默认情况下,Spring Security 会对所有请求进行拦截和认证处理,包括 OPTIONS 请求。而预检请求不应该要求认证,否则就会返回 401。
幸运的是,Spring 提供了一个开箱即用的解决方案:
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// ...
http.cors(Customizer.withDefaults()); // 关键一行,解决 CORS 401 问题
return http.build();
}
}
这行代码会注册一个由 Spring 提供的 CorsFilter
,它会自动放行 OPTIONS 请求,不再对其进行认证检查。
✅ 现在重新测试,你会发现预检请求正常返回 200,不会再报错了。
7. 总结
在这篇文章中,我们解决了在使用 Spring Security 和 CORS 时常见的 401 错误问题:
- 跨域请求需要预检(OPTIONS)
- Spring Security 默认会拦截所有请求,包括 OPTIONS
- 必须显式开启 CORS 支持(使用
.cors()
),让 Spring 自动处理 OPTIONS 请求
💡 最后提醒一句:要复现这个问题,客户端和服务端必须运行在不同的域名或端口上。比如在本地测试时,可以把前端部署在 localhost:4200
,后端运行在 localhost:8080
。