本文介绍如何在 ASP.NET Core 的应用程序中启用 CORS。

MOZILLA CORS 一文。

跨源资源共享(CORS):

  • 是一种 W3C 标准,可让服务器放宽相同的源策略。
  • CORS 的工作原理。
  • 允许服务器明确允许一些跨源请求,同时拒绝其他请求。
  • JSONP)更安全且更灵活。

如何下载)

同一原点

RFC 6454),则它们具有相同的源。

这两个 Url 具有相同的源:

  • https://example.com/foo.html
  • https://example.com/bar.html

这些 Url 的起源不同于前两个 Url:

  • https://example.net – 个不同的域
  • https://www.example.com/foo.html – 个不同的子域
  • http://example.com/foo.html – 个不同的方案
  • https://example.com:9000/foo.html – 个不同端口

比较来源时,Internet Explorer 不会考虑该端口。

具有命名策略和中间件的 CORS

以下代码通过指定源为整个应用启用 CORS:

 
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(MyAllowSpecificOrigins,
            builder =>
            {
                builder.WithOrigins("http://example.com",
                                    "http://www.contoso.com");
            });
        });

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseCors(MyAllowSpecificOrigins); 

        app.UseHttpsRedirection();
        app.UseMvc();
    }
}

 

前面的代码:

  • 策略名称为任意名称。
  • UseCors 扩展方法,这将启用 CORS。
  • 配置选项,如 WithOrigins

@No__t-0 方法调用将 CORS 服务添加到应用的服务容器:

 
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy(MyAllowSpecificOrigins,
        builder =>
        {
            builder.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

 

CORS 策略选项。

@No__t-0 方法可以链接方法,如以下代码所示:

 
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy(MyAllowSpecificOrigins,
        builder =>
        {
            builder.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

 

如果 URL 以 / 终止,比较将返回 false,并且不返回任何标头。

将 CORS 策略应用到所有终结点

以下代码通过 CORS 中间件将 CORS 策略应用到所有应用终结点:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // Preceding code ommitted.
    app.UseRouting();

    app.UseCors();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });

    // Following code ommited.
}

 

 警告

配置不正确将导致中间件停止正常运行。

在 Razor Pages、控制器和操作方法中启用 cors,以在页面/控制器/操作级别应用 cors 策略。

测试 CORS 。

 

使用终结点路由启用 Cors

使用终结点路由,可以根据每个终结点启用 CORS,使用 @no__t 的扩展方法集。

app.UseEndpoints(endpoints =>
{
  endpoints.MapGet("/echo", async context => context.Response.WriteAsync("echo"))
    .RequireCors("policy-name");
});

 

同样,也可以为所有控制器启用 CORS:

app.UseEndpoints(endpoints => { endpoints.MapControllers().RequireCors("policy-name"); }); 

使用属性启用 CORS

@No__t-0 特性可为选定的终结点(而不是所有终结点)启用 CORS。

 指定策略。

@No__t-0 特性可应用于:

  • Razor 页 PageModel
  • 控制器
  • 控制器操作方法

使用同一个应用中的 [EnableCors] 特性或中间件。

下面的代码将不同的策略应用于每个方法:

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors]        // Default policy.
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        switch (id)
        {
            case 1:
                return "green widget";
            case 2:
                return "red widget";
            default:
                return NotFound();
        }
    }
}

 

 的策略:

 
public class StartupMultiPolicy
{
    public StartupMultiPolicy(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddDefaultPolicy(
                builder =>
                {
                   
                    builder.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });

            options.AddPolicy("AnotherPolicy",
                builder =>
                {
                    builder.WithOrigins("http://www.contoso.com")
                                        .AllowAnyHeader()
                                        .AllowAnyMethod();
                });

        });

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseMvc();
    }
}

 

禁用 CORS

@No__t-1DisableCors @ no__t-2属性对控制器/页模型/操作禁用 CORS。

CORS 策略选项

本部分介绍可在 CORS 策略中设置的各种选项:

CORS 如何工作部分。

设置允许的来源

AllowAnyOrigin 不安全,因为任何网站都可以向应用程序发出跨域请求。

 备注

使用这两种方法配置应用时,CORS 服务将返回无效的 CORS 响应。

预检请求部分。

SetIsOriginAllowedToAllowWildcardSubdomains – 将策略的 @no__t 设置为在评估是否允许源时允许源与配置的通配符域匹配的函数。

 
options.AddPolicy("AllowSubdomain",
    builder =>
    {
        builder.WithOrigins("https://*.example.com")
            .SetIsOriginAllowedToAllowWildcardSubdomains();
    });

 

设置允许的 HTTP 方法

AllowAnyMethod:

  • 允许任何 HTTP 方法:
  • 预检请求部分。

设置允许的请求标头

WithHeaders 并指定允许的标头:

 
options.AddPolicy("AllowHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

 

AllowAnyHeader:

 
options.AddPolicy("AllowAllHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .AllowAnyHeader();
    });

预检请求部分。

仅当在 Access-Control-Request-Headers 中发送的标头与 WithHeaders 中指定的标头完全匹配时,才可以使用 CORS 中间件策略与 WithHeaders 指定的特定标头匹配。

例如,考虑按如下方式配置的应用:

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl)); 

HeaderNames):

Access-Control-Request-Headers: Cache-Control, Content-Language

因此,浏览器不会尝试跨域请求。

