------------------- ~。。~  想看优雅调试CORS请求:请下拉到文末---------------

跨域请求

站点A 请求站点B的数据,都可以认为是跨域请求;

由于安全原因,浏览器严格限制通过脚本的跨域请求,阻止某域名下的web应用尝试发送AJAX请求到其他域(http、protocol、port),这个限制被称为同源策略阻止恶意站点A尝试通过脚本的形式访问站点B的数据

       一句话:减轻互联网上跨域请求实践中站点B的潜在风险。

举个栗子:

你已经登录并正在访问电商B站点, 这是邮件收到一份钓鱼网站的链接,点开该链接,进入该钓鱼A站点,该钓鱼A站点内部有Ajax请求代码,该请求现在可以无声的发起到电商站点B的请求,偷取数据。

 
实际架构设计中需要站点A能够发起跨域请求到站点B, 基于客观需要,提出了跨域共享资源Cross-Origin Resource Sharing的技术机制。

该机制通过增加站点B的响应HTTP头,告知浏览器允许特定域名下的web应用能够跨域访问B站点。

举个橘子:

记一次优雅调试CORS请求的方法-LMLPHP

针对以上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协议规定在浏览器和服务端在完成跨域资源共享时的行为:

  1. 浏览器: 客户端使用XmlHttpRequest发起Ajax请求,浏览器会自动携带Origin请求头(表示请求来自站点A)

  2. 服务端: 服务端有一套完整的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跨域请求

  3.  浏览器会查看Access-Control-Allow-Origin响应头,根据header value判断是否让脚本访问响应体

预检:

“简单请求”(GET,HEAD,POST)不会触发预检(preflight), “简单请求”必须同时满足以下条件:
  • 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, 该跨域请求必定会预检。

记一次优雅调试CORS请求的方法-LMLPHP

 观察完整的预检流程:
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]
  • 浏览器会取得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

--------------如果你觉得文章对你有价值,请点赞或者关注,支持原创势力,蟹蟹--------------~~。。~~--------------~~。。~~----------------

01-30 21:31