前端跨域问题总结
同源策略SOP(Same origin policy)是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。协议、域名、端口号有一个不同就为跨域。
例如:http://www.baidu.com:80/ 包括:http:协议;www.baidu.com:域名,其中 baidu.com 为一级域名,又叫主域名,www 为二级域名,又叫子域名;80:端口号。
跨域请求请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
一些常用的跨域解决方案
①JSONP
JSONP(json with padding):是一种非正式传输协议,允许用户传递参数到服务器端,服务器端会将这个参数作为函数名来包裹住 JSON 数据并返回,这样客户端就可以随意定制自己的函数来处理服务器端返回的数据了。
原理:利用 script img link iframe等标签没有跨域限制(带有src或者href属性),通过回调函数的形式实现跨域访问。要求:接口的格式必须符合jsonp格式(即接口返回一个函数,函数的参数是需要返回的数据)。有一个缺点:jsonp只能用于get请求(因为script等标签只能get请求)。
JSONP 和 JSON 的区别: JSON 是一种数据格式,而 JSONP 是一种数据的获取方式。
①jsonp原生js实现:
var script = document.createElement('script');
script.src = 'http://localhost/study-code/backEnd/jsonp-exercise/ky.php?callback=cb';
document.body.appendChild(script);
function cb(res) {
console.log(res);
}
② jsonp ajax实现:相比于原生js实现,ajax可以发送post请求。其实就是普通的ajax通过参数指定类型为jsonp,指定返回值函数名,需要后端配合返回jsonp格式数据。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>jsonp-ajax</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<button class='btn'>获取 JSON 数据</button>
<script>
$(document).ready(function(){
$("button").click(function(){
$.ajax({
url:"http://192.168.1.103:8888/ky",
type:'post',//规定请求的类型(GET 或 POST)。
//dataType:'json',//预期的服务器响应的数据类型。
dataType:'jsonp',//如果是json则为ajax请求,若为jsonp则为jsonp请求
jsonpCallback:"handleCallBack",//指定返回的全局函数的名字,不加这一句会随机生成一个名字
success:function(result){
console.log(result)
},
error:function(xhr,status,error){
alert("请求失败");
}
})
})
})
</script>
</body>
</html>
③jsonp Vue.js实现
this.$http.jsonp('http://192.168.1.103:8888/ky', {
params: {},
jsonp: 'handleCallBack'
}).then((res) => {
console.log(res);
})
【jsonp方式node.js服务端代码】
const express = require('express')
const bodyParser = require('body-parser');
const app = express()
//配置body-parser
app.use(bodyParser.urlencoded({extended:false}))
app.use(bodyParser.json())
//此接口适用于jsonp方式实现跨域
app.use('/ky',(req,res)=>{
console.log(req.query)//{callback: 'jQuery110205949897442704204_1591147268373',_: '1591147268374'}//jq默认生成的全局随机函数
let {callback} = req.query//解构赋值
//console.log(callback)
res.send(`${callback}(${JSON.stringify({name:'花花'})})`)
})
app.listen(8888)
console.log('http://127.0.0.1:8888/ http://192.168.1.103:8888/')
②CORS
CORS (跨域资源共享 Cross-Origin Resource Sharing),是新的 W3C 标准。它新增的一组HTTP首部字段允许服务器声明哪些来源请求有权限访问哪些资源。这种方式最主要的特点就是会在响应的HTTP首部增加 Access-Control-Allow-Origin等信息,从而判定哪些资源可以进跨域请求。如:header(‘Access-Control-Allow-Origin:*’) 允许所有来源的请求。
1、普通跨域请求时前端不需要设置什么,正常使用就行。
【不携带cookie时node.js服务端代码】
const express = require('express')
const bodyParser = require('body-parser');
const cors = require('cors')
const app = express()
//配置body-parser
app.use(bodyParser.urlencoded({extended:false}))
app.use(bodyParser.json())
//cors方式解决跨域问题1
app.use(cors())
//cors方式解决跨域问题2
/* app.use((req, res, next)=>{
res.header("Access-Control-Allow-Origin", "*");//允许哪个源(协议+域名+端口)进行请求,不能同时指定多个源, 如有需要可以通过代码动态控制;*允许所有源进行访问,不过就不能携带cookie了,只有写具体的源才可以携带cookie等
res.header("Access-Control-Allow-Headers", "X-Requested-With");//允许的请求头
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");//允许请求的方式
res.header("Access-Control-Allow-Credentials",true);//跨域请求是否允许携带资源凭证,如cookie
res.header("Content-Type", "application/json;charset=utf-8");
next();
}) */
//此接口适用于cors方式实现跨域
app.use('/cors',(req,res)=>{
res.json({ww:"拉拉"})
})
app.listen(8888)
console.log('http://127.0.0.1:8888/ http://192.168.1.103:8888/')
2、携带凭证如cookie的跨域请求
①原生ajax
var xmlhttp;
if (window.XMLHttpRequest){// for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();//XMLHttpRequest 对象用于和服务器交换数据。
}else{// for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.withCredentials = true;// 前端是否带cookie
xmlhttp.open("post","http://192.168.1.103:8888/ky",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");//setRequestHeader()添加 HTTP 头。
xmlhttp.send();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState==4 && xmlhttp.status==200){
alert(xhr.responseText);
}
}
② jQuery ajax
$.ajax({
url: '',
type: 'get',
data: {},
dataType:'json',
xhrFields: {
withCredentials: true //前端设置是否带cookie
},
crossDomain: true, //会让请求头中包含跨域的额外信息,但不会含cookie
})
③ axios
axios.defaults.withCredentials = true
【携带cookie时node.js服务端代码】
const express = require('express')
const bodyParser = require('body-parser');
const cors = require('cors')
const app = express()
//配置body-parser
app.use(bodyParser.urlencoded({extended:false}))
app.use(bodyParser.json())
//cors方式解决跨域问题1
app.use(cors({
origin: 'http://192.168.1.103:8080',//origin是web服务的域名(就是vue前端部分运行后的 Network 部分网址)。只允许 http://192.168.43.67:8888 访问
credentials: true //配置客户端请求时可以携带cookie
}))
//cors方式解决跨域问题2
/* app.use('*', function(req, res, next) {
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.header("X-Powered-By",' 3.2.1');
res.header("Content-Type", "application/json;charset=utf-8");
res.header('Access-Control-Allow-Credentials: true');
res.header("Access-Control-Allow-Origin", 'http://192.168.1.103:8888/');
next();
})*/
//此接口适用于cors方式实现跨域
app.use('/cors',(req,res)=>{
res.json({ww:"拉拉"})
})
app.listen(8888)
console.log('http://127.0.0.1:8888/ http://192.168.1.103:8888/')
③后端代理,后端是服务器端,不存在跨域,让公司内部的后端帮忙拿到数据,返回给前端。
④nginx(反向代理)
⑤document.domain + iframe跨域
此方案仅限主域相同,子域不同的跨域应用场景。
1.)父窗口:(http://www.baidu.com/a.html)
<iframe id="iframe" src="http://ccc.baidu.com/b.html"></iframe>
<script>
document.domain = 'baidu.com';
var user = 'admin';
</script>
2.)子窗口:(http://ccc.baidu.com/b.html)
<script>
document.domain = 'baidu.com';
// 获取父窗口中变量
alert('get js data from parent ---> ' + window.parent.user);
</script>
④跨文档通信 API:window.postMessage()
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
a.) 页面和其打开的新窗口的数据传递
b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递
d.) 上面三个场景的跨域数据传递
用法:postMessage(data,origin)方法接受两个参数
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
1.)a.html:(http://www.domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify({name:'oo'}), 'http://www.domain2.com');
};//contentWindow或contentDocument能返回 iframe 中的文档。
//console.log(iframe.contentWindow)//window
// 接受domain2返回数据
window.addEventListener('message', function(e) {
alert(e.data);
}, false);
</script>
2.)b.html:(http://www.domain2.com/b.html)
<script>
// 接收domain1的数据
window.addEventListener('message', function(e) {
alert( e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
}, false);
</script>