设置公开的响应标头

W3C 跨域资源共享(术语):简单的响应标头。

默认情况下可用的响应标头包括:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

WithExposedHeaders:

 
options.AddPolicy("ExposeResponseHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .WithExposedHeaders("x-custom-header");
    });

 

跨域请求中的凭据

若要使用跨域请求发送凭据,客户端必须将 XMLHttpRequest.withCredentials 设置为 true

直接使用 @no__t:

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

 

使用 jQuery:

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

 

提取 API:

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

 

AllowCredentials:

 
options.AddPolicy("AllowCredentials",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .AllowCredentials();
    });

 

HTTP 响应包含一个 @no__t 0 的标头,该标头通知浏览器服务器允许跨源请求的凭据。

如果浏览器发送凭据,但响应不包含有效的 @no__t 0 标头,则浏览器不会向应用程序公开响应,而且跨源请求会失败。

另一个域中的网站可以代表用户将登录用户的凭据发送给该应用程序,而无需用户的知识。

 (所有源)是无效的。

预检请求

如果满足以下条件,浏览器可以跳过预检请求:

  • 请求方法为 GET、HEAD 或 POST。
  • 应用未设置 AcceptAccept-LanguageContent-LanguageContent-Type 或 @no__t 为的请求标头。
  • @No__t 的标头(如果已设置)具有以下值之一:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

规则不适用于浏览器可以设置的标头,如 User-AgentHost 或 Content-Length

下面是预检请求的示例:

OPTIONS https://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: https://myclient.azurewebsites.net
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: accept, x-my-custom-header
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Content-Length: 0

它包括两个特殊标头:

  • Access-Control-Request-Method:将用于实际请求的 HTTP 方法。
  • 如前文所述,这不包含浏览器设置的标头,如 User-Agent

CORS 预检请求可能包括一个 @no__t 0 标头,该标头向服务器指示与实际请求一起发送的标头。

WithHeaders:

 options.AddPolicy("AllowHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

 

AllowAnyHeader:

 
options.AddPolicy("AllowAllHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .AllowAnyHeader();
    });

 

AllowAnyHeader)以外的任何内容,则至少应包含 AcceptContent-Type 和 @no__t,以及要支持的任何自定义标头。

下面是针对预检请求的示例响应(假定服务器允许该请求):

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: https://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 20 May 2015 06:33:22 GMT

如果预检请求成功,则浏览器发送实际请求。

因此,浏览器不会尝试跨域请求。

设置预检过期时间

SetPreflightMaxAge:

options.AddPolicy("SetPreflightExpiration",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
    });

 

CORS 如何工作

CORS请求中发生的情况。

  • CORS 是一种 W3C 标准,可让服务器放宽相同的源策略。
    • 阻止跨站点脚本(XSS) ,并对启用了 CORS 的站点执行跨站点请求,以窃取信息。
  • API 不能通过允许 CORS 来更安全。
    • 例如,以下任何工具都将显示服务器响应:
  • 获取 API请求,否则将被禁止。
    • 允许跨源加载脚本。

若要启用 CORS,无需自定义 JavaScript 代码。

@No__t 的标头是必需的,并且必须与主机不同。

GET https://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: https://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: https://myclient.azurewebsites.net
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net

,表示允许任何源:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: https://myclient.azurewebsites.net
Date: Wed, 20 May 2015 06:27:30 GMT
Content-Length: 12

Test message

即使服务器返回成功的响应,浏览器也不会将响应提供给客户端应用程序。

 

测试 CORS

测试 CORS:

  1. 下载该示例。
  2. 例如:
 
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    // Shows UseCors with CorsPolicyBuilder.
    app.UseCors(builder =>
    {
        builder.WithOrigins("http://example.com",
                            "http://www.contoso.com",
                            "https://localhost:44375",
                            "https://localhost:5001");
    });

    app.UseHttpsRedirection();
    app.UseMvc();
}

 

 警告

下载示例代码的示例应用。

  1. 可以在与 API 项目相同的解决方案中创建 web 应用。
  2. 将以下突出显示的代码添加到索引 cshtml文件中:
 
@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="text-center">
    <h1 class="display-4">CORS Test</h1>
</div>

<div>
    <input type="button" value="Test" 
           onclick="requestVal('https://<web app>.azurewebsites.net/api/values')" />
    <span id='result'></span>
</div>

<script>
    function requestVal(uri) {
        const resultSpan = document.getElementById('result');

        fetch(uri)
            .then(response => response.json())
            .then(data => resultSpan.innerText = data)
            .catch(error => resultSpan.innerText = 'See F12 Console for error');
    }
</script>
  1. 在上面的代码中,将 url: 'https://<web app>.azurewebsites.net/api/values/1', 替换为已部署应用的 URL。

  2. 部署到 Azure。

  3. 使用 F12 工具查看错误消息。

  4. 例如,在 Visual Studio 中运行。

  5. 根据浏览器,你会收到类似于以下内容的错误(在 F12 工具控制台中):

    • 使用 Microsoft Edge:

      SEC7120: [CORS] 源 https://localhost:44375 在 @no__t 上找不到跨源资源的访问控制允许源响应标头中的 https://localhost:44375

    • 使用 Chrome:

如果请求不是基于 Origin 标头的值域的,则:

  • 不需要 CORS 中间件来处理请求。
  • 不会在响应中返回 CORS 标头。

相关文章: