如何解决前端跨域问题?

本文详细阐述了跨域的定义、同源策略的优点和限制,介绍了JSONP、document.domain、iframe、location.hash、window.name和CORS等解决跨域问题的方法,以及nginx和Node.js代理在跨域中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一。什么是跨域?

跨域:是指浏览器不能执行其他网站的脚本,是由浏览器的“同源策略”导致的,是浏览器对javaScript实施的安全限制。

同源策略:同源(origin)指的是端口号、协议、域名相同。

跨域报错:

一个域名地址的组成:

那什么情况下算跨域呢?

URL说明是否允许通信

http://www.a.com/a.js

http://www.a.com/b.js

同一域名下
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下的不同文件夹
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同端口
http://www.a.com/a.js
https://www.a.com/b.js
同一域名,不同协议
http://www.a.com/a.js
http://70.32.92.74/b.js
域名和域名对应ip
http://www.a.com/a.js
http://script.a.com/b.js
主域名相同,子域名不同
http://www.a.com/a.js
http://a.com/b.js
同一域名,不同二级域名(同上)不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js
http://www.a.com/b.js
不同域名

 说明:

1.如果是“协议”和“端口”造成的跨域问题,前台是无法解决的;

2.在跨域问题上,域仅仅是通过”url的首部“来识别,而不会去尝试判断相同的ip地址对应着的两个域或两个域是否在同一ip上。

url的首部”是指window.location.protocol+window.location.host

二。同源策略有哪些优点和限制?

优点:同源策略的存在,可以保护用户的隐私信息,防止身份伪造等(读取cookie)。

插入:用户可通过 document.cookie 获取到 cookie。

同源策略限制的内容有(或者说同源策略会导致什么问题):

cookie、LocalStorage、indexedDB等存储性内容;

DOM节点;

AJAX请求不能发送。

但是,有三个标签是允许跨域加载资源的:

<img src=""/>
<link href="">
<script src="">

三。如何解决跨域问题?

1.JSONP

 JSONP的实现原理:利用上述三个标签的特性,服务器端不再返回JSON格式的数据,而是返回一段调用某个函数的js代码,在src中进行调用。通过动态创建script,在请求一个带参网址实现跨域通信。

(1)原生实现方案:

<script>
   var script = document.createElement('script');
   script.type = 'text/javascript';

   // 传参一个回调函数名(handleCallback)给后端,方便后端返回时执行这个在前端定义的回调函数
   script.src = 'http://www/domain2.com:8080/login?user=admin&callback=handleCallback';
   document.head.appendChild(script);

   // 回调执行函数
   function handleCallback(res) {
      alert(JSON.stringify(res));
   }
</script>

 服务器端返回如下(返回时即执行全局函数)

handleCallback({'status': true, 'user': 'admin'});

(2)jquery ajax实现

$.ajax({
     url: 'http://www/domain2.com:8080/login',
     type: 'get',
     dataType: 'jsonp', // 请求方式为jsonp
     jsonCallback: 'handleCallback', // 自定义回调函数名
     data: {}
});

(3)vue.js

this.$http.jsonp('http://www/domain2.com:8080/login', {
     params: {},
     jsonp: 'handleCallback',
   }).then((res) => {
     console.log(res);
})

jsonp虽然实现简单,但有如下缺点:

1.只能实现 get 一种请求;

2.安全问题(请求代码中可能存在安全隐患);

3.要确定jsonp请求是否失败并不容易。

2.document.damain + iframe跨域(此方法只适用于 ‘主域名相同,子域名不同’的场景)

实现原理:两个页面都通过js强制设置document.damain为基础主域,就实现了同域。

