跨域问题的产生于处理
什么是跨域
1、先了解一下域名的组成:
JS 出于安全方面的考虑,不允许跨域调用其他页面的对象,那什么是跨域呢,简单地理解就是因为浏览器同源策略的限制,a.com 域名下无法操作 b.com 或是 c.a.com 域名下的对象
# |
URL |
说明 |
是否允许通信 |
1 |
同一域名下 |
允许 |
|
2 |
同一域名下不同资源地址 |
允许 |
|
3 |
同一域名,不同端口 |
不允许 |
|
4 |
同一域名,不同协议 |
不允许 |
|
5 |
域名与域名对应的IP |
不允许 |
|
6 |
主域名相同,子域名不同 |
不允许 |
|
7 |
主域名相同,子域名不同 |
不允许 |
|
8 |
不同域名 |
不允许 |
2、广义的跨域
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的,其实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景
知识点:同源策略
同源策略限制一个源的资源(文档或脚本)如何与另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。它的存在可以保护用户隐私信息、防止身份伪造等。
同源策略限制内容有:
- Cookie、LocalStorage、IndexedDB 等存储性内容
- DOM 节点
- AJAX 请求不能发送
跨域简单举例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>a页面</title>
</head>
<body>
<button οnclick="clickButton()">A点击</button>
</body>
<script src="./js/jquery-1.10.2.min.js"></script>
<script>
function clickButton() {
$.ajax({
url:'http://www.wfm.com:3000/server',
success:function(data){
console.log(data);
},
error:function(e){
console.log(e);
}
});
</script>
</html>
处理跨域的方法
-
JSONP
(1)原生
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>a页面</title>
</head>
<body>
<button οnclick="clickButton()">A点击</button>
</body>
<script src="./js/jquery-1.10.2.min.js"></script>
<script>
function clickButton() {
// jsonp(只能用于get请求)
// 方式一
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'http://www.wfm.com:3000/server?callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
alert("回调函数执行!!")
alert(JSON.stringify(res));
}
}
</script>
</html>
nodejs:
var express = require('express');
var router = express.Router();
var http = require('http');
var querystring = require('querystring');
var url = require('url');
/* GET home page. */
router.get('/server', function (req, res, next) {
var urlPath = url.parse(req.url).pathname;
var param = querystring.parse(req.url.split('?')[1]);
res.writeHead(200, { 'Content-Type': 'application/json;charset=utf-8' });
var data = { msg: 'helloworld' };
data = JSON.stringify(data);
var callback = param.callback + '(' + data + ');';
res.write(callback);
res.end();
});
module.exports = router;
结果:
(2)Jquery ajax
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>a页面</title>
</head>
<body>
<button οnclick="clickButton()">A点击</button>
</body>
<script src="./js/jquery-1.10.2.min.js"></script>
<script>
function clickButton() {
//方式二
$.ajax({
url: 'http://www.wfm.com:3000/server?callback=handleCallback',
jsonp: 'callback',//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
type: 'get',
dataType: 'jsonp',//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以不写,jQuery会把数据拿出来放到success函数的参数里
jsonpCallback: 'handleCallback',
success(data) {
alert('123')
console.log(data);
},
error(XMLHttpRequest, textStatus, errorThrown) {
console.error(XMLHttpRequest, textStatus, errorThrown)
}
});
// 回调执行函数
function handleCallback(res) {
alert("回调函数执行!!")
alert(JSON.stringify(res));
}
}
</script>
</html>
nodejs
var express = require('express');
var router = express.Router();
var http = require('http');
var querystring = require('querystring');
var url = require('url');
/* GET home page. */
router.get('/server', function (req, res, next) {
var urlPath = url.parse(req.url).pathname;
var param = querystring.parse(req.url.split('?')[1]);
res.writeHead(200, { 'Content-Type': 'application/json;charset=utf-8' });
var data = { msg: 'helloworld' };
data = JSON.stringify(data);
var callback = param.callback + '(' + data + ');';
res.write(callback);
res.end();
});
module.exports = router;
Tip:为什么呢????
原因就是浏览器是允许三个标签跨域加载资源的:
<img src="path/to/xxx">
<link href="path/to/xxx">
<script src="path/to/xxx"></script>
JSONP利用 <script> 标签的这个开放策略,网页可以得到从其他来源动态产生的 JSON 数据。
但是这种方式只是用与get请求,不推荐使用
-
CORS 跨域资源共享
(1)介绍
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源
(2)带有身份凭证的CORS
- 默认情况下的跨域请求都是不会把cookie发送给服务器的,在需要发送的情况下,如果是xhr,那么需要设置xhr.withCredentials=true,
- 如果是采用fetch获取的话,那么需要在request里面设置 credentials:'include',
- 但是如果服务器在预请求的时候没返回Access-Control-Allow-Crenditials:true的话,那么在实际请求的时候,cookie是不会被发送给服务器端的,要特别注意对于简单的get请求,不会有预请求的过程,
- 那么在实际请求的时候,如果服务器没有返回Access-Control-Allow-Crenditials:true的话那么响应结果浏览器也不会交给请求者
(3)HTTP响应首部字段
- Access-Control-Allow-Origin: <origin> | *
- Access-Control-Expose-Headers 头让服务器把允许浏览器访问的头放入白名单
- Access-Control-Max-Age 头指定了preflight请求的结果能够被缓存多久
- Access-Control-Allow-Credentials
头指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。 - Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。
- Access-Control-Allow-Headers 首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段
后台服务CORS解决跨域(nodejs)
var express = require('express');
var router = express.Router();
var url=require('url');
var app=express();
var allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:8899');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Credentials','true');
next();
};
app.use(allowCrossDomain);
/* GET users listing. */
app.post('/', function(req, res, next) {
res.send('respond with a resource');
});
module.exports = app;
-
Nginx代理
通过nginx配置一个代理服务器(域名与localhost相同,端口不同)做跳板机,反向代理访问wfm接口,实现跨域
server{
# 监听8899端口
listen 8899;
# 域名是localhost
server_name localhost;
#凡是localhost:8899/api这个样子的,都转发到真正的服务端地址http://www.wfm.com:3000
location ^~ /users {
proxy_pass http://www.wfm.com:3000;
}
}
Tip:配置之后就不需要前端做什么修改了,一般我们在前后端分离项目中开发阶段会采用这种方式,但不是所有场景都能这样做,例如后端接口是一个公共的API,比如一些公共服务获取用户信息等
-
window.name + iframe跨域
(1)iframe_a.html:(http://www.wfm1.com:7799/a.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iframe_a页面</title>
</head>
<body>
</body>
<script>
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement('iframe');
// 加载跨域页面
iframe.src = url;
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
iframe.onload = function() {
if (state === 1) {
// 第2次onload(同域proxy页)成功后,读取同域window.name中数据
callback(iframe.contentWindow.name);
destoryFrame();
} else if (state === 0) {
// 第1次onload(跨域页)成功后,切换到同域代理页面
iframe.contentWindow.location = 'http://www.wfm1.com:7799/iframe_proxy.html';
state = 1;
}
};
document.body.appendChild(iframe);
// 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
function destoryFrame() {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
};
// 请求跨域b页面数据
proxy('http://www.wfm2.com:6699/iframe_b.html', function(data){
alert(data);
});
</script>
</html>
(2)iframe_proxy.html:(http://www.wfm1.com/iframe_proxy.html)
中间代理页,与iframe_a.html同域,内容为空即可。
(3)iframe_b.html:(http:// www.wfm2.com /iframe_b.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iframe_b页面</title>
</head>
<body>
iframe_b页面
</body>
<script>
window.name = 'This is domain2 data!';
</script>
</html>