bigywater.tech
Spring · 107

CORS 与 Security:预检为什么被 401 拦下

浏览器报 CORS 错误,但 MVC 里明明配了跨域?预检请求为什么是 401?http.cors() 到底做了什么?allowedOrigins("*") 能不能带凭据?这篇在 Spring Boot 4.0.8、Spring Security 7 上用 OPTIONS 请求逐个实测。

401
没有 http.cors() 时,不带凭据的预检 OPTIONS 得到的状态码,响应里没有任何 access-control-* 头
1800
加上 http.cors(withDefaults) 之后预检响应里的 access-control-max-age(秒)
回显 Origin
allowedOriginPatterns 配合 allowCredentials 时,access-control-allow-origin 回显请求的 Origin

预检被 401 拦下

实测 · 本机执行输出
  预检 OPTIONS /api/data(Origin https://app.example)→ HTTP 401,相关响应头 {'www-authenticate': 'Basic realm="Realm"'}
  带凭据的 GET /api/data(Origin https://app.example)→ HTTP 200,相关响应头 {'vary': 'Access-Control-Request-Headers', 'access-control-allow-origin': 'https://app.example', 'access-control-allow-credentials': 'true'}
  带凭据的 GET /api/data(Origin https://evil.example)→ HTTP 403 Invalid CORS request,相关响应头 {'vary': 'Access-Control-Request-Headers'}

预检不带凭据,在认证这一关就被拦下,没有 CORS 头;真实请求带凭据时却是通的。

http.cors()

实测 · 本机执行输出
  预检(允许的 Origin)→ HTTP 200,相关响应头 {'vary': 'Access-Control-Request-Headers', 'access-control-allow-origin': 'https://app.example', 'access-control-allow-methods': 'GET,POST,PUT', 'access-control-allow-headers': 'content-type, x-token', 'access-control-allow-credentials': 'true', 'access-control-max-age': '1800'}
  预检(不在允许列表里的 Origin https://evil.example)→ HTTP 403 Invalid CORS request,相关响应头 {'vary': 'Access-Control-Request-Headers'}
  预检(请求方法 DELETE 不在允许列表里)→ HTTP 403 Invalid CORS request
  预检(请求头 x-other 不在允许列表里)→ HTTP 403 Invalid CORS request
  带凭据的 GET → HTTP 200,相关响应头 {'vary': 'Access-Control-Request-Headers', 'access-control-allow-origin': 'https://app.example', 'access-control-allow-credentials': 'true'}

CorsFilter 排在认证之前,复用 MVC 的 CORS 规则;不允许的 Origin、方法、请求头预检 403。

全局规则与凭据

实测 · 本机执行输出
  setAllowedOrigins(["*"]) + setAllowCredentials(true):应用启动成功
    但预检请求 → HTTP 401 ,相关响应头 {'vary': 'Access-Control-Request-Headers', 'www-authenticate': 'Basic realm="Realm"'}
    服务端日志:(无)
  setAllowedOriginPatterns(["https://*.example"]) + allowCredentials(true):预检 Origin https://shop.example → HTTP 200,相关响应头 {'vary': 'Access-Control-Request-Headers', 'access-control-allow-origin': 'https://shop.example', 'access-control-allow-methods': 'GET,POST', 'access-control-allow-headers': 'content-type, x-token', 'access-control-allow-credentials': 'true'}
    预检 Origin https://shop.other → HTTP 403 Invalid CORS request

bean 名要叫 corsConfigurationSource;带凭据用 allowedOriginPatterns。

分步演示

CORS
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Spring Security 7)。/api/data 需要 Basic 认证(alice / pw);预检用 OPTIONS 加 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 模拟,真实浏览器还会带别的头;配置由 -Dx107.cfg 切换;Python http.client 发请求,不自动重定向
    • 没有验证:真实浏览器里的 CORS 错误提示;allowedOrigins("*") 加 allowCredentials(true) 预检没有生效的具体原因(只观察到 401 且没有 CORS 头,日志里也没有异常);Private Network Access;Access-Control-Expose-Headers;响应式 WebFlux 里的 CORS;@CrossOrigin 注解与全局配置的合并规则
    京ICP备2026067502号-1京公网安备11010502063487号