(1)父窗口(http://www.domain.com/a.html)

<iframe id='iframe' src='http://child.domain.com/b.html'></iframe>
<script>
    document.domain = 'domain.com';
    var user = 'admin';
</script>

(2)子窗口(http://child.domain.com/b.html) 

<script>
    document.domain = 'domain.com';
    // 获取父窗口中变量
    alert('get js data from parent ---> ' + window.parent.user);
</script>

 3.location.hash + iframe跨域

实现原理:a欲与b跨域进行通信,通过中间页c来实现。三个页面,不同域之间通过iframe的loaction.hash来传值,相同域之间之间js访问来通信。

具体实现:A域:a.html -> B域:b.html -> A域:c.html。a与b不同域只能通过hash值单向通信,b与c页不同域也只能通过hash单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。

(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');

   // 向b.html页面传hash值
   setTimeout(() => {
     iframe.src = iframe.src + '#user=admin';
   }, 1000);

   // 开放给同域的c.html的回调方法
   function onCallback(res) {
      alert('data from c.html' + res);
    }
</script>

(2)b.html:(http://www.domain2.com/b.html)

<iframe id='iframe' src='http://www.domain1.com/c.html' style='display:none'></iframe>
<script>
   var iframe = document.getElementById('iframe');

   // 监听a.html页面传来的hash值,再传给c.html
    window.onhashchange = () => {
        iframe.src = iframe.src + location.hash;
    }
</script>

(3)c.html: (http://www.domain1.com/c.html)

<script>
   // 监听b.html页面传来的hash值
    window.onhashchange = () => {
        // 再通过操作同域a.html的js回调,将结果传回
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
    }
</script>

4.window.name + iframe跨域

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的name值(2MB)。

(1)a.html: (http://www/domain1.com/a.html)

<script>
   var proxy = (url, callback) => {
      var state = 0;
      var iframe = document.createElement('iframe');

      // 加载跨域页面
      iframe.src = url;

      iframe.onload = () => {
        if (state === 0) {
          // 第1次onloade(跨域页)成功后,切换到同域代理页面
          iframe.contentWindow.location = 'http://www/domain1.com/proxy.html';
          state = 1;
        } else if (state === 1){
          //第2次onload(同域proxy页)
          callback(iframe.contentWindow.name);
          destoryFrame();
        }
      }

      document.body.appendChild(iframe);

      // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域iframe js访问)
      destoryFrame = () => {
        iframe.contentWindow.document.write('');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
      }
   }

   // 请求跨域b页面数据
   proxy('http://www/domain2.com/b.html', (data) => {
    alert(data);
   })
</script>

(2)proxy.html: (http://www/domain1.com/proxy.html)

中间代理页,与a.html同域,内容为空即可。

(3)b.html: (http://www/domain2.com/b.html)

<script>
    window.name = 'This is domain2 data!';
</script>

5.postMessage跨域

6.跨域资源共享(CORS)

普通跨域请求:只需要服务器端设置Access-Control-Allow-Origin即可,前端无需设置,若要带cookie请求,前后端都得设置。

注意:

由于同源策略的限制,所读取的cookie为跨域请求接口所在的cookie,而非当前页。如果想实现当前页的cookie的写入,可参考“nginx反向代理中设置proxy_cookie_domain”和“nodeJs中间件代理中cookieDomainRewiter”参数的设置。

目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS),CORS也已经成为主流的跨域解决方案。

(1)前端设置

1)原生ajax

// 前端设置都否带cookie
xhr.withCredentials = true;

 示例:

   var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomianRequest兼容

   // 前端设置是否带cookie
   xhr.withCredentials = true;

   xhr.open('post', 'http://www/domain2.com:8080/login', true);
   xhr.setRequestHeader('Content-type', 'application/x-www/form-urlencoded');
   xhr.send('user=admin');

   xhr.onreadystatechange = () => {
     if (xhr.readyState === 4 && xhr.status === 200) {
        alert(xhr.responseText);
     }
   }

2)jquery ajax

$.ajax({
     ...
     xhrFields: {
       withCredentials: true // 前端设置是否携带cookie
     },
     crossDomain: true, // 会让请求头中博涵跨域的额外信息,但不会含cookie
     ...
   });

3)vue框架

3.1)axios设置

axios.defaults.withCredentials = true

3.2)vue-resource设置:

Vue.http.options.credentials = true

(2)服务器端设置

若后端设置成功,前端浏览器控制台则不会出现跨域报错信息,反之,说明没设成功。 

7.nginx代理跨域

跨域原理:同源策略是浏览器的安全策略,不是http协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨域问题。

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳转机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

nginx具体配置:

具体nginx的学习,传送门:https://www.cnblogs.com/zhoulifeng/p/10236116.html

#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等中间件代理接口访问nginx时,此时无浏览器参与,故没有
       同源策略,下面的跨域配置可不启用
       add_header Access-Control-Allow-Origin http://www.domain1.com # 当前端只跨域不带cookie,设置为*
       add_header Access-Control-Allow-Credentials true;
     }
   }

(1) 前端代码示例

   var xhr = new XMLHttpRequest();

   // 前端开关:浏览器是否读写cookie
   xhr.withCredentials = true;

   // 访问nginx中的代理服务器
   xhr.open('get', 'http://www/domain1.com:81/?uer=admin', true);
   xhr.send();

 (2) nodejs后台示例

var http = require('http');
var server = http.createServer();
var qs = require('querystring');

server.on('request', function(req, res) {
    var params = qs.parse(req.url.substring(2));

    // 向前台写cookie
    res.writeHead(200, {
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:脚本无法读取
    });

    res.write(JSON.stringify(params));
    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

8.Nodejs中间件代理跨域

node中间件实现跨域原理,原理大致与nginx相同,都是通过启动一个代理服务器,实现数据转发,也可以设置cookieDomainRewiter参数修改相应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

9.webSocket协议跨域

参考老师:https://segmentfault.com/a/1190000011145364

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值