关于跨域那些事儿

在工作中,难免会遇到跨域的问题,就像你高高兴兴的带着老婆吃着火锅,啊不对,是匆匆忙忙的在搬砖,突然浏览器告诉你跨域了,意不意外?

既然遇到了,就只能解决他,平时一顿乱操作,也能解决问题,但一直没有好好的来总结。

俗话说的好,知己知彼、百战不殆。我们来看看什么是跨域?谁在搞事情?

原来是浏览器在搞事情,不过,浏览器表示它不想背这个锅:

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

这是官方的解释,比较拗口,意思就是,为了网页更加安全而设计的安全策略,这个同源策略规定:

如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源,访问不受限制;否则则为跨源(跨域),限制访问。

浏览器为什么要搞个同源策略呢?

这是为了防止CSRF攻击Cross-site request forgery),中文名称:跨站请求伪造。谁不怕坐着火车出了城,突然就被麻匪给劫了不是。

既然跨域必须存在,而我们又必须绕不过它,那我们该如何解决呢?

1、JSONP-----需要后端接口配合

利用scriptimg这样没有跨域限制的标签,来发起请求。

第一版的JSONP

 1 <!DOCTYPE html>
 2 <html>
 3   <head>
 4     <meta charset="utf-8">
 5   </head>
 6   <body>
 7     <script type='text/javascript'>
 8       // 后端返回直接执行的方法,相当于执行这个方法,由于后端把返回的数据放在方法的参数里,所以这里能拿到res。
 9       window.jsonpCb = function (res) {
10         console.log(res)
11       }
12     </script>
13     <script src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb' type='text/javascript'></script>
14   </body>
15 </html>

 封装版:

 1 /**
 2  * JSONP请求工具
 3  * @param url 请求的地址
 4  * @param data 请求的参数
 5  * @returns {Promise<any>}
 6  */
 7 const request = ({url, data}) => {
 8   return new Promise((resolve, reject) => {
 9     // 处理传参成xx=yy&aa=bb的形式
10     const handleData = (data) => {
11       const keys = Object.keys(data)
12       const keysLen = keys.length
13       return keys.reduce((pre, cur, index) => {
14         const value = data[cur]
15         const flag = index !== keysLen - 1 ? '&' : ''
16         return `${pre}${cur}=${value}${flag}`
17       }, '')
18     }
19     // 动态创建script标签
20     const script = document.createElement('script')
21     // 接口返回的数据获取
22     window.jsonpCb = (res) => {
23       document.body.removeChild(script)
24       delete window.jsonpCb
25       resolve(res)
26     }
27     script.src = `${url}?${handleData(data)}&cb=jsonpCb`
28     document.body.appendChild(script)
29   })
30 }
31 // 使用方式
32 request({
33   url: 'http://localhost:9871/api/jsonp',
34   data: {
35     // 传参
36     msg: 'helloJsonp'
37   }
38 }).then(res => {
39   console.log(res)
40 })

2、iframeform配合

因为script标签加载资源的方式就是GET。所以JSONP只能发GET请求,那么我们发送POST请求这么办呢?

 1 const requestPost = ({url, data}) => {
 2   // 首先创建一个用来发送数据的iframe.
 3   const iframe = document.createElement('iframe')
 4   iframe.name = 'iframePost'
 5   iframe.style.display = 'none'
 6   document.body.appendChild(iframe)
 7   const form = document.createElement('form')
 8   const node = document.createElement('input')
 9   // 注册iframe的load事件处理程序,如果你需要在响应返回时执行一些操作的话.
10   iframe.addEventListener('load', function () {
11     console.log('post success')
12   })
13 
14   form.action = url
15   // 在指定的iframe中执行form
16   form.target = iframe.name
17   form.method = 'post'
18   for (let name in data) {
19     node.name = name
20     node.value = data[name].toString()
21     form.appendChild(node.cloneNode())
22   }
23   // 表单元素需要添加到主文档中.
24   form.style.display = 'none'
25   document.body.appendChild(form)
26   form.submit()
27 
28   // 表单提交后,就可以删除这个表单,不影响下次的数据发送.
29   document.body.removeChild(form)
30 }
31 // 使用方式
32 requestPost({
33   url: 'http://localhost:9871/api/iframePost',
34   data: {
35     msg: 'helloIframePost'
36   }
37 })

3、CORS

CORS是一个W3C标准,全称是"跨域资源共享"Cross-origin resource sharing)跨域资源共享 CORS 详解。看名字就知道这是处理跨域问题的标准做法。

CORS需要浏览器和服务器同时支持,服务器需做如下设置:

  3.1、Access-Control-Allow-Origin 该字段必填。它的值要么是请求时Origin字段的具体值,要么是一个*,表示接受任意域名的请求。例如:

  ctx.set('Access-Control-Allow-Origin', 'http://localhost:9099')

   3.2、Access-Control-Allow-Credentials
  该字段可选。它的值是一个布尔值,表示是否允许发送Cookie.默认情况下,不发生Cookie,即:false。对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json,这个值只能设为true。如果服务器不要浏览器发送Cookie,删除该字段即可。例如

    ctx.set('Access-Control-Allow-Credentials', true)  

  3.3、Access-Control-Allow-Methods 该字段必填。它的值是逗号分隔的一个具体的字符串或者*,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。例如:

    ctx.set('Access-Control-Request-Method', 'PUT,POST,GET,DELETE,OPTIONS')

   3.4、Access-Control-Expose-Headers 该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。例如:

    ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, t') 

  3.5、Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求。

前端:

fetch(`http://localhost:9871/api/cors?msg=helloCors`, {

  // 需要带上cookie

  credentials: 'include',

  // 这里添加额外的headers来触发非简单请求

  headers: {

    't': 'extra headers'

  }

}).then(res => {

  console.log(res)

})

4、代理

我们使用Nginx作为我们的代理服务器。Nginx配置

server{
    # 监听9099端口
    listen 9099;
    # 域名是localhost
    server_name localhost;
    #凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871 
    location ^~ /api {
        proxy_pass http://localhost:9871;
    }    
}

Nginx配置好之后,前端什么也不用干,正常请求就行,Nginx会把请求转发到真正的地址。

现在,跨域对你来说,应该不是事儿了吧,可以开开心心的吃火锅了。

猜你喜欢

转载自www.cnblogs.com/zixian/p/9998131.html