Summed up the front cross-domain solutions

Foreword

Front and rear ends are often encountered when dealing with cross-domain data exchange problem, then what is the cross-domain? What cross-domain methods are? How to solve cross-domain appears it?

First, what is the cross-domain?

First, understand cross-domain origin policy to understand, it is a safe browser js restrictions imposed, if the lack of the same origin policy, the browser is vulnerable to XSS, CSFR, SQL injection attacks. The so-called homology means the agreement, the domain name, the port must be the same. Browser-origin policy must follow when requesting data, all the URL protocol for sending requests, the domain name, it is not the same among the three ports, is called cross-domain.

                                   

And among the same-origin policy, what it will have the following restrictions:

  • Cookie, LocalStorage, IndexedDB storage and other content
  • DOM node
  • AJAX request can not be transmitted

However, the following three labels are allowed to load cross-domain resources:

  1. img src attribute of: <img src = XXX>
  2. href attribute of the link tag: <link href = XXX>
  3. script src attribute: <script src = XXX>

Common cross-domain scenario above chart has been said, the protocol , the domain name , the port number three, if not one of them will lead to the same phenomenon across domains.

 

The two points of special note:

First: If the problem is cross-domain protocols and ports caused by the "front desk" it is powerless.

Second: the cross-domain problem, by simply "URL header" identifies the domain name and will not be judged whether the IP address corresponding to the same. "URL of the first" can be understood as "protocol, domain names and ports must match" .

Here you may have a question: request cross-domain, then the request is not sent out in the end?

Cross-domain request is not unable to go, the request can be sent out, the server to receive the request and return to normal result, but the result was the browser blocks . You might question obviously can initiate cross-domain request form by the way, why Ajax will not? Because in the final analysis, in order to prevent cross-domain user to read content in another domain, Ajax can get a response, the browser that this is not safety, so the intercepted response. But the form does not get new content, it is possible to initiate cross-domain requests. But also it illustrates the cross-domain does not completely prevent CSRF, because after all, the request is sent out.

 

Cross-domain solution to the problem:

  A. CORS

    CORS is a W3C standard, stands for "Cross-Origin Resource Sharing" (Cross-origin resource sharing). It allows the browser to cross the source server, issued a request XMLHttpRequest, which overcomes the limitations of AJAX only homologous use.

      Basically all current browsers implement the standard CORS, in fact, almost all browsers ajax requests are based on CORS mechanism, but may usually front-end developers do not care about it (so that in fact the main solution is now CORS consider the question of how to achieve the background).

      CORS you need to support both current and back-end, all browsers support this feature, IE browser can not be less than IE10, IE 8 and 9 need to achieve through XDomainRequest .

      For developers, there is no difference AJAX communication CORS communicate with homologous, exactly as it was. Once AJAX browser cross-origin request, it will automatically add some additional header information, and sometimes more than a request for additional time, but users do not have feelings.   

      Key browser will automatically communicate CORS, CORS achieve communication is the back-end. As long as the back-end implementation of CORS, to achieve cross-domain.

      Server set up Access-Control-Allow-Origin can open CORS. This attribute indicates which domains can access resources, if you set a wildcard indicates that all sites can access resources.

      通过这种方式解决跨域会出现两种请求:   分别是简单请求复杂请求.

      1) 简单请求(同时,同时,同时满足以下两大条件):

(1) 请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

    

      凡是不同时满足上面两个条件,就属于非简单请求。

      浏览器对这两种请求的处理,是不一样的。

      下面就来仔细谈谈简单请求的基本流程:

      1.1基本流程     

      对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

      下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。

      

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...

 

 

      上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

 

      如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequestonerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

 

      如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

 

 

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...

 

      上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。

    (1)Access-Control-Allow-Origin

        该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个 *,表示接受任意域名的请求。

   (2)Access-Control-Allow-Credentials

        该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

     (3)Access-Control-Expose-Headers

          该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

   1.2 withCredentials 属性

    上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。

  

Access-Control-Allow-Credentials: true

    另一方面,开发者必须在AJAX请求中打开withCredentials属性。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

    否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。

    但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials

xhr.withCredentials = false;

    需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

   2)复杂请求:    

      不符合以上条件的请求就肯定是复杂请求了。
      复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。

      我们用PUT向后台请求时,属于复杂请求,后台需做如下配置:

// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// OPTIONS请求不做任何处理
if (req.method === 'OPTIONS') {
  res.end() 
}
// 定义后台返回的内容
app.put('/getData', function(req, res) {
  console.log(req.headers)
  res.end('小学生多多指教,我是小芳妞和小帆仔!')
})

      接下来我们看下一个完整复杂请求的例子,并且介绍下CORS请求相关的字段

