如何使用 .NET Core 2 配置 Angular 6 以允许来自任何主机的 CORS?

Posted

技术标签:

【中文标题】如何使用 .NET Core 2 配置 Angular 6 以允许来自任何主机的 CORS?【英文标题】:How to Configure Angular 6 with .NET Core 2 to allow CORS from any host? 【发布时间】:2019-06-02 18:49:21 【问题描述】:

我有一个在 4200 本地运行的简单 Angular 应用程序,我正在测试一个使用 .net 核心在 5000 本地运行的 web api。我的 startup.cs 已正确配置 CORS,以允许我相信的一切。

在 ConfigureServices 部分我有: services.AddCors();

在配置部分我有:

app.UseCors(options => options.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());

但是,当我尝试点击我的 webapi 时,我仍然会在浏览器中看到它。

来自原点“http://localhost:4200”已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:预检请求不允许重定向。

我查看了其他答案,它们似乎都有我尝试过的这种或类似的变体,没有任何变化。不知道我还需要做什么?

【问题讨论】:

【参考方案1】:

可能的问题:

在您的startup.cs.Configure() 方法中,app.UseCors() 是否在app.useMVC() 之前? Http 请求 URL 是否包含尾部斜杠 (/)? Http 请求是否包含凭据? 浏览器是否接收到来自 API 的 Http 响应? 响应是否包含“Access-Control-Allow-Origin”标头? 当您从 Postman 发送请求时,Http 响应是否包含“Access-Control-Allow-Origin”标头以及包含数据的正文?

陷阱

Firefox 需要为您的 API 安装证书才能使用 HTTPS 协议发送 Http 请求。

使用 Postman 和浏览器开发工具测试您的 API。注意 2 Http 请求。 Http 200 是一个“预测试”,用于查看可用的 CORS 选项。

如果您的 API (.NET) 抛出 HTTP 500 (Internal Server Error),它将返回开发人员异常页面,并且 Postman 将显示 “no ‘Access-Control-Allow-Origin’ header is present on the requested resource” 消息 - 这是误导。 这种情况下的实际问题是开发者异常页面无法从服务器返回到运行在不同 Origin 上的客户端(浏览器)。

我想恭敬地指出以下几点:

