广告位 · 页头(后台-模块管理-新建模块后替换此区域)

C# ASP.NET Core如何配置CORS跨域请求?

在前后端分离的开发模式下,浏览器同源策略会阻止来自不同域名、端口或协议的JavaScript请求。CORS(跨源资源共享)机制通过服务器返回特定的响应头来告知浏览器哪些跨域请求是合法的。ASP.NET Core内置了一套完整的CORS中间件体系,借助AddCors和UseCors两个扩展方法就能轻松完成配置。但在实际项目中,不少开发者对命名策略、默认策略、凭据支持等概念理解不够深入,导致配置看似正确却依然出现跨域错误。

C# ASP.NET Core如何配置CORS跨域请求?

本文会从最基础的全局允许所有源开始,逐步过渡到基于命名策略的精细控制,再到带凭据请求和动态来源校验,完整覆盖C# ASP.NET Core中CORS配置的常见用法和隐藏细节。无论是刚接触跨域问题的新手,还是希望优化现有策略的开发者,都能从中找到可直接落地的代码示例和排查思路。

一、CORS跨域问题的本质与中间件执行顺序

浏览器执行同源策略时,会检查发起请求的页面源(协议、域名、端口)与目标API的源是否一致。如果不同,浏览器会先发送一个预检请求(OPTIONS方法)询问服务器是否允许该跨域操作。服务器需要通过Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers等响应头进行授权。ASP.NET Core的CORS中间件正是负责在请求管道中自动处理这些预检请求,并向实际响应添加对应的响应头。

在配置CORS时,中间件的注册顺序非常关键。UseCors必须放置在UseRouting和UseEndpoints之前,这样才能在路由匹配之前对跨域预检请求做出响应。如果顺序颠倒,预检请求可能直接进入MVC管道而返回404或405状态码,浏览器会认为跨域被拒绝。标准的管道配置写法如下:

var app = builder.Build();
app.UseRouting();
app.UseCors(); // 必须放在UseRouting之后、UseEndpoints之前
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

可以看到,UseCors放在UseRouting之后是为了让路由系统先识别出当前请求是否匹配某个端点,而放在UseEndpoints之前则是为了保证预检请求不会真正执行端点逻辑。注意如果使用最小API(Minimal API),同样遵循这个顺序规则。中间件顺序错误最常见的表现是:生产环境正常,本地开发时跨域预检请求被路由捕获并提示“No resource found”。

二、基础配置:注册CORS服务并启用全局策略

最简单的跨域配置只需要两行代码:在服务容器中调用AddCors,然后在管道中调用UseCors并传入策略名称或直接使用默认策略。如果希望开发阶段允许所有源、所有方法和所有请求头,可以这样写:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAll", policy =>
    {
        policy.AllowAnyOrigin()
              .AllowAnyMethod()
              .AllowAnyHeader();
    });
});
var app = builder.Build();
app.UseRouting();
app.UseCors("AllowAll");
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});
app.Run();

这个示例定义了一个名为“AllowAll”的策略,并允许任意来源、任意HTTP方法以及任意请求头。对于纯开发环境来说非常方便,因为不需要反复修改允许的域名列表。但需要注意,AllowAnyOrigin会把Access-Control-Allow-Origin设置为通配符*,此时如果请求中携带了Cookie或者Authorization头,浏览器会认为该跨域请求不安全,因为凭据和通配符源不能同时使用。这也是很多开发者明明配置了AllowAnyOrigin却依然无法携带Cookie的原因。

如果不需要命名策略,也可以直接使用AddCors注册默认策略,然后调用无参数的UseCors。但默认策略的概念在ASP.NET Core中有些微妙:如果不指定策略名称,UseCors会应用一个不会允许任何跨域请求的空策略,除非你在AddCors中配置了一个名为DefaultPolicy的选项。推荐的做法是始终显式指定策略名称,这样代码可读性更强,也便于后续引入多个策略时做区分。

三、使用命名策略实现精细化的跨域控制

生产环境通常不会允许所有源,而是需要明确列出可信的前端域名。通过命名策略可以在一处集中定义多个不同的跨域规则,例如管理后台和移动端API可能使用不同的策略。下面是一个允许特定来源、特定方法并支持自定义请求头的完整示例:

