出于安全原因,浏览器遵循同源策略标准,阻止一些“跨域”请求。CORS (cross-origin sharing standard 跨域资源共享标准)新增了一些 HTTP 首部字段,请求首部字段声明了跨域请求的源的信息,响应首部字段声明了允许哪些源有权跨域请求服务器上的资源,最后由浏览器决定这段跨域是否被允许。
浏览器实际上并没有限制发起跨域请求,跨域请求被正常发送到服务器,服务器并没有对跨域请求做出限制,当 Request 得到 Response 之后,是由浏览器决定阻止不安全的跨域请求,但对于遵循 CORS 的请求,浏览器根据 Request 和 Response 的首部字段来判断这个跨域请求是否安全,以决定是否阻止该请求。
请求报文头和阻止跨域都由浏览器完成,浏览器需要跨域请求时,会自动加上跨域共享请求标头(cross-origin sharing request headers),所以实际前端开发人员再使用跨站点XMLHttpRequest功能时,不需要以编程方式设置任何跨域共享请求标头,而后端程序员需要在响应报头里声明共享请求标头。
CORS规范包含在WHATWG的Fetch Living标准中。较早的规范已发布为W3C建议书。
一些请求会触发 CORS 预检请求,一些请求不会触发。
触发 CORS 预检的请求,浏览器跨域的时候会自动发送一个 OPTIONS
方法的请求,这个请求被称之为预检(preflight),预检会携带 CORS 相关的报头,如果预检的结果是没有访问这个服务器的权限,实际请求不会被发送,如果预检通过了,才会发送实际请求。
不需要预检的请求通常被叫做“简单请求”,简单请求直接发送实际请求,并且实际请求报头就会携带 CORS 相关报头,简单请求只需要携带 Orgin
报头就可以了。满足下面2个条件的请求就是简单请求。
(1) 请求方法是以下3个方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下5个字段,且Content-Type只能以下3个值之一:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type: application/x-www-form-urlencoded、 multipart/form-data、text/plain
为什么需要预检:
假设 yoursite.com 要跨域访问 lucio.cn,发起的是一个简单请求:
Client yoursite.com Server lucio.cn
------------------------------------------>
GET /path HTTP/1.1
Orgin: http://yoursite.com
<------------------------------------------
HTTP/1.1 200 OK
Access-Control-Allow-Origin:http://yoursite.com
简单请求,客户端会在请求中声明 Orgin
报头,服务器的响应需要声明 Access-Control-Allow-Origin
报头,
浏览器对跨域的请求会自动加上 Orgin
报头,用来声明请求的“源”。而服务器的响应需要声明 Access-Control-Allow-Origin
报头,用来告知浏览器允许哪些“源”的跨域请求。
Orgin
和 Access-Control-Allow-Origin
的内容一致则浏览器认为这是一个安全的跨域请求,但要求“一致”就有个问题,有时候有些浏览器发送的是 http://www.yoursite.com,那样会导致跨域失败。
首先我们使用 JavaScript 跨域请求一个没有开启 CORS 的接口
// JavaScript 代码,发起一个请求异步请求
var xhr = new XMLHttpRequest();
xhr.open(‘GET‘, ‘http://lucio.cn/HttpTest/GetDisableCors‘);
xhr.send();
实际请求完成之后,因为服务器响应报头没有声明安全跨域的信息,所以这次交互会被浏览器阻止,浏览器会报错告诉用户”跨域是不被允许的“。报错信息如下:
Access to XMLHttpRequest at ‘https://lucio.cn/HttpTest/GetEnableCors‘ from origin ‘https:/www.yoursite.com‘ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin‘ header is present on the requested resource.
GET https://localhost:44385/HttpTest/GetDisableCors net::ERR_FAILED
实际上这次交互已经完成,服务器正常响应了这次请求,但是因为服务器的这个接口没有声明 CORS 定义的报头字段Access-Control-Allow-Origin
,浏览器认为这是一个不安全的跨域请求,所以被浏览器给阻止。
GET /HttpTest/GetDisableCors HTTP/1.1
Host: lucio.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Origin: http://yoursite.com
Connection: keep-alive
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
Date: Thu, 27 Aug 2020 06:19:12 GMT
Content-Length: 131
除了服务器没有开启 CORS
,还有一种错误就是 Orgin
是 HTTPS
协议要跨域访问 HTTP
协议的目标,也是不允许的。
Mixed Content: The page at ‘https://yoursite.com‘ was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint ‘http://lucio.cn/HttpTest/GetDisableCors‘. This request has been blocked; the content must be served over HTTPS.
Access-Control-Allow-Origin
Access-Control-Allow-Origin
:服务器声明允许被哪些“源”访问,需要指定具体的“源”或者使用“*”通配符表示允许所有来源。
请求会跨域时,支持CORS的浏览器会自动加上相关报头,所以前端程序员不需要做什么。但后端需要添加响应报头字段 Access-Control-Allow-Origin
,以asp.net mvc为例:
public string GetEnableCors()
{
HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", "*");
return "ok";
}
星号表示允许所有源的请求,如果要指定允许来自哪些源的请求,这里要注意源是由“协议”,“主机”和“端口”(默认80)组成的。
HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", "https://yoursite.com");
完成服务器代码之后,再发起跨域请求,这个请求不会被浏览器阻止。
var xhr = new XMLHttpRequest();
xhr.open(‘GET‘, ‘http://lucio.cn/HttpTest/GetEnableCors‘);
xhr.send();
查看 HTTP,我们发现 Response 报头多了一个 Access-Control-Allow-Origin
字段。
GET /HttpTest/GetEnableCors HTTP/1.1
Host: lucio.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Origin: http://yoursite.com
Connection: keep-alive
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
Access-Control-Allow-Origin: *
Date: Thu, 27 Aug 2020 08:32:19 GMT
Content-Length: 130
假设 yoursite.com 要跨域访问 lucio.cn,发起的是一个非简单请求,浏览器会先发起一个 Option
方法的请求检测接口是否可以跨域,这称之为预检。预检的结果是安全的跨域请求,再发起第实际请求,否则会阻止发送实际请求。
浏览器发起的预检信息会自动携带3个请求报头:Orgin
、 Access-Control-Request-Method
和 Access-Control-Request-Headers
。
Client yoursite.com Server licio.com
------------------------------------------>
OPTION /path HTTP/1.1
Orgin: http://yoursite.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Say-Bye,Content-Type
<------------------------------------------
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://yoursite.com
Access-Control-Allow-Methods: POST, GET OPTIONS
Access-Control-Allow-Headers: X-Say-Bye, Content-Type
------------------------------------------>
GET /path HTTP/1.1
Origin: http://yoursite.com
X-Say-Bye: June
Content-Type: application/xml
<------------------------------------------
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://yoursite.com
首先我们使用一个带有自定义报头的请求发送给之前的接口,该接口只声明了 Access-Control-Allow-Origin
报头。
var xhr = new XMLHttpRequest();
xhr.open(‘GET‘, ‘http://lucio.cn/HttpTest/GetEnableCors‘);
xhr.setRequestHeader(‘X-Say-Bye‘, ‘June‘);
xhr.send();
报错了,报错信息提示了:预先响应结果是,报头“X-HELLO”是不被允许的。(不同浏览器报错信息不同)Chrome如下:
Access to XMLHttpRequest at ‘http://lucio.cn/HttpTest/GetEnableCors‘ from origin ‘https://yoursite.com‘ has been blocked by CORS policy: Request header field x-say-bye is not allowed by Access-Control-Allow-Headers in preflight response.
GET http://lucio.cn/HttpTest/GetEnableCors net::ERR_FAILED
显然,光声明 Access-Control-Allow-Origin
还不够。
Access-Control-Allow-Headers
对于非简单请求,response 需要 Access-Control-Allow-Headers
报文头来声明允许接受的 Request Headers。
Access-Control-Allow-Headers
:声明在进行实际跨域请求时允许携带的HTTP标头,用于响应预检请求。
public string GetNonsimpleCorsSayBye()
{
HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", "*");
// 添加 Access-Control-Allow-Headers 请求头,指明了实际请求中允许携带的首部字段。
HttpContext.Response.AppendHeader("Access-Control-Allow-Headers", "X-Say-Bye");
return "GetNonsimpleCorsSayBye";
}
这样服务器端就通过 Access-Control-Allow-Headers
声明了允许的Request Headers,如果需要允许多个则用都好风格,“*”通配符表示全部。然后再发起一次请求就好了。
var xhr = new XMLHttpRequest();
xhr.open(‘GET‘, ‘http://lucio.cn/HttpTest/GetNonsimpleCorsSayBye‘);
xhr.setRequestHeader(‘X-Say-Bye‘, ‘June‘);
xhr.send();
Access-Control-Allow-Methods
非简单请求,浏览器会自动添加一个 Access-Control-Request-Method
请求头部字段,对应的响应头部字段是 Access-Control-Allow-Methods
,由于它的默认值就是 “ GET, POST, HEAD”,所以我们并没有添加。
Access-Control-Allow-Methods
:声明在进行实际跨域请求时允许的一种或多种方法,用于响应预检请求。
public string GetNonSimpleCors()
{
// Access-Control-Allow-Origin 使用通配符“*”应该没啥太大的问题
HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", "*");
// Access-Control-Allow-Headers 不建议使用通配符“*”
HttpContext.Response.AppendHeader("Access-Control-Allow-Headers", "*");
// Access-Control-Allow-Method: GET, POST, HEADER 其实就是默认值
HttpContext.Response.AppendHeader("Access-Control-Allow-Method", "GET, POST, HEADER");
return "GetNonSimpleCors";
}
此时Header使用通配符“*”声明,所以我们已经可以接受任意的headers了,但是不建议这么使用。然后再发起一个请求测试一下:
var body = ‘<?xml version="1.0"?><message>Hello June</message>‘;
var xhr = new XMLHttpRequest();
xhr.open(‘GET‘, ‘http://lucio.cn/HttpTest/GetNonSimpleCors‘);
// 添加一个自定义头部字段,这是非简单请求
xhr.setRequestHeader(‘X-Say-Bye‘, ‘June‘);
// Content-Type: application/xml 也是非简单请求
xhr.setRequestHeader(‘Content-Type‘, ‘application/xml‘);
xhr.send(body);
非简单请求会发起2次请求,再浏览器按 F12
可以打开控制台,再 “网络(Network)”这个Tab可以看到发起了2个请求(但是chrome只能看到一次实际请求,不知道怎么设置)。第一个是“预检”:
OPTIONS /HttpTest/GetNonSimpleCors HTTP/1.1
Host: lucio.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type,x-say-bye
Origin: http://yoursite.com
Connection: keep-alive
Pragma: no-cache
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Method: GET, POST, HEADER
Date: Sat, 29 Aug 2020 16:09:33 GMT
第二个是实际请求:
GET /HttpTest/GetNonSimpleCors HTTP/1.1
Host: lucio.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
X-Say-Bye: June
Content-Type: application/xml
Origin: http://yoursite.com
Connection: keep-alive
Cache-Control: no-cache
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 5.2
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Method: GET, POST, HEADER
Date: Sat, 29 Aug 2020 16:09:33 GMT
Content-Length: 133
.net的服务器可以在web.config的 <customHeaders>
节点内配置,这样每一个response header都会带有这些节点,而不用再每个方法单独使用 HttpContext.Response.AppendHeader
方法
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="X-Say-Bye, Content-Type" />
<add name="Access-Control-Allow-Methods" value="GET, POST, HEADER" />
</customHeaders>
</httpProtocol>
</system.webServer>
https://www.cnblogs.com/gdpw/p/9236661.html
默认情况下,跨站点 XMLHttpRequest
或 Fetch
调用中,浏览器“不”发送凭据。request 必须设置一个特定的标志 withCredentials
才会发送凭证,服务器需要声明 Access-Control-Allow-Credentials
头部字段表示允许跨域发送带凭证的请求。
Client yoursite.com Server lucio.cn
------------------------------------------>
GET /path HTTP/1.1
Orgin: http://yoursite.com
Cookie: account=mongogorilla;level=admin
<------------------------------------------
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://yoursite.com
Access-Control-Allow-Credentials: true
首先创建一个携带 Cookie 的 Response。
public string GetEnableCorsSetCookie()
{
// 设置允许跨域请求
HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", "*");
// 设置 response 的 Cookie
HttpContext.Response.SetCookie(new HttpCookie("account", "mongogorilla"));
return "GetEnableCorsSetCookie";
}
然后通过3种方式访问这个资源:http://lucio.cn/httptext/GetEnableCorsSetCookie
XMLHttpRequest
访问这个资源。XMLHttpRequest
跨源访问这个资源。// 使用 XMLHttpRequest
var xhr = new XMLHttpRequest();
xhr.open(‘GET‘, ‘http://lucio.cn/httptest/GetEnableCorsSetCookie‘);
xhr.send();
通过“开发者工具”查看 Request
,发现只有跨源访问的时候,Request
头部一直都不携带 Cookie 字段。如果想要使用 XMLHttpRequest
跨源访问时候携带 Cookie
头部字段,需要设置 XMLHttpRequest
的属性 withCredentials
为 true。
withCredentials
withCredentials
:指示了是否该使用类似 cookies
, authorization headers
(头部授权)或者TLS
客户端证书这一类资格证书来创建一个跨站点访问控制(cross-site Access-Control
)请求。在同一个站点下使用 withCredentials
属性是无效的。
// 使用 XMLHttpRequest
var xhr = new XMLHttpRequest();
xhr.open(‘GET‘, ‘http://lucio.cn/httptest/GetEnableCorsSetCookie‘);
xhr.withCredentials = true;
xhr.send();
会出现1个警告和2个报错
cookie associated with a cross-site resource at http://lucio.cn/ was set without the `SameSite` attribute. It has been blocked, as Chrome now only delivers cookies with cross-site requests if they are set with `SameSite=None` and `Secure`. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5088147346030592 and https://www.chromestatus.com/feature/5633521622188032.
Access to XMLHttpRequest at ‘http://lucio.cn/httptest/GetEnableCorsSetCookie‘ from origin ‘http://yoursite.com‘ has been blocked by CORS policy: The value of the ‘Access-Control-Allow-Origin‘ header in the response must not be the wildcard ‘*‘ when the request‘s credentials mode is ‘include‘. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
GET http://lucio.cn/httptest/GetEnableCorsSetCookie net::ERR_FAILED
先忽略这个警告,报错的大概意思是 withCredentials = true
的时候,Access-Control-Allow-Origin
不能是 “*” 通配符。所以我们修改这个报头声明。
HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", "http://yoursite.com");
然后再次运行,又报错:
Access to XMLHttpRequest at ‘http://lucio.cn/httptest/GetEnableCorsAllowCredentials‘ from origin ‘http://yoursite.com‘ has been blocked by CORS policy: The value of the ‘Access-Control-Allow-Credentials‘ header in the response is ‘‘ which must be ‘true‘ when the request‘s credentials mode is ‘include‘. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
GET http://lucio.cn/httptest/GetEnableCorsAllowCredentials net::ERR_FAILED
报错大概意思是带凭证的跨域请求必须声明报头字段 Access-Control-Allow-Credentials = true
。
Access-Control-Allow-Credentials
Access-Control-Allow-Credentials
:指示是否对所述请求的响应可以在被暴露credentials标记为真。当用作对预检请求的响应的一部分时,这表明是否可以使用凭据发出实际请求。
public string GetEnableCorsAllowCredentials()
{
// 不太懂,为什么request一定要在response前面,不然设置response,request里也会有这个值。
// 从 request 得到 Cookie
var account = HttpContext.Request.Cookies["account"]?.Value;
var origin = HttpContext.Request.Headers["origin"];
// 声明允许跨域请求
HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", origin);
// 声明允许跨域请求携带凭证
HttpContext.Response.AppendHeader("Access-Control-Allow-Credentials", "true");
// 设置 response 的 Cookie
HttpContext.Response.Cookies.Add(new HttpCookie("account", "mongogorilla"));
if (!string.IsNullOrWhiteSpace(account))
{
return "succeed!Cookie传送成功。" + account;
}
return "failure!Cookie传送失败";
}
再一次发起带 Cookie
的请求
var xhr = new XMLHttpRequest();
xhr.open(‘GET‘, ‘http://lucio.cn/httptest/GetEnableCorsAllowCredentials‘);
xhr.withCredentials = true;
xhr.send();
发现运行成功,但此时浏览器一般都会发出一个警告,这时候不同的浏览器可能会有不同的结果,火狐会发出警告,但是 Request
会携带 Cookie
,而 Chome
会发出警告,并且 Request
不会携带 Cookie
。
A cookie associated with a cross-site resource at http://lucio.cn/ was set without the `SameSite` attribute. It has been blocked, as Chrome now only delivers cookies with cross-site requests if they are set with `SameSite=None` and `Secure`. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5088147346030592 and https://www.chromestatus.com/feature/5633521622188032.
上面是Chrome的警告,大概意思是必须要 SameSite=None
和 Secure
,而我用的是老版本的 .NET MVC
,并且域名没有 https
,所以这个2个属性不方便测试。因为Chrome必须要这2个属性才能跨域携带 Cookie
,而火狐是虽然有警告,但是是可以携带 Cookie
的,所以只能使用火狐的“F12开发者工具”查看 http
:
GET /httptest/GetEnableCorsAllowCredentials HTTP/1.1
Host: lucio.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Origin: http://xialala.com
Connection: keep-alive
Referer: http://xialala.com/
Cookie: account=mongogorilla
Pragma: no-cache
Cache-Control: no-cache
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 5.2
Access-Control-Allow-Origin: http://xialala.com
Access-Control-Allow-Credentials: true
X-AspNet-Version: 4.0.30319
Set-Cookie: account=mongogorilla; path=/
X-Powered-By: ASP.NET
Date: Wed, 02 Sep 2020 03:43:42 GMT
Content-Length: 168
火狐对localhost地址不支持CORS,除此之外没有问题,不需要额外设置来开启CORS。(当前时间为2020年8月24日)
https://stackoverflow.com/questions/25504851/how-to-enable-cors-on-firefox/25507329#25507329
https://stackoverflow.com/questions/17088609/disable-firefox-same-origin-policy
--
https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
原文:https://www.cnblogs.com/luciolu/p/13602154.html