跨域请求解决方案及详解

跨域请求解决方案及详解1 什么是跨域跨域 是指浏览器不能执行其他网站的脚本

大家好,欢迎来到IT知识分享网。

1. 什么是跨域

       跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。

       同源策略限制了一下行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 和 JS 对象无法获取
  • Ajax请求发送不出去
    (因为浏览器的同源策略导致了跨域,就是浏览器在搞事情。)
    浏览器为什么要搞事件?是不想我们程序员好日子过?对于这样的质问,浏览器甩锅道:“同源策略是一个重要的安全机制,它限制了不同域名交互带来的潜在危险。”
    在这里插入图片描述


2. 同源策略

3. 常见的跨域场景

4. 没有同源策略限制的两大危险场景

       据我了解,浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对DOM的查询。试想下没有这样的限制这两种动作有什么危险。


4.1. 没有同源策略限制的接口请求

       有个小小的东西叫Cookie大家应该知道,一般用来处理登录等场景,目的是让服务端知道谁发出的请求。如果你请求了接口进行登录,服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再请求的时候,浏览器会自动将Cookie附加在HTTP请求头字段Cookie中,服务器端就能知道这个用户已登录过了。

       如果这不是一个淘宝网账号,而是你的银行账号,那……。

       这就是传说中的CSRF攻击。


5. 案例

       使用浏览器打开百度,同时按F12开启开发者工具,输入:fetch(“https://www.baidu.com”),发现没有任何问题,因为当前域就是https://www.baidu.com/,属于同源。
在这里插入图片描述
再输入:fetch(“https://www.taobao.com”),发现出现跨域报错:
在这里插入图片描述
有些html标签是可以直接跨域调用的,如:
<link>、<script>、<img>、<frame>等这些标签具有跨域特性,可以直接访问。




6. 跨域请求解决方案

  • 修改浏览器的安全设置(不推荐)
  • JSONP
  • 跨域资源共享CORS(Cross-Origin Resource Sharing)
  • Iframe(不推荐)
  • 反向代理

6.1. JSONP

       示例1:JavaScript原生的实现方式

let script = document.createElement('script'); script.src = 'http://localhost:8082/user?callback=callback'; document.body.appendChild(script); function callback(res) { 
    console.log(res); } 

       示例2:jQuery的实现方式
       注:当前是http://localhost:8081,向8082发送请求

<script src="jquery-2.1.1.min.js" ></script> <script> $(function(){ 
    $('#abc').on('click', function(){ 
    $.ajax({ 
    url : 'http://localhost:8082/user', type : 'get', dataType : 'jsonp', jsonp : 'callback', //请求发送callback success : function(result){ 
    console.log(result); } }); }); }); </script> 

       Controller类中写法:

@RequestMapping("/user") public String findUser(String callback){ 
    //接收callback值 //模拟数据 User user = new User(); user.setUsername("admin"); user.setPassword(""); //使用jackson将对象转换成JSON格式数据 ObjectMapper om = new ObjectMapper(); String s = null; try { 
    s = om.writeValueAsString(user); } catch (JsonProcessingException e) { 
    e.printStackTrace(); } //注:这里需要拼接callback中的数据 return callback +"("+ s +")"; } 

6.2. CORS

        CORS(Cross-origin resource sharing,跨域源资源共享)是一个 W3C 标准,它是一份浏览器技术的规范,提供了Web服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,这是 JSONP 模式的现代版。
       CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
       实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
       在Spring框架中,对于CORS也提供了相应的解决方案。





       示例:SpringBoot中实现 CORS

<script src="jquery-2.1.1.min.js" ></script> <script> $(function(){ 
    $('#abc').on('click', function(){ 
    $.ajax({ 
    url : 'http://localhost:8082/user', type : 'post', //可使用其他请求方式 success : function(result){ 
    console.log(result); } }); }); }); </script> 

       Controller类中代码:

