kratos gateway 的 cors 必须在 config.yaml 中统一配置,因其作为独立进程默认拦截并响应 options 预检请求,且 allowcredentials: true 时 alloworigins 禁用 "*",否则浏览器静默丢弃响应。

Kratos 的跨域必须配在 Gateway 层的 config.yaml 里,下游 service 加 CORS 中间件完全无效——因为网关已拦截 OPTIONS 请求,根本不会转发过去。
为什么 Kratos Gateway 必须统一配 CORS
Kratos Gateway 是独立进程,所有 HTTP 流量先经过它再路由到下游服务。它的 cors 中间件默认开启且 OptionsPassthrough: false,这意味着浏览器发来的预检 OPTIONS 请求会被网关直接处理并返回 204,根本不会透传给后端 service。
常见错误现象包括:
Response to preflight request doesn't pass access control check- 控制台只报 CORS 错误,但后端日志里完全看不到
OPTIONS请求 - 即使 downstream service 用
gorilla/handlers.CORS()配了头,浏览器依然收不到Access-Control-Allow-Origin
根本原因:网关没配 CORS,或配了但响应头被覆盖/未生效。
allowCredentials: true 时 allowOrigins 不能写 ["*"]
这是最常踩的坑——allowCredentials: true 表示允许前端携带 Cookie 或 Authorization 头,此时浏览器强制要求 Access-Control-Allow-Origin 必须是具体域名,禁止通配符 *。否则响应会被静默丢弃,连控制台错误都不会出现。
正确做法:
- 开发环境:硬编码本地地址,如
["http://localhost:3000", "https://app.example.com"] - 生产环境:禁用
["*"],改用动态白名单逻辑(从请求头Origin提取 host,校验后再回写该值) - 务必显式配置
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],漏掉"OPTIONS"会导致预检失败 - 若前端带自定义 header(如
X-Auth-Token),allowHeaders也得列全,不能只写["*"](部分浏览器不支持)
如何验证网关是否正确处理了 OPTIONS 预检
别依赖浏览器 Network 面板看“有没有发出去”,要直接用 curl 模拟真实预检行为:
curl -v -X OPTIONS \ -H "Origin: http://localhost:3000" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: X-Auth-Token" \ http://your-gateway/api/v1/users
关键看响应头是否包含:
-
Access-Control-Allow-Origin: http://localhost:3000(不是*) -
Access-Control-Allow-Credentials: true(如果开了 credential) Access-Control-Allow-Methods: POST, OPTIONSAccess-Control-Allow-Headers: X-Auth-Token-
Vary: Origin(Kratos 默认加,但手写中间件容易漏)
状态码应为 204 No Content;若返回 404 或 500,说明网关路由或中间件配置有误。
下游 service 绝对不要自己加 CORS 中间件
你在 user service 或 order service 里加 cors middleware,不仅没用,还可能引发 header 冲突(比如重复写 Access-Control-Allow-Origin 导致 500)。
唯一例外是:你明确关闭了网关的 cors 中间件,并自行接管所有跨域逻辑——但这违背 Kratos Gateway 的设计初衷,也失去统一治理能力。
真正需要下游配合的,只有两件事:
- 确保网关配置的
allowOrigins和实际前端域名一致 - 若用
allowCredentials: true,后端接口需校验Cookie或Authorization并返回对应用户上下文(网关只透传,不鉴权)
复杂点在于:网关层无法做动态 Origin 白名单校验(比如正则匹配子域名),这种逻辑必须自己扩展中间件,且要注意并发安全和性能开销——别在每次 OPTIONS 里查数据库。











