根本原因是IIS或ASP.NET管道在预检请求到达控制器前就拦截了OPTIONS请求,因默认未放行该方法,导致直接返回405而非进入CORS处理流程。

为什么 OPTIONS 请求总返回 405 或被拦截
根本原因不是 CORS 配置漏了,而是 IIS 或 ASP.NET 管道在预检请求到达控制器前就把它拦下了。默认情况下,IIS 不允许 OPTIONS 方法通过,尤其当 Web.config 没显式放开、或 Global.asax 里没提前截获时,请求根本进不了 WebAPI 的路由层,直接 405 Method Not Allowed。
常见现象包括:
- 浏览器 Network 面板看到
OPTIONS请求状态码是 405,后面没有真正的 POST/PUT 请求 - 即使加了
config.EnableCors(),依然报错 “Response to preflight request doesn’t pass access control check” - 带自定义 Header(如
Authorization、X-Api-Key)或非简单 Content-Type(如application/json)的请求必触发预检,但服务端没响应头
Global.asax 中 Application_BeginRequest 必须做两件事
这是绕过 IIS 拦截最直接有效的手段,必须在请求进入 MVC/WebAPI 管道前就处理掉 OPTIONS 请求。
关键点:
- 判断条件要严格:同时满足
Request.Headers.AllKeys.Contains("Origin")且Request.HttpMethod == "OPTIONS" - 不能只写
Response.End()—— 它可能引发线程中止异常;推荐用Response.StatusCode = 200; Response.End();或更稳妥的Response.SuppressContent = true; Response.StatusCode = 200; Response.End(); - 务必确保该逻辑在
Application_BeginRequest中,不能放在Application_Start或控制器里 - 如果项目启用了 FormsAuthentication 或其他模块,需确认它们不会在
BeginRequest后覆盖响应
Web.config 的 customHeaders 和 handler 映射必须匹配
仅靠代码层 CORS 配置不够,IIS 层也要放行 OPTIONS 并注入必要响应头,否则预检响应会缺失关键字段。
在 <system.webServer> 节点下添加:
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="https://your-frontend-domain.com" />
<add name="Access-Control-Allow-Methods" value="GET,POST,PUT,DELETE,OPTIONS" />
<add name="Access-Control-Allow-Headers" value="Content-Type,Authorization,X-Requested-With" />
<add name="Access-Control-Allow-Credentials" value="true" />
</customHeaders>
</httpProtocol>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" responseBufferLimit="0" />
</handlers>注意:
-
Access-Control-Allow-Origin值不能为*且Access-Control-Allow-Credentials为true,否则浏览器拒绝响应 -
<handlers>配置确保OPTIONS能被正确路由到 ASP.NET,避免 IIS 直接返回 405 - 如果部署在子目录或虚拟目录下,检查
path是否影响匹配
EnableCorsAttribute 的 origins 参数不能写 * 当需要凭据时
很多人配置 new EnableCorsAttribute("*", "*", "*") 后仍失败,问题就出在这里 —— 只要前端设置了 withCredentials: true(比如传 Cookie 或 Authorization header),后端 Access-Control-Allow-Origin 就绝不能是通配符 *。
正确做法:
- 开发环境可写死:
origins: "http://localhost:8080" - 生产环境建议白名单校验,例如在
Application_BeginRequest中动态读取 Origin 并比对合法域名列表,再设置响应头 - 若用
GlobalConfiguration.Configuration.EnableCors()全局启用,记得它和控制器级[EnableCors]是叠加关系,后者优先级更高 -
SupportsCredentials = true必须显式设,否则Access-Control-Allow-Credentials: true不会输出
真正容易被忽略的是:预检响应里缺失 Access-Control-Allow-Headers 会导致后续请求被静默拦截,而这个头在 Web.config 和代码层都要对齐 —— 前端发了什么自定义 header,后端就必须在 allow 列表里列出来,一个都不能少。


















