前言
前后端数据交互接口调试常见的问题就是跨域,什么是跨域,以及有哪些跨域方式呢?
什么是跨域
1. 什么是同源策略及其限制内容?
同源策略 是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指 "协议+域名+端口" 三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制以下几种行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM和JS对象无法获得
- AJAX 请求不能发送
但是有三个标签是允许跨域加载资源的:
<img src=XXX>
<link href=XXX>
<script src=XXX>
2. 常见跨域场景?
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域
跨域解决方案
1.Jsonp
原理
利用标签没有跨域限制的漏洞,网页跨域得到从其他来源动态产生的json数据。JSONP请求一定要做到请求的服务器支持才可以。
缺点
仅支持get请求具有局限性,不安全可能会遭受XSS攻击
实现流程
- 在服务器上定义的函数,如(show函数),通过ajax请求将服务器定义好的函数作为请求参数传递,如果服务器获取到该参数则在服务器端调用该函数并返回json串
- 在页面创建script标签,通过拼接路径的形式?
wd=b&jiajia=jiajia&cb=show
获取后台返回的json串
function jsonp({url, params, cb}) {
return new Promise((resolve, reject) => {
window[cb] = function (data) {
resolve(data)
document.body.removeChild(script)
}
let arrs = [];
params = {
...params,
cb
}
for (let key in params) {
arrs.push(`${key}=${
params[key]
}`)
}
let script = document.createElement("script");
script.src = `${url}?${
arrs.join("&")
}`;
document.body.appendChild(script)
})
}
// 封装 一个jsonp请求
jsonp({
url: 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
params: {
wd: "b",
jiajia: 'jiajia'
},
cb: "show"
}).then(res => {
console.log("res", res)
let div = document.createElement("div")
document.body.appendChild(div)
div.innerHTML = res.s
})
2.cros
cros 需要后端与浏览器同时支持,只需要在服务器端Access-Control-Allow-Origin
,设置可以允许访问的
- 3000端口的静态页面访问4000端口的接口
const xhr = new XMLHttpRequest;
xhr.open("GET",'http://localhost:4000/getData',true);
// 如果想通过前端的ajax服务设置http请求头,需要后台服务响应的支持
xhr.setRequestHeader('name','zfpx');
// 强制http请求必须带上cookie
document.cookie = 'name=jiajia';
xhr.withCredentials = true;
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status >= 200 ){
console.log("ajax start")
console.log(xhr.response)
}
}
}
xhr.send();
- 2 3000端口托管步骤1的静态页面
const express = require("express");
const app = express();
app.use(express.static(__dirname));
app.listen(3000, () => {
console.log("server is running at 3000")
})
- 3 开启4000端口的接口服务,允许端口3000的静态页面访问
const express = require("express");
const app = express();
let whiteList = ['http://localhost:3000'];
app.use(function (req, res, next) {
const origin = req.headers.origin;
console.log("origin",req.headers.origin)
if (whiteList.includes(origin)) {
// 设置哪个源可以访问我
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader("Content-type","text/html;charset=UTF8")
// 允许携带哪个头访问我
res.setHeader('Access-Control-Allow-Headers', "name");
// 允许哪个方法访问我
res.setHeader("Access-Control-Allow-Methods", 'PUT');
// 预检存货时间(非简单请求会有预检,6m之后不会同时发送)
res.setHeader("Access-Control-Max-Age", 6000);
// 允许携带cookie
res.setHeader("Access-Control-Allow-Credentials", true);
// 允许前端获取哪个头
res.setHeader("Access-Control-Expose-Headers",'name')
if (res.method === 'OPTIONS') {
res.end();//options 请求不做任何处理
}
}
next()
})
app.get("/getData", (req, res) => {
console.log("req.header",req.header)
res.end("我不爱你")
})
// app.use(express.static(__dirname))
app.listen(4000, () => {
console.log("server is running at 4000")
})
- 服务端在4000端口的接口对3000端口设置了
Access-Control-Allow-Origin
则就可以访问了,设置的相应头都可以在浏览器中查看的到
3.Iframe- postMessage
托管在3000端口的a页面,想要内嵌托管在4000端口的b页面
- 托管在3000端口的a页面
<body>
a文件
<iframe class="lazy" src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe>
<!-- 等它加载完触发一个事件 -->
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') // 发送数据
window.onmessage = function (e) { // 接受返回数据
console.log("我调用的给我回的消息为", e.data) // 我不爱你
}
}
</script>
</body>
- 托管在4000端口的b页面
<body>
<div>b文件</div>
</body>
<script>
window.onmessage = function (e) {
console.log("调用我的人对我的说的话为", e.data) // 我爱你
e.source.postMessage('我不爱你', e.origin)
console.log("b-postMessage")
}
</script>
4.websocket
- 在页面创建一个 WebSocket实例
<body>
this is WebSocket
<script>
let socket = new WebSocket('ws://localhost:3000');
socket.onopen = function () {
socket.send('我爱你'); // 向服务器发送数据
}
socket.onmessage = function (e) {
console.log(e.data); // 接收服务器返回的数据
}
</script>
</body>
- 在服务器端监听前端的WebSocket通信
const express = require('express');
const app = express();
app.use(express.static(__dirname))
const WebSocket = require('ws');//记得安装ws
const wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
ws.on('message', function (data) {
ws.send('我不爱你')
});
})
app.listen(4000, () => {
console.log('success')
})
在浏览器可以在WS查看浏览器与服务器的通信数据
5.Node中间件代理
实现原理:同源策略是浏览器需要遵循的标准,而服务器通信就无需遵循同源策略。代理服务器,需要做到以下几个步骤
- 接受客户端请求
- 将请求转发给实际提供数据的服务器
- 拿到服务器相应的数据并返回给浏览器
- 1 在3000端口的静态页面访问4000端口的接口服务
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
$.ajax({
url: 'http://localhost:4000',
type: 'post',
data: { name: '佳佳', password: '123456' },
contentType: 'application/json;charset=utf-8',
success: function(result) {
console.log(result) // {"title":"fontend","password":"123456"}
},
error: function(msg) {
console.log(msg)
}
})
</script>
- 2 监听3000端口服务,特定的接口服务进行转发
const http = require("http");
// 1. step1 接受客户端请求
const server = http.createServer((request,response) => {
// 代理服务器直接与接口服务器通信,需要设置CROS的首部字段
response.writeHead(200,{
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Headers': 'Content-Type'
})
//2 step2 将请求转发给服务器
const proxyRequest = http
.request({
host: '127.0.0.1',
port: 4000,
url: '/',
method: request.method,
headers: request.headers
},(serverResponse)=>{
//3 step3 收到服务的响应
var body = ''
serverResponse.on('data',(chunk) => {
body += chunk;
})
serverResponse.on('end',() =>{
response.end(body)
})
})
.end()
})
app.use(express.static(__dirname))
server.listen(3000, () => {
console.log('The proxyServer is running at http://localhost:3000')
})
- 3 4000端口提供数据的接口服务
const http = require("http");
const data = { title:"fontend",password:"123456"};
const server = http.createServer((request,response) => {
if(request.url == '/'){
response.end(JSON.stringify(data))
}
})
server.listen(4000,() => {
console.log('data is support on 4000 port')
})
6.nginx反向代理
- 通过nginx 提供的web服务修改nginx的配置即可 实现原理:通过nginx配置一个代理服务器域名与页面的静态服务器域名相同,端口号不同做跳板机,反向代理访问domain2的接口,并且可以修改cookie中domian携带的信息,方便写入cookie,实现跨域登录。
// proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
总结
- node中间件以及nginx都是利用的服务器没有同源策略的限制
- 工作中常用的是nginx以及cors中间件