CORS(Cross-Origin Resource Sharing,跨域资源共享)是前后端分离架构下绕不开的话题。当浏览器中的前端页面向一个不同源的后端接口发起请求时,浏览器会根据CORS规范检查响应头,不满足条件就会直接拦截掉这次请求,并在控制台抛出类似“已被CORS策略阻止”的错误。在C#的ASP.NET Core项目中,解决这个问题的核心是正确注册跨域服务并按正确顺序挂载中间件。本文将从原理讲到实战,覆盖全局配置、命名策略、自定义策略以及常见踩坑点。

一、先弄明白跨域到底是怎么回事
所谓同源,指的是协议、域名、端口三者完全相同。例如前端页面地址为https://ipipp.com:8080/index.html,它去请求http://ipipp.com:5000/api/users,端口不一致,就构成了跨域。需要注意,跨域限制是浏览器的行为,服务器实际上已经收到了请求并返回了响应,只是浏览器在校验响应头时发现缺少允许跨域的标记,才把结果丢弃并报错。这也是为什么用Postman直接调用接口永远不会遇到CORS问题的原因。
CORS将请求分为两类:简单请求和预检请求。简单请求要求请求方法为GET、POST或HEAD,且不携带自定义头部。对于简单请求,浏览器直接发送,但会附带Origin请求头标识来源,服务器需要在响应中返回Access-Control-Allow-Origin头部。而一旦前端使用了PUT、DELETE方法,或者设置了Content-Type: application/json之外的复杂头(实际上json的Content-Type也会触发预检),浏览器会先发一个OPTIONS方法的预检请求,询问服务器是否允许真正的请求,只有预检通过,后续的真实请求才会发出。
理解预检机制对排查问题非常重要。很多开发者遇到的情况是:GET接口正常,PUT或DELETE接口一直报跨域错误,检查配置又看不出问题。这往往就是因为预检请求被拦截了,比如被身份验证中间件提前返回了401,导致浏览器认为预检失败。
二、ASP.NET Core中的基础CORS配置
在ASP.NET Core中配置跨域只需要两步:在ConfigureServices(.NET 6之后的minimal hosting中是builder.Services)中注册服务,以及在请求管道中启用中间件。最简单的写法是允许所有来源、所有方法、所有请求头:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
var app = builder.Build();
// 注意:UseCors 必须放在 UseRouting 之后、UseAuthorization 之前
app.UseCors("AllowAll");
app.MapGet("/api/test", () => "hello cors");
app.Run();这里有个非常关键的顺序问题:UseCors必须放在UseRouting之后、UseAuthorization和UseAuthentication之前。如果放在了授权中间件之后,带身份验证的请求在到达CORS中间件之前就可能被拒绝,预检请求会直接失败。如果启动时框架检测到顺序不对,还会直接抛出异常提示中间件顺序错误。
允许所有来源虽然方便调试,但存在安全隐患,生产环境通常不推荐。而且AllowAnyOrigin与AllowCredentials不能同时使用,因为CORS规范明确禁止响应头Access-Control-Allow-Origin为星号时同时允许携带凭证。遇到这种组合,框架在启动或运行时会直接报错,提示两者互斥。
三、生产环境推荐:限定来源的自定义策略
更稳妥的做法是明确指定允许的来源域名、请求方法和头部,并为预检结果设置缓存时间,减少浏览器重复发送OPTIONS请求的开销:
builder.Services.AddCors(options =>
{
options.AddPolicy("ProductPolicy", policy =>
{
policy.WithOrigins(
"https://ipipp.com",
"https://www.ipipp.com",
"http://localhost:8080")
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("Content-Type", "Authorization", "X-Custom-Header")
.AllowCredentials() // 允许携带Cookie
.SetPreflightMaxAge(TimeSpan.FromSeconds(600));
});
});
app.UseCors("ProductPolicy");如果前端需要携带Cookie(例如使用了Session或基于Cookie的登录态),必须调用AllowCredentials,同时前端在发请求时要设置withCredentials = true。并且一旦调用了AllowCredentials,来源就不能用AllowAnyOrigin,必须用WithOrigins明确列出,这样框架会自动把Access-Control-Allow-Origin回显为具体的来源地址,符合浏览器的要求。
除了命名策略,还可以使用默认策略简化配置,调用无参的app.UseCors()即可启用:
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
policy.WithOrigins("https://ipipp.com")
.AllowAnyHeader()
.AllowAnyMethod());
});
// 直接启用默认策略,无需传参
app.UseCors();两种方式的区别在于作用范围:默认策略对所有经过CORS中间件的请求生效,而命名策略可以灵活地在控制器或Action上局部应用,实现不同接口不同策略的精细化控制。
四、局部跨域与控制器级别的策略控制
有些场景下,整个站点大部分接口只对内部开放,只有少数接口需要对外开放给第三方前端。这时可以在控制器或Action上通过EnableCors特性指定命名策略:
[ApiController]
[Route("api/[controller]")]
[EnableCors("ProductPolicy")] // 该控制器下所有接口应用此策略
public class OpenController : ControllerBase
{
[HttpGet("data")]
public IActionResult GetData() => Ok(new { code = 0, data = "ok" });
}也可以只标注在某个Action上,甚至用DisableCors显式禁用某个接口的跨域。特性标注的优先级高于全局中间件配置,当两者同时存在时,以特性指定的策略为准。需要注意的是,即使只使用特性标注而不使用全局中间件,也必须注册AddCors服务,并且仍然要在管道中调用app.UseCors(),否则特性不会生效,这是新手非常容易踩的坑。
五、常见踩坑问题排查
第一个高频问题是配置了CORS但依然报错。排查思路是先看响应头有没有Access-Control-Allow-Origin,可以用浏览器开发者工具的Network面板查看OPTIONS预检请求。如果预检请求返回404或405,很可能是路由没有匹配到OPTIONS请求,或者被自定义中间件拦截了;如果返回401,则是认证中间件把它挡住了,把UseCors挪到认证之前即可。
第二个问题是响应头重复。如果自己在中间件或Web.config、IIS设置里手动加过Access-Control-Allow-Origin头部,又同时使用了框架的CORS中间件,浏览器会收到两个重复的头部并直接报错。解决原则是只用一种方式加跨域头,删掉手动的那个。
第三个问题是老版本的ASP.NET MVC或Web API项目(基于System.Web.Http的框架),配置方式不同,需要在WebApiConfig.Register中调用config.EnableCors(),并通过[EnableCors]特性指定来源:
// 旧版 ASP.NET Web API 的配置方式
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.EnableCors(new EnableCorsAttribute(
origins: "https://ipipp.com",
headers: "*",
methods: "GET,POST"));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
}
}总结一下,C#中解决跨域问题的要点在于:理解简单请求与预检请求的区别,掌握AddCors注册与UseCors启用的两步配置,注意中间件顺序,生产环境用WithOrigins限定来源而不是全部放开。把这些细节处理好,跨域报错基本都能迎刃而解。
CORS跨域配置C#跨域资源共享ASP.NET Core跨域修改时间:2026-09-11 23:52:45