------------------- ~。。~ 想看优雅调试CORS请求:请下拉到文末---------------
跨域请求
站点A 请求站点B的数据,都可以认为是跨域请求;
由于安全原因,浏览器严格限制通过脚本的跨域请求,阻止某域名下的web应用尝试发送AJAX请求到其他域(http、protocol、port),这个限制被称为同源策略,阻止恶意站点A尝试通过脚本的形式访问站点B的数据。
一句话:减轻互联网上跨域请求实践中站点B的潜在风险。
举个栗子:
你已经登录并正在访问电商B站点, 这是邮件收到一份钓鱼网站的链接,点开该链接,进入该钓鱼A站点,该钓鱼A站点内部有Ajax请求代码,该请求现在可以无声的发起到电商站点B的请求,偷取数据。
该机制通过增加站点B的响应HTTP头,告知浏览器允许特定域名下的web应用能够跨域访问B站点。
举个橘子:
针对以上Http请求观察 浏览器发送和服务端响应的行为:
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[XML Data]
跨域请求方案
CORS是一个服务器允许放松同源策略的W3C标准,服务器可以显式允许同源请求并且拒绝其他的非同源访问,CORS更安全而且相比早期的JSONP技术更灵活。
在完成CORS时:很大程度上体现的是服务端的资源保护策略
CORS协议规定在浏览器和服务端在完成跨域资源共享时的行为:
浏览器: 客户端使用XmlHttpRequest发起Ajax请求,浏览器会自动携带Origin请求头(表示请求来自站点A)
服务端: 服务端有一套完整的CORS策略,针对Origin服务端会在响应头Access-Control-Allow-Origin设定跨域策略,3种结果:
Access-Control-Allow-Origin: * // 允许任意Origin跨域请求
Access-Control-Allow-Origin: <origin> // 这里只能指定一个Origin,一般情况下是请求携带的Origin
Access-Control-Allow-Origin: null // 不允许该Origin跨域请求
- 浏览器会查看Access-Control-Allow-Origin响应头,根据header value判断是否让脚本访问响应体。
预检:
The only allowed methods are: GET HEAD POST
除浏览器自动添加的request header以外,允许某些手动添加的 header:Accept,Content-langugae等
request 中的Content-Type只允许以下值:
application/x-www-form-urlencoded
multipart/form-data
text/plain
不满足以上任意一个条件的基本上都有预检,预检是由浏览器自动检测并发起的。
预检请求首先需要向站点B的资源发送一个 HTTP OPTIONS 请求头,其目的就是为了B站点能判断实际发送的请求是否是安全的(是否是自己允许的请求域名)。
举个大梨子:
const invocation = new XMLHttpRequest(); const url = 'http://bar.other/resources/post-here/'; const body = '<?xml version="1.0"?><person><name>Arun</name></person>'; function callOtherDomain(){ if(invocation) { invocation.open('POST', url, true); invocation.setRequestHeader('X-PINGOTHER', 'pingpong'); invocation.setRequestHeader('Content-Type', 'application/xml'); invocation.onreadystatechange = handler; invocation.send(body); } } ...... // 以上使用POST发送了一个xml,同时自定义了一个request header: X-PINGOTHER, 该跨域请求必定会预检。
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
一旦预检成功,真实的POST就会发起:
POST /resources/post-here/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive X-PINGOTHER: pingpong Content-Type: text/xml; charset=UTF-8 Referer: http://foo.example/examples/preflightInvocation.html Content-Length: 55 Origin: http://foo.example Pragma: no-cache Cache-Control: no-cache <?xml version="1.0"?><person><name>Arun</name></person> HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:40 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://foo.example Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 235 Keep-Alive: timeout=2, max=99 Connection: Keep-Alive Content-Type: text/plain [Some GZIP'd payload]
携带凭据跨域
const invocation = new XMLHttpRequest();
const url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain(){
if(invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
}
// 以上是一个简单请求,并不会触发预检,浏览器会拒绝不包含Access-Control-Allow-Credentials: true跨域响应头的响应,并不允许web内容访问到响应数据。
GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example // 当响应的是一个携带凭据的请求,服务端必须为Access-Control-Allow-Origin响应头指定一个Origin,而不能再用 * 通配符。
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT // 设置新的Cookie
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
[text/plain payload]
- 以上请求头将会携带Cookie请求头,如果响应头Access-Control-Allow-Origin被指定为“*”, 跨域请求将会失败:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials。
- 浏览器会取得Response,但是浏览器会阻止你的代码访问Response内容。
Asp.Net Core 2.1->2.2 迁移指南中也提到这个问题: 如果服务端允许携带凭据跨域,就不允许再设置这种 Access-Control-Allow-Origin:* 响应头,必须明确指定Origin;
app.UseCors(p => p.SetIsOriginAllowed(_ => true) .AllowCredentials() .WithMethods(HttpMethods.Get, HttpMethods.Post, HttpMethods.Put, HttpMethods.Delete) .WithHeaders(HeaderNames.ContentType) .SetPreflightMaxAge(TimeSpan.FromHours(24))); // _()=>true 为任意一个Origin生成对应的Access-Control-Allow-Origin响应头
附:一种优雅地调试 跨域请求 的方法
同源策略针对 脚本跨域请求(HTTP直接跨域请求例如:src外链不存在这样的说法),本次着重探讨脚本跨域请求的验证方式。
直观上:需要在某个服务器域名下构造Ajax请求,过程太繁琐, 需要搭建服务器、构建AJAX脚本请求。
用一种极客的方法测试CORS请求:
-H 标记请求的Header,上例指出了当前发起跨域请求的站点Origin; 你也可以在此指定User-Agent
--verbose 打印整个响应体
-X 指定请求动词
本文部分内容取自:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
--------------如果你觉得文章对你有价值,请点赞或者关注,支持原创势力,蟹蟹--------------~~。。~~--------------~~。。~~----------------