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



原始标题:Fixing 401s with CORS Preflights and Spring Security | Baeldung