@RestController public class CrossAController { 
    @CrossOrigin @RequestMapping("/user") public User findUser(){ 
    //模拟数据 User user = new User(); user.setUsername("admin"); user.setPassword(""); return user; } } 

       请求头参数:
       Access-Control-Allow-Origin:* 表示不再限制跨域。
在这里插入图片描述
       @CrossOrigin的使用:


  • 在;方法上,表示该方法可以实现跨域请求。
  • 在;上,表示该类中所有方法都可实现跨域请求。

       @CrossOrigin的两个参数:

  • origins:允许可访问的域列表
  • maxAge:准备响应前的缓存持续的最大时间(以为单位)。

       示例:

@CrossOrigin(origins = "http://localhost:8082", maxAge = 3600) 

       CORS的全局配置:
       @CrossOrigin可以注解在类或方法上,也可以设置成全局。创建配置类,实现WebMvcConfigurer接口,重写addCorsMappings方法

       示例:CORS全局配置类

@Configuration public class WebMvcConfig implements WebMvcConfigurer { 
    @Override public void addCorsMappings(CorsRegistry registry) { 
    registry.addMapping("/") //允许所有请求映射 .allowedOrigins("http://localhost:8082") //允许8082跨域请求 .allowedHeaders("*") //设置请求头 .allowedMethods("*") //允许所有请求方式 .maxAge(3600); //设置有效时间 } } 

       注:在Controller注解上方添加@CrossOrigin注解后,仍然出现跨域问题,解决方案之一就是:在@RequestMapping注解中没有指定Get、Post方式,具体指定后,问题解决。

       如:@RequestMapping(value = “/user”, method = RequestMethod.POST)


       JSONP与CORS的区别:

  • CORS与JSONP的使用目的相同,但是比JSONP更强大。
  • JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。


6.3. Nginx代理跨域

6.3.1. Nginx配置解决iconfont跨域

       浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在Nginx的静态资源服务器中加入以下配置。

location / { 
    add_header Access-Control-Allow-Origin *; } 

6.3.2. Nginx反向代理接口跨域

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

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

       示例:Nginx具体配置

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

       前端代码示例:

var xhr = new XMLHttpRequest(); // 前端开关:浏览器是否读写cookie xhr.withCredentials = true; // 访问nginx中的代理服务器 xhr.open('get', 'http://www.domain1.com:81/?user=admin', true); xhr.send(); 

       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=a;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...'); 

6.4.document.domain + iframe 跨域

       这种跨域的方式最主要的是要求主域名相同。
       什么是主域名相同呢?www.yiyuanxinghe.com、map.yiyuanxinghe.com、ba.yiyuanxinghe.com这三个主域名都是yiyuanxinghe.com,而主域名不同的就不能用此方法。
       假设目前a.yiyuanxinghe.com和 b.yiyuanxinghe.com分别对应指向不同IP的服务器。

       a.manubao.com下有一个test.html文件:

<script type="text/javascript" src = "jquery-1.12.1.js"></script> <div>A页面</div> <iframe style = "display:none" name = "iframe1" id = "iframe" src="http://b.manubao.com/1.html" frameborder="0"></iframe> <script type="text/javascript"> $(function(){ 
      try{ 
      document.domain = "manubao.com"; }catch(e){ 
     } $("#iframe").load(function(){ 
      var jq = document.getElementById('iframe').contentWindow.$ jq.get("http://manubao.com/test.json", function(data){ 
      console.log(data); }); }) }) </script> 

       利用 iframe 加载其他域下的文件(manubao.com/1.html),同时 document.domain 设置成 manubao.com ,当 iframe 加载完毕后就可以获取manubao.com 域下的全局对象,此时尝试着去请求 manubao.com域名下的 test.json (此时可以请求接口),就会发现数据请求失败了~~惊不惊喜,意不意外!

数据请求失败,目的没有达到,自然是还少一步: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>html</title> <script type="text/javascript" src = "jquery-1.12.1.js"></script> <script type="text/javascript"> $(function(){ 
      try{ 
      document.domain = "manubao.com" }catch(e){ 
     } }) </script> </head> <body> <div id = "div1">B页面</div> </body> </html> 

6.5. window.name + iframe 跨域

<body> <script type="text/javascript"> iframe = document.createElement('iframe'), iframe.src = 'http://localhost:8080/data.json'; document.body.appendChild(iframe); iframe.onload = function() { 
      console.log(iframe.contentWindow.name) }; </script> </body> 
<body> <script type="text/javascript"> iframe = document.createElement('iframe'), iframe.src = 'http://localhost:8080/data.json'; document.body.appendChild(iframe); iframe.onload = function() { 
      iframe.src = 'http://localhost:81/cross-domain/proxy.html'; console.log(iframe.contentWindow.name) }; </script> </body> 

       理想似乎很美好,在iframe载入过程中,迅速重置iframe.src的指向,使之与index.html同源,那么index页面就能去获取它的name值了!但是现实是残酷的,iframe在现实中的表现是一直不停地刷新, 也很好理解,每次触发onload时间后,重置src,相当于重新载入页面,又触发onload事件,于是就不停地刷新了(但是需要的数据还是能输出的)。修改后代码如下:

<body> <script type="text/javascript"> iframe = document.createElement('iframe'); iframe.style.display = 'none'; var state = 0; iframe.onload = function() { 
      if(state === 1) { 
      var data = JSON.parse(iframe.contentWindow.name); console.log(data); iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } else if(state === 0) { 
      state = 1; iframe.contentWindow.location = 'http://localhost:81/cross-domain/proxy.html'; } }; iframe.src = 'http://localhost:8080/data.json'; document.body.appendChild(iframe); </script> </body> 

       所以如上,我们就拿到了服务器返回的数据,但是有几个条件是必不可少的:

  • iframe标签的跨域能力
  • window.names属性值在文档刷新后依然存在的能力

6.6. location.hash + iframe 跨域

       此跨域方法和上面介绍的比较类似,一样是动态插入一个iframe然后设置其src为服务端地址,而服务端同样输出一端js代码,也同时通过与子窗口之间的通信来完成数据的传输。

       关于锚点相信大家都已经知道了,其实就是设置锚点,让文档指定的相应的位置。锚点的设置用a标签,然后href指向要跳转到的id,当然,前提是你得有个滚动条,不然也不好滚动嘛是吧。

       而location.hash其实就是url的锚点。比如https://www.geekjc.com#geekjcg的网址打开后,在控制台输入location.hash就会返回#geekjc的字段。

       基础知识补充完毕,下面我们来说下如何实现跨域

       如果index页面要获取远端服务器的数据,动态的插入一个iframe,将iframe的src执行服务器的地址,这时候的top window 和包裹这个iframe的子窗口是不能通信的,因为同源策略,所以改变子窗口的路径就可以了,将数据当做改变后的路径的hash值加载路径上,然后就可以通信了。将数据加在index页面地址的hash上, index页面监听hash的变化,h5的hashchange方法

<body> <script type="text/javascript"> function getData(url, fn) { 
      var iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = url; iframe.onload = function() { 
      fn(iframe.contentWindow.location.hash.substring(1)); window.location.hash = ''; document.body.removeChild(iframe); }; document.body.appendChild(iframe); } // get data from server var url = 'http://localhost:8080/data.php'; getData(url, function(data) { 
      var jsondata = JSON.parse(data); console.log(jsondata.name + ' ' + jsondata.age); }); </script> </body> 

       补充说明:其实location.hash和window.name都是差不多的,都是利用全局对象属性的方法,然后这两种方法和jsonp也是一样的,就是只能够实现get请求


6.7.postMessage跨域

       这是由H5提出来的一个炫酷的API,IE8+,chrome,ff都已经支持实现了这个功能。这个功能也是非常的简单,其中包括接受信息的Message时间,和发送信息的postMessage方法。

       发送信息的postMessage方法是向外界窗口发送信息

var onmessage = function(event) { 
    var data = event.data; var origin = event.origin; } if(typeof window.addEventListener != 'undefined'){ 
    window.addEventListener('message',onmessage,false); }else if(typeof window.attachEvent != 'undefined'){ 
    window.attachEvent('onmessage', onmessage); } 

举个例子:

a.html(http://www.nealyang.cn/a.html)
<iframe id="iframe" src="http://www.neal.cn/b.html" style="display:none;"></iframe> <script> var iframe = document.getElementById('iframe'); iframe.onload = function() { 
      var data = { 
      name: 'aym' }; // 向neal传送跨域数据 iframe.contentWindow.postMessage(JSON.stringify(data),'http://www.neal.cn'); }; // 接受domain2返回数据 window.addEventListener('message', function(e) { 
      alert('data from neal ---> ' + e.data); }, false); </script>


b.html(http://www.neal.cn/b.html)
<script> // 接收domain1的数据 window.addEventListener('message', function(e) { 
      alert('data from nealyang ---> ' + e.data); var data = JSON.parse(e.data); if (data) { 
      data.number = 16; // 处理后再发回nealyang window.parent.postMessage(JSON.stringify(data), 'http://www.nealyang.cn'); } }, false); </script> 

6.8. WebSocket协议跨域

<div>user input:<input type="text"></div> <script src="./socket.io.js"></script> <script> var socket = io('http://www.domain2.com:8080'); // 连接成功处理 socket.on('connect', function() { 
    // 监听服务端消息 socket.on('message', function(msg) { 
    console.log('data from server: ---> ' + msg); }); // 监听服务端关闭 socket.on('disconnect', function() { 
    console.log('Server socket has closed.'); }); }); document.getElementsByTagName('input')[0].onblur = function() { 
    socket.send(this.value); }; </script> node Server var http = require('http'); var socket = require('socket.io'); // 启http服务 var server = http.createServer(function(req, res) { 
    res.writeHead(200, { 
    'Content-type': 'text/html' }); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...'); // 监听socket连接 socket.listen(server).on('connection', function(client) { 
    // 接收信息 client.on('message', function(msg) { 
    client.send('hello:' + msg); console.log('data from client: ---> ' + msg); }); // 断开处理 client.on('disconnect', function() { 
    console.log('Client socket has closed.'); }); }); 

6.9. node代理跨域

var xhr = new XMLHttpRequest(); // 前端开关:浏览器是否读写cookie xhr.withCredentials = true; // 访问http-proxy-middleware代理服务器 xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true); xhr.send(); 后端代码 var express = require('express'); var proxy = require('http-proxy-middleware'); var app = express(); app.use('/', proxy({ 
    // 代理跨域目标接口 target: 'http://www.domain2.com:8080', changeOrigin: true, // 修改响应头信息,实现跨域并允许带cookie onProxyRes: function(proxyRes, req, res) { 
    res.header('Access-Control-Allow-Origin', 'http://www.domain1.com'); res.header('Access-Control-Allow-Credentials', 'true'); }, // 修改响应信息中的cookie域名 cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改 })); app.listen(3000); console.log('Proxy server is listen at port 3000...'); 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/111260.html

(0)
上一篇 2026-01-26 07:15
下一篇 2026-01-26 07:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信