前端要懂的跨域问题,面试专题

179 阅读7分钟

前言

复习跨域

  • 什么是跨域
  • 跨域的解决方案

跨域基本概念

提起跨域先了解一下同源策略

同源策略

先看一个url www.baidu.com/

这分为三部分:

  1. 协议:https、http
  2. 域名:www.baidu.com
  3. 端口,当http没有写端口的时候默认为80,https默认端口为443

同源就是另外的url和上面这三个一致的话,那说明这两个url是同源,只要有一个不一样就会导致跨域。跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了

同源策略又分为Ajax同源策略和DOM同源策略。

Ajax 同源策略主要是使得不同源的页面不能获取 Cookie 且不能发起 Ajax 请求,这样在一定层度上防止了 CSRF 攻击。

DOM 同源策略也一样,它限制了不同源页面不能获取 DOM,这样可以防止一些恶意网站在自己的网站中利用 iframe 嵌入正规的网站并迷惑用户,以此来达到窃取用户信息。

跨域带来的问题

  1. ajax请求无法发送出去
  2. 无法获取dom元素进行操作
  3. 无法读取cookie,localstorage

解决跨域问题

  • jsonp
  • cors
  • postMessage
  • webSocket
  • Nginx反向代理

jsonp

JSONP (JSON with Padding) 是 JSON 的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。

jsonp的实现原理

由于浏览器受同源策略的影响,无法通过Ajax请求非同源的接口数据,但是script标签不受浏览器同源策略的影响,可以通过src属性,请求非同源的js脚本。

所以实现原理:通过<script>标签的src属性,请求跨域的数据接口,通过函数调用的形式,接收跨域接口响应回来的数据。

实现步骤

1.定义一个函数

2.访问另一个地址,在script中的src属性写上接口,写上callback=函数名

3.这个接口里面有数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .result{
            width: 200px;
            height: 200px;
            border: 1px solid purple;
        }
    </style>
</head>
<body>
    <div class="result">


    </div>
    <script>
        function handle(data){
            const r = document.querySelector('.result')
            r.addEventListener('mouseenter',function(){
                r.innerHTML = data
            })
}
    </script>
    <script src="http://127.0.0.1:7000/json">
    </script>
</body>
</html>

例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    用户名:<input type="text" placeholder="请输入用户名">
    <p></p>
    <script>
        const ipt = document.querySelector('input')
        const p = document.querySelector('p')
        //声明handle函数
        function handle(data){
            ipt.style.border = '1px solid red',
            p.innerHTML = data.msg
        }


        ipt.onblur = function(){
            //采集到的信息
            const data = this.value
            //向服务器发送请求,看是否是相等的
            //1.创建script标签
            const script = document.createElement('script')
            //2.设置src属性
            script.src = 'http://localhost:7000/json-serve'
            //3.插入到文档里面
            document.body.appendChild(script)
        }
    </script>
</body>
</html>

是输入用户名发给后端,后端查看是否有重名

缺点

JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。

cors

这一部分在于浏览器和后端的支持。Cors背后的思路就是通过自定义的HTTP头部允许浏览器和服务器之间实现通信。

服务端设置Access-Control-Allow-Origin 就可以开启 CORS。如果允许所有的源都能跨域的话,可以写Access-Control-Allow-Origin:*

通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求复杂请求

简单请求

条件1:使用下列方法之一:

  • GET
  • HEAD
  • POST

条件2:Content-Type 的值仅限于下列三者之一:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

满足以上的条件就是简单请求

针对简单请求:可以简单的设置为

Access-Control-Allow-Origin:*
// 如果只是针对某一个请求源进行设置的话,可以设置为具体的值
Access-Control-Allow-Origin: 'http://www.yourwebsite.com'

复杂请求

也叫做预检请求,发送涉及某些高级选项的请求的时候,会向服务器发送一个预检请求,服务器确定是否允许这类型的请求。