builder.Services.AddCors(options =>
{
    options.AddPolicy("WebClientPolicy", policy =>
    {
        policy.WithOrigins("https://www.ipipp.com", "https://admin.ipipp.com")
              .WithMethods("GET", "POST", "PUT", "DELETE")
              .WithHeaders("Content-Type", "Authorization", "X-Requested-With")
              .AllowCredentials();
    });
    options.AddPolicy("MobileClientPolicy", policy =>
    {
        policy.WithOrigins("capacitor://localhost", "http://localhost:8080")
              .AllowAnyMethod()
              .AllowAnyHeader()
              .AllowCredentials();
    });
});

在上面的代码中,WithOrigins接收一个字符串数组,每个来源必须写成完整的源格式,包括协议、域名和端口。如果省略端口,浏览器会默认采用80端口(HTTP)或443端口(HTTPS)。AllowCredentials允许跨域请求携带Cookie、HTTP认证信息或客户端证书。一旦调用了AllowCredentials,就不能再使用AllowAnyOrigin,否则会在运行时抛出异常,提示“The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time”。

多个策略可以在不同端点或控制器级别分别应用。除了在全局管道中使用UseCors("策略名"),还可以在控制器或Action上使用[EnableCors("策略名")]特性,或者使用[DisableCors]关闭某个特定端点的跨域支持。这种细粒度控制非常适合微服务架构下针对不同API端点配置不同安全级别的场景。需要强调的是,如果同时使用了全局UseCors和Endpoint级别的EnableCors特性,Endpoint级别的策略会覆盖全局策略。

四、处理带凭据的跨域请求与动态来源验证

带凭据的跨域请求是配置中最容易踩坑的部分。所谓凭据包括Cookie、TLS客户端证书以及HTTP认证信息(如Bearer Token)。当客户端使用fetch或XMLHttpRequest发出带凭据请求时,必须在请求选项中显式设置credentials: 'include'或withCredentials: true。与此同时,服务器端必须同时满足三个条件:设置AllowCredentials、返回一个具体的来源而不是*、并且响应头中不能包含通配符。

正确的服务器端配置如下:

options.AddPolicy("SecureCookiePolicy", policy =>
{
    policy.WithOrigins("https://trusted-frontend.com")
          .AllowCredentials()
          .AllowAnyHeader()
          .AllowAnyMethod();
});

如果来源列表会随着业务扩展频繁变化,或者需要根据请求头、查询参数动态判断是否允许某个来源,可以使用SetIsOriginAllowed方法传入一个自定义委托。这个委托接收一个来源字符串参数,返回布尔值表示是否允许。比如允许所有以.trusted-domain.com结尾的子域名:

options.AddPolicy("DynamicSubdomainPolicy", policy =>
{
    policy.SetIsOriginAllowed(origin =>
    {
        var uri = new Uri(origin);
        return uri.Host.EndsWith(".trusted-domain.com", StringComparison.OrdinalIgnoreCase);
    })
    .AllowCredentials()
    .AllowAnyHeader()
    .AllowAnyMethod();
});

这种方式虽然灵活,但每个请求都会执行一次委托,如果逻辑复杂可能带来性能开销。对于需要更高灵活性的场景(例如从数据库读取允许的来源列表),可以实现ICorsPolicyProvider接口,在GetPolicyAsync方法中根据HttpContext动态生成或选择策略。但大多数项目使用SetIsOriginAllowed已经足够。

五、跨域配置常见错误排查与最佳实践

实际开发中,跨域问题最常见的表现形式是浏览器控制台出现类似“blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present”的错误。排查时首先确认中间件顺序是否正确;其次检查是否同时注册了多个CORS策略但没有在UseCors中指定正确的名称;最后确认请求中是否携带了自定义请求头或非简单方法,导致触发了预检请求,而服务器没有处理OPTIONS请求。

另一个容易忽略的点是:当使用反向代理(如Nginx或IIS)时,预检请求可能被代理层拦截或修改,导致ASP.NET Core根本看不到OPTIONS请求。此时需要在代理配置中确保将OPTIONS请求原样转发到后端,或者在后端手工添加一个处理OPTIONS请求的中间件。建议在开发环境直接使用Kestrel启动并测试跨域策略,减少中间层干扰。

最佳实践方面,强烈建议为开发、测试、生产环境分别配置不同的CORS策略,并通过配置文件读取允许的来源列表,避免硬编码。例如在appsettings.json中存储来源数组,然后在AddCors中遍历添加。另外,不要为了方便在生产环境使用AllowAnyOrigin,尤其是涉及登录态和敏感数据的API。合理利用命名策略和Endpoint级别的特性,能够为不同API端点提供差异化的跨域保护,既保障前后端联调顺畅,又避免将不必要的权限开放给未知来源。

广告位 · 文章底部(后台-模块管理替换此区域)