前言
复习跨域
- 什么是跨域
- 跨域的解决方案
跨域基本概念
提起跨域先了解一下同源策略
同源策略
先看一个url www.baidu.com/
这分为三部分:
- 协议:https、http
- 域名:www.baidu.com
- 端口,当http没有写端口的时候默认为
80,https默认端口为443
同源就是另外的url和上面这三个一致的话,那说明这两个url是同源,只要有一个不一样就会导致跨域。跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
同源策略又分为Ajax同源策略和DOM同源策略。
Ajax 同源策略主要是使得不同源的页面不能获取 Cookie 且不能发起 Ajax 请求,这样在一定层度上防止了 CSRF 攻击。
DOM 同源策略也一样,它限制了不同源页面不能获取 DOM,这样可以防止一些恶意网站在自己的网站中利用 iframe 嵌入正规的网站并迷惑用户,以此来达到窃取用户信息。
跨域带来的问题
- ajax请求无法发送出去
- 无法获取dom元素进行操作
- 无法读取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的配置即可解决跨域问题。
例子:
- 访问 http://127.0.0.1:9001/edu/ 直接跳转到 127.0.0.1:8081
- 访问 http://127.0.0.1:9001/vod/ 直接跳转到 127.0.0.1:8082
在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;#携带凭证
}
}