跨域
由于浏览器同源策略,只要发送请求的url的协议、域名、端口号任意一个与当前页面不同则为跨域。要注意的是,这是浏览器出于安全的限制。
没有同源限制的危险场景
接口请求
我们都知道cookie一般用于处理系统登录等场景,目的是为了让服务端知道是谁发出的请求。当我们调用登录接口进行登录的时候,服务端验证通过后会在响应头加入set-cookie字段,之后客户端发送请求时,浏览器会自动将cookie附加在http请求头中,这样服务端就知道该用户已经登录过。
了解了cookie的基本使用,看下面的场景:
- 小明准备购物,打开了www.gouwu.com,然后登录成功,将商品加入购物车
- 在浏览其他商品的时候,好友发来一个链接www.xiaodianying.com,然后小明打开了链接
- 当小明在浏览www.xiaodianying.com的时候,由于没有同源策略的限制,www.xiaodianying.com向购物网站www.gouwu.com发起了请求,由于之前小明已经登陆过该网站,浏览器之后会自动把cookie加在请求头,这样这个不法网站也就相当于登录了小明的账号,可以进行各种操作。
- 上面也就是我们常说的CSRF攻击
DOM查询
还是以购物网站为例,假如购物网站是www.gouwu.com,有些不法分子会利用iframe嵌入www.gouwu.com,然后通过js获取用户的个人信息。
伪造的网址www.gouwuu.com
<iframe src="www.gouwu.com" name="ifm"></iframe>
<script>
//由于没有同源策略的限制,伪造网站可以直接取到别的网站的dom元素,进而获取用户信息
let iframe = window.frames['ifm'];
let inp = iframe.document.getElementById('账号用户名输入框');
</script>
解决跨域几种方式
JSONP
原理:利用script标签的跨域能力
- 客户端网页添加一个script元素,向服务器请求JSON数据
- 服务端对应的接口在返回参数外面添加函数包裹层
界面端编码
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://localhost:3000/?callback=foo');
}
function foo(data) {
console.log(data.name);
...
};
服务端编码(node)
const http = require('http');
const querystring = require('querystring');
const server = http.createServer((req, res) => {
let params = querystring.parse(req.url.split('?')[1]);
if(params.callback) {
res.writeHead(200, {'Content-Type': 'application/json;charset=utf-8'});
let data = {
name: 'fiber'
}
data = JSON.stringify(data);
let callback = params.callback + '(' + data + ')';
res.end(callback);
}else{
res.end('other');
}
}).listen(3000);
使用注意:由于JSONP的实现原理,JSONP只能用于'get'请求,不能进行较为复杂的请求。
CORS解决跨域问题
cors即跨域资源共享,它允许浏览器向跨源服务器发出xhr请求,从而克服ajax同源限制
以node为例,添加对应的响应头信息即可
const http = require('http');
const server = http.createServer((req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With');
res.setHeader('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS');
res.end('success');
});
server.listen(3001);
postMessage
这个方法允许跨窗口通信,不论这两个窗口是否同源。
以打开一个非同源页面为例
父页面:http://localhost:8080
子页面:http://localhost:8081
//父页面监听信息 http://localhost:8080
window.addEventListener('message', (e) => {
console.log(e.data);
//通过e.source向子窗口传递信息
e.source.postMessage('来自父窗口的信息', 'http://localhost:8081')
}, false);
//子窗口向父窗口传递信息
window.opener.postMessage('来自子窗口的信息', 'http://localhost:8080');
window.addEventListener('message', (e) => {
console.log(e.data);
})
注:message事件的事件对象event,提供以下三个属性
- event.source 发送消息的窗口
- event.origin 消息发向的网址
- event.data 消息内容
nginx反向代理解决跨域
假如本地页面部署在http://localhost:8080,而我们要访问的接口地址是http:localhost:3000/api/getInfo,因为跨域这样肯定是无法获取到服务器端的数据。
只需要在nginx.conf文件添加请求拦截,匹配到/api/getInfo请求,通过nginx转发至服务端
server{
listen 8080;
server_name 127.0.0.1;
location / {
root G:/workspace/Test
index index.html;
}
location ^~ /api/ {
proxy_pass http://127.0.0.1:3000/;
}
}