针对复杂的请求需要设置不同的响应头,因为在预检请求的时候会携带相应的请求头信息

响应头

//来自哪里的请求
Access-Control-Allow-Origin: http://foo.example
//允许请求的方法
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
// 设置max age,浏览器端会进行缓存。没有过期之前真对同一个请求只会发送一次预检请求
Access-Control-Max-Age: 86400

预检请求携带的请求头

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-CUSTOMER-HEADER, Content-Type

凭据请求

一般而言,对于跨域 XMLHttpRequest或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。
如果在发送请求的时候,给xhr 设置了withCredentials为true,从而向服务器发送 Cookies,如果服务端需要想客户端也发送cookie的情况,需要服务器端也返回Access-Control-Allow-Credentials: true响应头信息。

对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin的值为“*”,要设置请求源。

postMessage

MDN对它的解释:方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage()  方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

适用于的场景:

  • 跨标签通信
  • 与内嵌iframe通信

例子

/*
 * A 窗口的域名是<http://example.com:8080>,以下是 A 窗口的 script 标签下的代码:
 */

var popup = window.open(...popup details...);


// 这行语句没有发送信息出去,即使假设当前页面没有改变 location(因为 targetOrigin 设置不对)
popup.postMessage("The user is 'bob' and the password is 'secret'",
                  "https://secure.example.net");

// 假设当前页面没有改变 location,这条语句会成功添加 message 到发送队列中去(targetOrigin 设置对了)
popup.postMessage("hello there!", "http://example.com");

function receiveMessage(event)
{
 //发送者是不是http://example.com
  if (event.origin !== "http://example.com")
    return;

  // event.source 是我们通过 window.open 打开的弹出页面 popup
  // event.data 是 popup 发送给当前页面的消息 "hi there yourself!  the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);

/*
 * 弹出页 popup 域名是 http://example.com,以下是 script 标签中的代码:
 */

//当 A 页面 postMessage 被调用后,这个 function 被 addEventListener 调用
function receiveMessage(event) {
  if (event.origin !== "http://example.com:8080") return;

  // event.source 就当前弹出页的来源页面
  // event.data 是 "hello there!"

  // 假设你已经验证了所受到信息的 origin (任何时候你都应该这样做), 一个很方便的方式就是把 event.source
  // 作为回信的对象,并且把 event.origin 作为 targetOrigin
  event.source.postMessage(
    "hi there yourself!  the secret response " + "is: rheeeeet!",
    event.origin,
  );
}

window.addEventListener("message", receiveMessage, false);

上面这个例子就是A窗口给B窗口发送信息,B窗口也给A窗口发送信息的过程。

webSocket

实现了浏览器与服务器的全双工通信,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。

WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};      
let express = require('express');
let app = express();
let WebSocket = require('ws');
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
  ws.on('message', function (data) {
    console.log(data);
    ws.send('哈哈哈哈哈')
  });
})

这样就实现了客户端和服务端的通话。

Nginx反向代理

nginx的配置即可解决跨域问题。

例子:

在nginx.conf里面修改

server {
 listen       9001;
 server_name  192.168.17.129;

 location ~ /edu/ {
 #反向代理
  proxy_pass  http://127.0.0.1:8080 
  # 当有了webpack-dev-server或者其他中间件来访问的时候,可以不用添加下面的内容
  add_header Access-Control-Allow-Origin http://www.127.0.0.1:8081; #当前端只跨域不带cookie时,可为*
  add_header Access-Control-Allow-Credentials true;#携带凭证

 }

 location ~ /vod/ {
  proxy_pass  http://127.0.0.1:8081
   # 当有了webpack-dev-server中间件来访问的时候,可以不用添加下面的内容
  add_header Access-Control-Allow-Origin http://www.127.0.0.1:8082; #当前端只跨域不带cookie时,可为*
  add_header Access-Control-Allow-Credentials true;#携带凭证
 }
}