1. 一句话

浏览器规定:响应带 Access-Control-Allow-Credentials: true 时,Access-Control-Allow-Origin / Allow-Headers / Allow-Methods 都不许是 *。想让"带 Cookie 的跨域请求"通过,服务器必须回显请求方的具体来源和请求头,而不是发通配符。

2. 发生条件

  • 前端要跨域发请求,并且带凭据(Cookie / Authorization + withCredentials)。
  • 后端 CORS 中间件图省事配了"允许所有"(*)。

3. 症状 / 报错

分两层,容易认错:

层表现
浏览器The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include';预检(OPTIONS)过不去,正式请求根本没发出
框架tower-http 会在构造时直接 panic:Invalid CORS configuration: Cannot combine 'Access-Control-Allow-Credentials: true' with 'Access-Control-Allow-Headers: *' —— 服务起不来,跟浏览器无关

第二层特别坑:本地 curl 完全正常,测试也过不了,因为进程在 Router 构造阶段就炸了。

4. 试过但没用的解法

  • 把 Allow-Headers 从 * 改成列一堆头 → 能跑,但每加一个自定义头(如 X-Trace-Id)就得回来改一次,漏了就静默失败。
  • 干脆关掉 allow_credentials(true) → 浏览器不再带 Cookie,需要登录态的功能全废。

5. 根因

* 的语义是"任何人",credentials: true 的语义是"允许携带用户身份"。两者叠加等于对任意站点开放用户凭据,浏览器直接禁止。规范如此,不是框架的锅。

正确做法是动态回显:把请求里的 Origin、Access-Control-Request-Headers、Access-Control-Request-Method 原样回写。回显在效果上等价于"允许所有",但范围被限制在对方自己声明要用的东西上,不构成对任意站的开放。

6. 最终解法

tower-http(Rust / axum)

use tower_http::cors::{AllowHeaders, AllowMethods, AllowOrigin, CorsLayer};

CorsLayer::new()
    .allow_origin(AllowOrigin::list(origins))   // 白名单,逐个 parse 成 HeaderValue
    .allow_credentials(true)
    .allow_methods(AllowMethods::mirror_request())   // 回显 Access-Control-Request-Method
    .allow_headers(AllowHeaders::mirror_request())   // 回显 Access-Control-Request-Headers

注意 mirror_request() 只在预检请求里回显;普通请求不带这两个头,也就自然切回手头默认值,不会出错。

Starlette / FastAPI

框架已经内建了这套回显逻辑:allow_methods=["*"] + allow_headers=["*"] + allow_credentials=True 在 Starlette 里是合法的,它在预检时自动把 Access-Control-Request-Headers 回显出去。所以从 FastAPI 迁到 axum 时,不能照抄"配 * 就行"的写法。

7. 下次怎么识别

  • 报错里同时出现 Access-Control-Allow-Credentials 和 *(或 cannot combine)→ 直接看第 6 节。
  • 从 Python/Starlette 迁到 Rust/axum:CORS 配置要重写,不要照抄,两者的"全允许"语义不同。
  • 反向代理也要同步检查:nginx 里手写 add_header Access-Control-Allow-Origin "*" 的,遇到带凭据请求同样会被浏览器拦。

8. 相关概念

来源:2026-09 迁移 RoleFit 管理后台(FastAPI → axum)时,tower-http 在 Router 构造阶段 panic 暴露此差异。

本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web