可能的问题:
- 在您的
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 中实现 COR
app.module.ts
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
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 标题和值不应作为响应