跨域的几种解决方法?

285 阅读3分钟

跨域

由于浏览器同源策略,只要发送请求的url的协议、域名、端口号任意一个与当前页面不同则为跨域。要注意的是,这是浏览器出于安全的限制。

没有同源限制的危险场景

接口请求

我们都知道cookie一般用于处理系统登录等场景,目的是为了让服务端知道是谁发出的请求。当我们调用登录接口进行登录的时候,服务端验证通过后会在响应头加入set-cookie字段,之后客户端发送请求时,浏览器会自动将cookie附加在http请求头中,这样服务端就知道该用户已经登录过。
了解了cookie的基本使用,看下面的场景:

  1. 小明准备购物,打开了www.gouwu.com,然后登录成功,将商品加入购物车
  2. 在浏览其他商品的时候,好友发来一个链接www.xiaodianying.com,然后小明打开了链接
  3. 当小明在浏览www.xiaodianying.com的时候,由于没有同源策略的限制,www.xiaodianying.com向购物网站www.gouwu.com发起了请求,由于之前小明已经登陆过该网站,浏览器之后会自动把cookie加在请求头,这样这个不法网站也就相当于登录了小明的账号,可以进行各种操作。
  4. 上面也就是我们常说的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/;
    }
}