1. jsonp

jsonp 跨域其实也是JavaScript设计模式中的一种代理模式。在html页面中通过相应的标签从不同域名下加载静态资源文件是被浏览器允许的,所以我们可以通过这个“犯罪漏洞”来进行跨域。一般,我们可以动态的创建script标签,再去请求一个带参网址来实现跨域通信,但是,Jsonp只支持 Get 请求
本质是利用了标签具有可跨域的特性,将数据使用json格式用一个函数包裹起来,然后在进行访问的页面中定义一个相同函数名的函数,因为 script 标签src引用的js脚本到达浏览器时会执行,而我们有定义了一个同名的函数,所以json格式的数据,就做完参数传递给了我们定义的同名函数了,这样就完成了跨域数据交换。

  • 原生实现 jsonp

    1. const jsonp = function (url, data) {
    2. return new Promise((resolve, reject) => {
    3. // 初始化url
    4. let dataString = url.indexOf('?') === -1 ? '?' : '&'
    5. let callbackName = `jsonpCB_${Date.now()}`
    6. url += `${dataString}callback=${callbackName}`
    7. if (data) {
    8. // 有请求参数,依次添加到url
    9. for (let k in data) {
    10. url += `&${k}=${data[k]}`
    11. }
    12. }
    13. let jsNode = document.createElement('script')
    14. jsNode.src = url
    15. // 触发callback,触发后删除js标签和绑定在window上的callback
    16. window[callbackName] = result => {
    17. delete window[callbackName]
    18. document.body.removeChild(jsNode)
    19. if (result) {
    20. resolve(result)
    21. } else {
    22. reject('没有返回数据')
    23. }
    24. }
    25. // js加载异常的情况
    26. jsNode.addEventListener('error', () => {
    27. delete window[callbackName]
    28. document.body.removeChild(jsNode)
    29. reject('JavaScript资源加载失败')
    30. }, false)
    31. // 添加js节点到document上时,开始请求
    32. document.body.appendChild(jsNode)
    33. })
    34. }
    35. jsonp('http://192.168.0.103:8081/jsonp', {a: 1, b: 'heiheihei'})
    36. .then(result => { console.log(result) })
    37. .catch(err => { console.error(err) })
  • jqury 实现 jsonp ```javascript $.ajax({ url : url, method : ‘get’, data : {}, dataType : ‘jsonp’, // 请求方式是jsonp jsonpCallback : ‘callback’, // 回调函数 })

// 虽然这种方式非常好用,但是一个最大的缺陷是,只能够实现get请求

  1. <a name="fwar9"></a>
  2. ### 2. 服务端设置
  3. ```javascript
  4. // 指定允许其他域名访问
  5. header('Access-Control-Allow-Origin:*'); // *允许所有域名访问
  6. // 响应类型
  7. header('Access-Control-Allow-Methods:GET, POST, PUT, DELETE, OPTIONS');
  8. // 响应头设置
  9. header('Access-Control-Allow-Headers:Origin, X-Requested-With, Content-Type, Accept, authKey, sessionId');

3. document.domain + iframe 跨域

4. window.name + iframe 跨域

5. postMessage 跨域

跨文档消息传送,有时候简称为 XDM,指的是在来自不同域的页面间传递消息,XDM 的核心是 postMessage() 方法,postMessage()方法接收两个参数: 一条消息和一个表示消息接收方来自哪个域的字符串。,第二个参数对保障安全通信非常重要,可以防止浏览器把消息发送到不安全的地方。