// index.html
let xhr = new XMLHttpRequest()
document.cookie = 'name=xiamen' // cookie不能跨域
xhr.withCredentials = true // 前端设置是否带cookie
xhr.open('PUT', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('name', 'xiamen')
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      console.log(xhr.response)
      //得到响应头,后台需设置Access-Control-Expose-Headers
      console.log(xhr.getResponseHeader('name'))
    }
  }
}
xhr.send()
//server1.js
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
//server2.js
let express = require('express')
let app = express()
let whitList = ['http://localhost:3000'] //设置白名单
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whitList.includes(origin)) {
    // 设置哪个源可以访问我
    res.setHeader('Access-Control-Allow-Origin', origin)
    // 允许携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name')
    // 允许哪个方法访问我
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
    // 允许携带cookie
    res.setHeader('Access-Control-Allow-Credentials', true)
    // 预检的存活时间
    res.setHeader('Access-Control-Max-Age', 6)
    // 允许返回的头
    res.setHeader('Access-Control-Expose-Headers', 'name')
    if (req.method === 'OPTIONS') {
      res.end() // OPTIONS请求不做任何处理
    }
  }
  next()
})
app.put('/getData', function(req, res) {
  console.log(req.headers)
  res.setHeader('name', 'jw') //返回一个响应头,后台需设置
  res.end('哈喽,我是小芳妞!')
})
app.get('/getData', function(req, res) {
  console.log(req.headers)
  res.end('哈喽,我是小帆仔!')
})
app.use(express.static(__dirname))
app.listen(4000)

上述代码由http://127.0.0.1:3000/index.htmlhttp://127.0.0.1:4000/跨域请求,正如我们上面所说的,后端是实现 CORS 通信的关键。

 

  二.JSONP方式解决跨域问题

  jsonp解决跨域问题是一个比较古老的方案(实际中不推荐使用),这里做简单介绍(实际项目中如果要使用JSONP,一般会使用JQ等对JSONP进行了封装的类库来进行ajax请求)

  实现原理: 

  JSONP之所以能够用来解决跨域方案,主要是因为Web页面上调用js文件时则不受是否跨域的影响(不仅如此,我们还发现凡是拥有”src”这个属性的标签都拥有跨域的能力,比如<\script>、<\img>、<\iframe>)。

  如果想通过纯web端跨域访问数据,可以在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理。

  实现流程: 

  1.简单实现

  假设远程服务器根目录下有个remote.js文件代码如下:

alert('我是远程文件');

  本地服务器下有个jsonp.html页面代码如下:

  

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>
 
</body>
</html>

  毫无疑问,页面将会弹出一个提示窗体,显示跨域调用成功。

  2.现在我们在jsonp.html页面定义一个函数,然后在远程remote.js中传入数据进行调用

  jsonp.html页面代码如下:

  

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript">
    var localHandler = function(data){
        alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
    };
    </script>
    <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>
 
</body>
</html>

  remote.js文件代码如下:

localHandler({"result":"我是远程js带来的数据"});

  运行之后查看结果,页面成功弹出提示窗口,显示本地函数被跨域的远程js调用成功,并且还接收到了远程js带来的数据。
  很欣喜,跨域远程获取数据的目的基本实现了,但是又一个问题出现了,我怎么让远程js知道它应该调用的本地函数叫什么名字呢?毕竟是jsonp的服务者都要面对很多服务对象,而这些服务对象各自的本地函数都不相同啊?我们接着往下看。

  3.聪明的开发者很容易想到,只要服务端提供的js脚本是动态生成的就行了呗,这样调用者可以传一个参数过去告诉服务端 “我想要一段调用XXX函数的js代码,请你返回给我”,于是服务器就可以按照客户端的需求来生成js脚本并响应了。

  看jsonp.html页面的代码:

  

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript">
    // 得到航班信息查询结果后的回调函数
    var flightHandler = function(data){
        alert('你查询的航班结果是:票价 ' + data.price + ' 元,' + '余票 ' + data.tickets + ' 张。');
    };
    // 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
    var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
    // 创建script标签,设置其属性
    var script = document.createElement('script');
    script.setAttribute('src', url);
    // 把script标签加入head,此时调用开始
    document.getElementsByTagName('head')[0].appendChild(script); 
    </script>
</head>
<body>
 
</body>
</html>

  这次的代码变化比较大,不再直接把远程js文件写死,而是编码实现动态查询,而这也正是jsonp客户端实现的核心部分,本例中的重点也就在于如何完成jsonp调用的全过程。
  我们看到调用的url中传递了一个code参数,告诉服务器我要查的是CA1998次航班的信息,而callback参数则告诉服务器,我的本地回调函数叫做flightHandler,所以请把查询结果传入这个函数中进行调用。
  OK,服务器很聪明,这个叫做flightResult.aspx的页面生成了一段这样的代码提供给jsonp.html

  (服务端的实现这里就不演示了,与你选用的语言无关,说到底就是拼接字符串):

  

flightHandler({
    "code": "CA1998",
    "price": 1780,
    "tickets": 5
});

  使用注意

  基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求,所以遇到那种情况,就得参考下面的CORS解决跨域了(所以如今它也基本被淘汰了)

  三、代理请求方式解决接口跨域问题  

    注意,由于接口代理是有代价的,所以这个仅是开发过程中进行的。

    与前面的方法不同,前面CORS是后端解决,而这个主要是前端对接口进行代理,也就是:

    •   前端ajax请求的是本地接口   
    •   本地接口接收到请求后向实际的接口请求数据,然后再将信息返回给前端
    •   一般用node.js即可代理 

    关于如何实现代理,这里就不重点描述了,方法很多,也不难,基本都是基于node.js的。

    搜索关键字node.js,代理请求即可找到一大票的方案。

  OPTIONS预检的优化

Access-Control-Max-Age:

    这个头部加上后,可以缓存此次请求的秒数。

    在这个时间范围内,所有同类型的请求都将不再发送预检请求而是直接使用此次返回的头作为判断依据。

    非常有用,可以大幅优化请求次数

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Guess you like

Origin www.cnblogs.com/btsn/p/11953684.html