CORS 规范指出,如果 Access-Control-Allow-Credentials 标头存在,则将来源设置为“*”(所有来源)无效。 AllowAnyOrigin() 不建议在生产环境中使用,除非您打算允许任何人使用您的 API 并且您不会实施凭据。 在使用多个 CORS 策略时,您不需要在控制器级别使用 EnableCors 属性配置 CORS。 使用 ASP.NET Core 配置多个 CORS 策略很容易(请参阅下面的教程

以下文章值得回顾:

ASP.NET Core 2.2 does not permit allowing credentials with AllowAnyOrigin()

Enable Cross-Origin Requests (CORS) in ASP.NET Core

关键点(tldr;):

CORS 中间件必须位于应用配置中任何已定义的端点之前。 指定的 URL 必须没有尾部斜杠(/)。 如果浏览器发送凭据但响应不包含 有效的 Access-Control-Allow-Credentials 标头,浏览器没有 将响应暴露给应用,跨域请求失败。 CORS 规范还规定将源设置为“*”(所有 origins) 如果 Access-Control-Allow-Credentials 标头是无效的 展示。 如果浏览器支持 CORS,它会自动为跨域请求设置这些标头。 如果响应不包含 Access-Control-Allow-Origin 标头,则跨域请求失败。

CORS 教程:(2)Angular 客户端 + ASP.NET Core

    创建 Visual Studio 解决方案

    md c:\s\a cd c:\s\a c:\s\a>dotnet new sln -n 解决方案名称

    创建 ASP.NET Core 项目

    c:\s\a>md s c:\s\a>cd s c:\s\a\s>dotnet new webapi -o api -n api

    API 启动设置和 CORS 配置

launchSettings.json

将开发配置文件克隆到暂存配置文件


"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": 
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iis": 
    "applicationUrl": "http://localhost:myIISApiPortNumber",
    "sslPort": myIISApiSSLPortNumber
,
"iisExpress": 
    "applicationUrl": "http://localhost:myIISExpressApiPortNumber",
    "sslPort": myIISExpressApiSSLPortNumber

,
"profiles": 
"Development (IIS Express)": 
    "commandName": "IISExpress",
    "launchBrowser": true,
    "launchUrl": "api/values",
    "environmentVariables": 
    "ASPNETCORE_ENVIRONMENT": "Development"
    
,
"Staging (IIS Express)": 
    "commandName": "IISExpress",
    "launchBrowser": true,
    "launchUrl": "api/values",
    "applicationUrl": "https://localhost:myIISExpressApiSSLPortNumber;http://localhost:myIISExpressApiPortNumber",
    "environmentVariables": 
    "ASPNETCORE_ENVIRONMENT": "Staging"
    
,
"Production (IIS)": 
    "commandName": "IIS",
    "launchBrowser": true,
    "launchUrl": "api/values",
    "environmentVariables": 
    "ASPNETCORE_ENVIRONMENT": "Production"
    ,
    "applicationUrl": "https:localhost:myIISApiSSLPortNumber;http://localhost:myIISApiPortNumber"



startup.cs

添加 CORS 配置

public class Startup

    public IConfiguration Configuration  get; 

    public IServiceCollection _services  get; private set; 

    public Startup(IConfiguration configuration)
    
        Configuration = configuration;
    

    public void ConfigureServices(IServiceCollection services)
    
        _services = services;
        RegisterCorsPolicies();
        services.AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    
        if (env.IsDevelopment())
        
            app.UseCors("DevelopmentCorsPolicy");
            app.UseDeveloperExceptionPage();
        
        else if (env.IsStaging())
        
            app.UseCors("StagingCorsPolicy");
        
        else
        
            app.UseCors("ProductionCorsPolicy");
            app.UseHsts();
        

        app.UseHttpsRedirection();
        app.UseMvc(); // CORS middleware must precede any defined endpoints
    

    private void RegisterCorsPolicies()
    
        string[] localHostOrigins = new string[] 
        "http://localhost:4200", "http://localhost:3200";

        string[] stagingHostOrigins= new string[] 
        "http://localhost:4200";

        string[] productionHostOrigins = new string[] 
        "http://yourdomain.net", "http://www.yourdomain.net",
        "https://yourdomain.net", "https://www.yourdomain.net";

        _services.AddCors(options =>    // CORS middleware must precede any defined endpoints
        
            options.AddPolicy("DevelopmentCorsPolicy", builder =>
            
                builder.WithOrigins(localHostOrigins)
                        .AllowAnyHeader().AllowAnyMethod();
            );
            options.AddPolicy("StagingCorsPolicy", builder =>
            
                builder.WithOrigins(stagingHostOrigins)
                        .AllowAnyHeader().AllowAnyMethod();
            );
            options.AddPolicy("ProductionCorsPolicy", builder =>
            
                builder.WithOrigins(productionHostOrigins)
                        .AllowAnyHeader().AllowAnyMethod();
            );
        //options.AddPolicy("AllowAllOrigins",
        //    builder =>
        //    
        // WARNING: ASP.NET Core 2.2 does not permit allowing credentials with AllowAnyOrigin() 
        // cref: https://docs.microsoft.com/en-us/aspnet/core/migration/21-to-22?view=aspnetcore-2.2&tabs=visual-studio
            //        builder.AllowAnyOrigin()
            //               .AllowAnyHeader().AllowAnyMethod();
            //    );
            //options.AddPolicy("AllowSpecificMethods",
            //    builder =>
            //    
            //        builder.WithOrigins(productionHostOrigins)
            //               .WithMethods("GET", "POST", "HEAD");
            //    );
            //options.AddPolicy("AllowSpecificHeaders",
            //    builder =>
            //    
            //        builder.WithOrigins(productionHostOrigins)
            //               .WithHeaders("accept", "content-type", "origin", "x-custom-header");
            //    );
            //options.AddPolicy("ExposeResponseHeaders",
            //    builder =>
            //    
            //        builder.WithOrigins(productionHostOrigins)
            //               .WithExposedHeaders("x-custom-header");
            //    );
            //options.AddPolicy("AllowCredentials",
            // WARNING: ASP.NET Core 2.2 does not permit allowing credentials with AllowAnyOrigin() cref: https://docs.microsoft.com/en-us/aspnet/core/migration/21-to-22?view=aspnetcore-2.2&tabs=visual-studio
            //    builder =>
            //    
            //        builder.WithOrigins(productionHostOrigins)
            //               .AllowCredentials();
            //    );
            //options.AddPolicy("SetPreflightExpiration",
            //    builder =>
            //    
            //        builder.WithOrigins(productionHostOrigins)
            //               .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
            //    );
        );
    

    使用 开发 (IIS Express) 配置文件启动调试 API

ValuesController.Get()上设置断点

    使用 Postman 测试 API:

    https://localhost:myApiPortNumber/api/values

    Access-Control-Allow-Origin 标头和值应作为响应

    创建 Angular 应用程序

    c:\s\a\s>ng new Spa1 --routing(会自动创建Spa文件夹)

    启动 Spa1 应用程序

    c:\s\a\s>cd Spa1 c:\s\a\s\Spa1>Ng 发球

    浏览到http://localhost:4200/

    Spa1 应该成功启动

    在 Spa1 中实现 COR

app.module.ts

导入 HttpClientModule
import  HttpClientModule  from '@angular/common/http';

import  BrowserModule  from '@angular/platform-browser';
import  NgModule  from '@angular/core';

import  AppRoutingModule  from './app-routing.module';
import  AppComponent  from './app.component';

@NgModule(
  declarations: [
    AppComponent
  ],
  imports: [
    HttpClientModule,
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
)
export class AppModule  

app.component.ts

导入 HttpClient

使用 CORS 请求添加 getValues() 方法

import  Component, OnInit  from '@angular/core';
import  HttpClient  from '@angular/common/http';

@Component(
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
)

export class AppComponent implements OnInit 
  title = 'Spa1';
  values: any;
  apiUrl: string = environment.apiUrl;
  valuesUrl = this.apiUrl + "values";

  constructor(private http: HttpClient)  

  ngOnInit() 
    this.getValues();
  
  getValues() 
    this.http.get(this.valuesUrl).subscribe(response => 
      this.values = response;
  , error => 
  console.log(error);
);


app.component.html

<div style="text-align:center">
  <h1>
    Welcome to  title !
  </h1>
</div>
<h2>Values</h2>
<p *ngFor="let value of values">
  value
</p>
<router-outlet></router-outlet>

environment.ts

export const environment = 
  production: false,
  apiUrl: 'https://localhost:myApiPortNumber/api/'
;

    启动 Spa1 应用程序

    c:\s\a\s\Spa1>Ng 发球

    浏览到http://localhost:4200/

    Chrome 和 Edge:现在应该可以成功发出 COR 请求 Safari:我没有测试过 Firefox:由于证书不受信任,可能会阻止请求。

修复 Firefox 阻塞的一种方法:

火狐 |选项 |隐私与安全 |安全 |证书 | [查看证书]:

证书管理器 | [添加例外]:

   add localhost

CORS 测试

    克隆 Spa1

    c:\s\a\s>xcopy /s /i Spa1 Spa2

    重构 Spa2 的标题

app.component.ts

export class AppComponent implements OnInit 
  title = 'Spa2';

    端口 3200

    上启动 Spa2 应用程序 c:\s\a\s\Spa2>ng 服务 --port 3200

    浏览到http://localhost:3200/

    值数组应呈现在网页上

    使用开发 (IIS Express) 配置文件停止调试 API

    使用 Staging (IIS Express) 配置文件开始调试 API

    浏览到http://localhost:4200/

    值数组应该在网页上呈现

    浏览到http://localhost:3200/

    值数组不应呈现在网页上。

    使用开发者工具检查 Http 响应:

    Access-Control-Allow-Origin 标题和值不应作为响应

【讨论】:

我已经为此浪费了一整天的时间。感谢那。解释得很好!【参考方案2】:

在方法ConfigureServices中添加如下代码sn-p。编辑此项以仅允许自定义标题。

// Add service and create Policy with options
        services.AddCors(options =>
        
            options.AddPolicy("CorsPolicy",
                builder => builder.AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader()
                .AllowCredentials()
                );
        );

在配置方法中添加以下内容

app.UseCors("CorsPolicy");

将“EnableCors”属性添加到控制器。

[EnableCors("CorsPolicy")]

【讨论】:

谢谢!我没有意识到我还需要将 EnableCors 属性添加到控制器。我做到了,一切正常。 如果全局启用,则无需将 EnableCors 属性添加到控制器。 @randydaddis 如果我们有多个政策,这是必需的,如果我错了,请纠正我。 @KrishnaMohan 我能够向未配置“EnableCors”属性的控制器(从 ControllerBase 派生)发出 COR 请求。请参阅下面我的答案中的 Startup.cs 类,了解我全局配置 CORS 策略的方式。 你是救命稻草【参考方案3】:

在服务中注册 cors

services.AddCors(options =>
            
                options.AddPolicy("CorsPolicy",
                    builder => builder
                    .SetIsOriginAllowed((host) => true)
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials());
            );

添加 cors 中间件

app.UseCors("CorsPolicy");

【讨论】:

【参考方案4】:

我遇到了同样的问题,但我的情况是 windowsauthentification

@RandyDaddis 为我指明了正确的方向。 如果设置了身份验证,则不能使用“*”。

但这并不能解决漏洞问题(对我来说,'*' 不是一个好的选择)。

我不得不从 windowsAuthentication only 更改为 混合模式(其中还启用了 anonymousAuthentication),因为 preflight 没有发送凭据。

这已经是asp 讨论的问题。

重要的是您将其添加到 ConfigureServices 函数中的 startup.cs 中,以确保您的所有控制器仍在授权策略下:

 ...
 services.AddAuthentication(IISDefaults.AuthenticationScheme);
 services.AddMvc(options =>
        
            var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
            options.Filters.Add(new AuthorizeFilter(policy));
        )...

【讨论】:

以上是关于如何使用 .NET Core 2 配置 Angular 6 以允许来自任何主机的 CORS?的主要内容,如果未能解决你的问题,请参考以下文章

.net core SPA Angular中的脚手架管理员CRUD

如何在 ASP.NET Core 2.0 中根据路由配置服务身份验证

如何在 .NET Core 2.2 WebApi 和 Azure AD 2.0 中配置 Swagger?

asp.net core1.x/asp.net core2.0中如何加载多个配置文件

如何在 asp.net core 2.1 中使用 net.tcp 服务

如何使用 .NET Core 2 中的 Razor Pages 更改起始页?