接收到 XDM 消息时,会触发 window 对象的 message 事件。这个事件是以异步形式触发的,因此从发送消息到接收消息(触发接收窗口的 message 事件)可能要经过一段时间的延迟。触发 message 事件后,传递给 onmessage 处理程序的事件对象包含以下三方面的重要信息

  • data: 作为 postMessage()第一个参数传入的字符串数据
  • origin: 发送消息的文档所在的域, 例如: [http://www.pengdaokuan.cn](http://www.pengdaokuan.cn)
  • source: 发送消息的文档的 window 对象的代理。 这个代理对象主要用于在发送上一条消息的窗口中调用 postMessage()方法。如果发送消息的窗口来自同一个域,那这个对象就是 window。

接收到消息后验证发送窗口的来源是至关重要的。就像给 postMessage()方法指定第二个参数, 以确保浏览器不会把消息发送给未知页面一样,基本的检测模式如下 :

  1. EventUtil.addHandler(window, 'message', function () {
  2. // 确保发送消息的域是已知的域
  3. if (event.origin == 'http://www.pengdaokuan.cn') {
  4. // 处理接收到的数据
  5. processMessage(event.data)
  6. // 可选, 向来源窗口发送回执
  7. event.source.postMessage('Received', 'http://www.received.com')
  8. }
  9. })

【注意】: postMessage()的第一个参数最早是作为 “ 永远都是字符串 ” 来实现的。但后来这个参数的定义改了,改成允许传入任何数据结构。可是,并非所有浏览器都实现了这一变化。为保险起见,使用 postMessage() 时,最好还是只传字符串。如果你想传入结构化的数据,最佳选择是先在要传入的数据上调用 JSON.stringify(),通过 postMessage()传入得到的字符串,然后再在 onmessage 事件处理程序中调用 JSON.parse()

6. 反向代理

反向代理是指以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器;并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器
image.png

在nginx配置文件 “nginx.conf” 中修改信息

  1. // 第一种方式, 在服务器端的nginx.conf 中添加配置
  2. http {
  3. ......
  4. add_header Access-Control-Allow-Origin *;
  5. add_header Access-Control-Allow-Headers X-Requested-With;
  6. add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
  7. ......
  8. }
  9. // 第二种方式,在客户端的nginx.conf 中利用反向代理
  10. server {
  11. listen 8000;
  12. server_name localhost;
  13. location / {
  14. proxy_connect_timeout 1s;
  15. proxy_read_timeout 1s;
  16. proxy_send_timeout 1s;
  17. add_header 'Access-Control-Allow-Origin' '*'; #允许来自所有的访问地址
  18. add_header 'Access-Control-Allow-Credentials' 'true';
  19. add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS'; #支持请求方式
  20. add_header 'Access-Control-Allow-Headers' 'Content-Type,*';
  21. }
  22. }

nginx是一个高性能的web服务器,常用作反向代理服务器。nginx作为反向代理服务器,就是把http请求转发到另一个或者一些服务器上。
通过把本地一个url前缀映射到要跨域访问的web服务器上,就可以实现跨域访问。对于浏览器来说,访问的就是同源服务器上的一个url。而nginx通过检测url前缀,把http请求转发到后面真实的物理服务器。并通过rewrite命令把前缀再去掉。这样真实的服务器就可以正确处理请求,并且并不知道这个请求是来自代理服务器的
简单说,nginx服务器欺骗了浏览器,让它认为这是同源调用,从而解决了浏览器的跨域问题。又通过重写url,欺骗了真实的服务器,让它以为这个http请求是直接来自与用户浏览器的。

正向代理:

image.png 总结来说:正向代理,“它代理的是客户端”,是一个位于客户端和原始服务器(Origin Server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器)。 然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。

正向代理与反向代理的区别

image.png
image.png

7. CORS 实现跨域 (ie8 及其以下不支持)

通过 XHR 实现 Ajax 通信的一个主要限制,来源于跨域安全策略。默认情况下,XHR 对象只能访、问与包含它的页面位于同一个域中的资源。这种安全策略可以预防某些恶意行为。
CORS(Cross-Origin Resource Sharing,跨源资源共享) 定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
**
比如一个简单的使用 GETPOST 发送的请求,它没有自定义的头部,而主体内容是 text/plain。在 发送该请求时,需要给它附加一个额外的 Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。比如下边这个示例:

  1. origin: http://www.pengdaokuan.cn

如果服务器认为这个请求可以接受,就在 Access-Control-Allow-Origin 头部中回发相同的源信息(如果是公共资源,可以回发 “ * “ )

  1. Access-Control-Allow-Origin: http://www.pengdaokuan.cn

如果没有这个头部,或者有这个头部但源信息不匹配,浏览器就会驳回请求。正常情况下,浏览器会处理请求。注意, 请求和响应都不包含 cookie 信息

注意:带凭证的请求

默认情况下,跨源请求不提供凭据(cookie、HTTP 认证及客户端 SSL 证明等)。通过将 withCredentials 属性设置为 true,可以指定某个请求应该发送凭据。如果服务器接受带凭据的请求,会用下面的 HTTP 头部来响应。

  1. Access-Control-Allow-Credentials: true



跨域请求的类型

简单请求

  • request method 属于 GET, HEAD, POST 三种简单类型之一。
  • headers 只包含以下几种 (大小写敏感):
    • Cache-Control
    • Content-Language
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma
  • Content-Type 允许的值只有一下三种:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

      当 Content-Type 不在以上三种时(如 application/json)则会发送预检请求,且返回头里的 Access-Control-Allow-Headers 需要包含 Content-Type

非简单请求 (preflight)

  • 不符合以上简单请求规则的, 浏览器在发送请求前,会先向服务器发送一个 OPTIONS 请求, (称为 preflight), 只有当 preflight 收到 200/204 的回复时,才会发送真实的请求。

响应非简单请求

有些后端应用可能只有 GET 和 POST 两种请求方式,是否还需要能响应 preflight 请求呢?
答案是需要的,比如你的前端应用在多环境部署,可能需要在请求头中添加 X-Antcloud-Tenant header 这一请求头。因此,原来符合简单规则的请求,也有极大的可能会变成 preflight 请求。所以后端需要做的是,在收到 OPTIONS 请求时,返回 200/204 的状态码。

跨域相关的 headers

Request Headers:

  • Access-Control-Request-Headers

    • 浏览器自动添加,在 preflight 请求中,浏览器会加上真实请求中出现的 headers,后端可以根据该字段决定 Access-Control-Allow-Headers 的值。

      Response Headers:

  • Access-Control-Allow-Origin

    • 允许跨域的的域名,不推荐使用通配符(*), 因为在使用通配符时,会跳过 Access-Control-Allow-Credentials 的配置,直接过滤掉请求携带的 cookie. 可能导致后端接口鉴权失败。
  • Access-Control-Allow-Credentials
    • 是否允许跨域请求携带 cookie,通常情况下,用户信息和登陆状态都储存在 cookie 中。如果不携带 cookie 则无法通过后端的接口鉴权,需要设置为 true.
  • Access-Control-Allow-Headers
    • 允许出现的非简单请求头。
  • Access-Control-Allow-Methods
    • 允许跨域的方法,如果没有特殊的安全考虑,可以针对 POST,GET,OPTIONS,DELETE,PUT,HEAD,PATCH 请求都放开,让所有符合 restful 规范的请求都能跨域。
  • Access-Control-Expose-Headers
    • 非必填,如果前端需要消费非简单请求。则需通过该配置指定额外允许前端获得的 headers。部分情况下,浏览器的返回请求中出现 provisonal header,也与此配置有关。
  • Access-Control-Max-Age
    • 非必填,preflight 请求的有效期。

参考文档:
【1】跨域:https://github.com/PDKSophia/blog.io/blob/master/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95-%E6%B5%8F%E8%A7%88%E5%99%A8%E7%AF%87.md
【2】反向代理:https://zhuanlan.zhihu.com/p/152526491