16. CORS 跨域
16.1 什么是跨域
简单来说,当一个请求 url
的协议、域名、端口三者之间任意一个与当前页面 url
不同即为跨域。那为什么会出现跨域问题呢?
出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web
是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的 javascript 脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。
16.2 有跨域行为示例
当前页面 url | 被请求页面 url | 是否跨域 | 原因 |
---|---|---|---|
http://www.baiqian.ltd/ | http://www.baiqian.ltd/index.html | 否 | 同源(协议、域名、端口号相同) |
http://www.baiqian.ltd/ | https://www.baiqian.ltd/index.html | 跨域 | 协议不同(http/https) |
http://www.baiqian.ltd/ | http://www.baidu.com/ | 跨域 | 主域名不同(baiqian.ltd/baidu.com) |
http://furion.baiqian.ltd/ | http://fur.baiqian.ltd/ | 跨域 | 子域名不同(furion/fur) |
http://www.baiqian.ltd:8080/ | http://www.baiqian.ltd:7001/ | 跨域 | 端口号不同(8080/7001) |
16.3 什么是 CORS
跨源资源共享 (CORS
) :
- 是一种
W3C
标准,可让服务器放宽相同的源策略。 - 不是一项安全功能,
CORS
放宽security
。API
不能通过允许CORS
来更安全。 有关详细信息,请参阅 CORS 工作原理。 - 允许服务器明确允许一些跨源请求,同时拒绝其他请求。
- 比早期的技术(如
JSONP
)更安全且更灵活。
16.4 如何使用
16.4.1 添加 CORS
服务
启用跨域 Cors
支持 首先添加 CorsAccessor
服务,如:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Furion.Web.Core
{
[AppStartup(700)]
public sealed class FurWebCoreStartup : AppStartup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCorsAccessor();
// ...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseCorsAccessor();
// ...
}
}
}
services.AddCorsAccessor();
需在 services.AddControllers()
之前注册。
app.UseCorsAccessor();
需在 app.UseRouting();
和 app.UseAuthentication();
之间注册。
16.4.2 配置允许跨域域名
默认情况下,Furion
允许所有域名来源访问,也就是无需配置任何来源域名,另外前端也需要设置请求参数:withCredentials:false
如果需要指定特定域名,则添加以下配置即可:
{
"CorsAccessorSettings": {
"PolicyName": "自定义跨域策略名",
"WithOrigins": ["http://localhost:4200", "http://furion.baiqian.ltd"]
}
}
16.5 CorsAccessorSettings
配置
CorsAccessorSettings
PolicyName
:跨域策略名,string
类型,必填,默认App.Cors.Policy
WithOrigins
:允许跨域的域名列表,string[]
类型,默认*
WithHeaders
:请求表头,没有配置则允许所有表头,string[]
类型WithExposedHeaders
:设置客户端可获取的响应标头,string[]
类型,默认["access-token", "x-access-token"]
- 默认情况下,若后端输出特定的响应头
Key
,那么需将该Key
配置在数组中
- 默认情况下,若后端输出特定的响应头
WithMethods
:设置跨域允许请求谓词,没有配置则允许所有,string[]
类型AllowCredentials
:是否允许跨域请求中的凭据,bool
类型,默认值true
SetPreflightMaxAge
:设置预检过期时间,int
类型,默认值24小时
FixedClientToken
:是否默认配置WithExposedHeaders
,bool
类型,默认true
SignalRSupport
:是否启用SignalR
跨域支持,bool
类型,默认false
16.6 前端不能读取响应头注意事项
有时候,我们通过 ajax
或者 axios
第三方库无法读取响应头自定义信息,这时需要响应报文中公开特定 Header
才能放行,如:Access-Control-Expose-Headers: xxxxx
,所以,需要添加以下配置:
{
"CorsAccessorSettings": {
"WithExposedHeaders": ["access-token","x-access-token"]
}
}
需要获取哪个头,就在 WithExposedHeaders
数组中配置即可。如果使用 ajax
可以通过 xhr.getResponseHeader(key)
或 xhr.getAllResponseHeaders()
获取配置的 key
。
特别情况下不能请求,可以考虑设置 withCredentials: false
。
16.7 使用 $.ajax
前端注意事项
使用 Jquery
前端请求可以参考以下配置:
$.ajax({
url: "https://localhost:5001/api/system/getdata",
type: "GET",
xhrFields: {
withCredentials: false // 如果是https请求,可以试试 true
},
crossDomain: true,
success: function (res) {
render(res);
}
});
在本地开发阶段,请求如果出现 Access to XMLHttpRequest...has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header...
错误,请确保 ajax
的 url
参数是正确的,通常错误的做法是:
- 使用了
127.0.0.1
而不是localhost
主机地址 - 使用
http
而不是https
主机协议 - 使用了
5000
而不是5001
主机端口
16.8 禁用跨域
有时候,我们希望某个方法不检查跨域请求,可以在 Action
中贴 [DisableCors]
特性即可。
16.9 SignalR
跨域问题
SignalR
实现跨域需要满足下面几个条件:
- 允许特定的预期来源,允许任何来源是可行的,但不安全或不推荐使用
- 必须允许使用 HTTP 方法
GET
和POST
- 为了使基于
cookie
的粘滞会话正常工作,必须允许使用凭据,即使未使用身份验证,也必须启用它们。
官方文档说明 https://docs.microsoft.com/zh-cn/aspnet/core/signalr/security?view=aspnetcore-6.0
在 Furion 4.1.4+
版本已修正 SignalR
跨域问题,只需要启用 SignalRSupport
配置即可,如:
{
"CorsAccessorSettings": {
"SignalRSupport": true
}
}
16.10 静态资源跨域问题
有时候我们可能通过前端 XMLHttpRequest/Ajax/Fetch
方式加载静态资源,这时可能出现跨域问题,可以通过以下配置解决:
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = (stf) =>
{
stf.Context.Response.Headers["Access-Control-Allow-Origin"] = "*";
stf.Context.Response.Headers["Access-Control-Allow-Headers"] = "*";
}
});
如果已经注册了 app.UseStaticFiles()
,则只需要传递 new StaticFileOptions{ ... }
参数即可,避免多次注册 app.UseStaticFiles()
。
16.11 反馈与建议
给 Furion 提 Issue。
想了解更多 跨域请求
知识可查阅 ASP.NET Core - 启用跨域请求 章节。