XMLHttpRequest cannot load http://xxx.xxx. No Access-Control-Allow-Origin header is present 报错的解决方法

关于跨域的一些解决方案

########跨域(非同源策略请求)
-同源策略请求 ajax/fetch
-跨域传输

2013年以前 前后端不分离
部署到同一个web服务器上:同源策略
-xampp 修改本地的host文件

服务器拆分
web服务器:静态资源 kbs.spotrs.qq.com
data服务器:业务逻辑和数据分析 api.sports.qq.com
图片服务器:

三者都一样就是同源,只要有一个不同就是跨域
-协议
-域名
-端口号
WEB服务器地址:http://127.0.0.1:3000/index.html
数据接口地址:http://127.0.0.1:4000/list

=============================

1.JSONP
-script
-img
-link
-iframe
-...
以上不存在跨域请求的限制
向服务器发送请求,同时把本地的一个函数传递给服务器
服务器接收客户端的请求,准备数据,给客户端返回数据
因此,JSONP需要服务器端的支持
问题:jsonp只能处理get请求
=============================================

2、CORS跨域资源共享
-客户端(发送ajax/fetch请求)
-服务器端设置相关的头信息(需要处理options试探性请求)
====================================================

3、http proxy代理 =>webpack webpack-dev-server
=================================================

4、ngix反向代理 ==>不需要前端干啥
proxy 相当于node给你模拟了一个ngix请求,服务器请求服务器不存在跨域。proxy请求来数据,解决跨域

5、postMessage
=========================
6、WebSocket协议跨域

其余方案:基于iframe的方案

7、document.domain + iframe
只能实现:同一个主域,不同子域之间的操作

8、window.name+iframe

9、location.hash+iframe
需要3个页面,A和C同源,A和B非同源

1、JSONP

  • 利用了js脚本不受同源限制。
  • 前后端规范统一的脚本方法名,来传递数据执行特定的方法。在这里插入图片描述
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JSONP-demo</title>
</head>
<body>
<p>hello world</p>
<script>
    function myFunction (data) {
        alert('获取数据成功,2s后改变数据!')
        let p = document.getElementsByTagName('p')[0]
        setTimeout(function () {
            p.innerHTML = data.message
        }, 2000)
        // 2s后p标签内的内容将改变
    }
</script>
<!-- <script src="http://localhost:3001?callback=myFunction"></script> -->
<script >
    myFunction({'message': 'hello world from JSONP!?'}) //上面注释掉的等同于下面这段脚本。
</script> 
<!--在src里面的问号?后面的参数({callback: 'myFunction'})可以在3001端口页面中可以通过req.query.callback获取-->
</body>
</html>
  • 服务端获取方法名
app.get('/', function (req, res) {
    var callbackName = req.query.callback;   // 获取方法名:myFunction
    logger.debug(callbackName)
    res.send(callbackName+"({'message': 'hello world from JSONP!?'});");
    // myFunction({'message': 'hello world from JSONP!'})
    // 返回一个带参数的执行函数
})
总结jsonp

相当于就是一种前后端搭配做到动态执行js脚本,由后端传递参数的办法。


Ajax中的jsonp

  • 设置dataType:jsonp
  • 设置jsonp:callback
  • 设置前端执行的方法名 jsonpcallback:functionName
  • 在Ajax中会自动帮你生成回调方法名,所以可以不用指定jsonpCallback,可以直接调用success回调函数
 @RequestMapping(value="/user/info")
    public String userInfo( @RequestParam String callback){
        //参数callback会拿到jquery自动生成的方法名
        return callback+"('natural')";//与jsonp一样返回带参方法即可

    }

    $.ajax({
      url:'http://localhost:8080/user/info',
      type:'GET',
      dataType: 'jsonp',
      jsonp:'callback',//默认约定callback
      // jsonpCallback:'handleResponse',可以定义自己的方法,否则直接调用success回调方法。
      success:function(data){
        alert(data)
      },error:function(error){
        console.log(error)
      }
    })

    function handleResponse (data){ //自己定义的回调方法
      let p = document.getElementsByTagName('p')[0];
        setTimeout(function(){
          p.innerHTML=data
        },2000)
    }


CORS跨域资源共享

  • 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
  • 因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
  • 优点:xmlHttpRequest 的onerror 可以捕获错误信息。(不要以状态码200判断,因为http可能返回200的状态码。也就是请求成功了,但控制台报错)。不单单只是GET方法
  • 与jsonp比较:JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
  • 设置response
app.get('/', (req, res) => {
    res.set('Access-Control-Allow-Origin', 'http://localhost:3000'); // 设置允许跨域的origin,允许3000端口访问本端口(3001)
    res.send("Hello world from CROS.?");  
});

在这里插入图片描述

  • Request headers\ Response headers

Access-Control-Allow-Origin : 表示允许哪些原始域进行跨域访问,它的值要么是请求时 Origin字段的值,要么是一个 *,表示接受任意域名的请求

Access-Control-Allow-Credentials: 表示是否允许客户端发送 Cookie,是一个布尔值。默认情况下,Cookie不包括在 CORS 请求之中,设为 true,即表示服务器明确许可,Cookie 可以包含在请求中,一起发给服务器

Access-Control-Expose-Headers: CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段,自定义的header字段是拿不到的,如果想拿到自定义的Header 字段,就必须在 Access-Control-Expose-Headers里面指定

Access-Control-Request-Method: 用来列出浏览器的 CORS请求用到哪些HTTP方法  put\delete 等

SpringBoot Cors

  • @CrossOrigin(origins = “*”)
@RestController
@CrossOrigin(origins = "*")
public class UserController {
    
    @RequestMapping(value="/user/info")
    //@CrossOrigin(origins = "*")
    public String userInfo( ){
        return "('natural')";
		//局部方法上也可以添加@CrossOrigin 注解
    }
}
  • 设置response
 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest reqs = (HttpServletRequest) servletRequest;
        response.setHeader("Access-Control-Allow-Origin", reqs.getHeader("Origin"));
     //直接给response header中添加  Access-Control-Allow-Origin   
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Token");

        filterChain.doFilter(servletRequest, servletResponse);

    }
  • 重写 WebMvcConfigurer(全局跨域)
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                //是否发送Cookie
                .allowCredentials(true)
                //放行哪些原始域
                .allowedOrigins("*")
                .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
                .allowedHeaders("*");
    }
}

总结CORS : Cors 是一个标准只要实现了对应的标准就可以做到跨域


window.postMessage

Iframe

  • 每个iframe元素都有自己的会话历史记录(session history)DOM树。包含嵌入内容的浏览上下文称为父级浏览上下文。顶级浏览上下文(没有父级)通常是由Window对象表示的浏览器窗口。
  • 搞清楚window 指向谁。
  • 其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames
  • 一些可能嵌入跨域资源的标签
  • <script src="..."></script>标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。
  • <link rel="stylesheet" href="...">标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的Content-Type 消息头。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari (跳至CVE-2010-0051)部分 和 Opera。
  • <img>嵌入图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG,…
  • @font-face引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。
  • <frame> 和 <iframe>载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。
  • MDN: https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy (浏览器的同源策略)
window.postMessage();// 这里的windwos始终指向目标iframe
otherWindow.postMessage(message, targetOrigin, [transfer]);
window.addEventListener('message', receiveMessage, false);
receiveMessage = (event) => {}

event.origin //源
event.data 
event.source // event.source.postMessage() 直接双向通信。
<body>
<div id="father">
    <p>这里3000端口</p>
    <input type="text"/>
    <button>发送信息</button>
    <p style="text-align: left;">message :  <span></span></p>
</div>
<iframe id="child" src="http://localhost:3001"></iframe>
<script>
    var input = document.getElementsByTagName('input')[0];
    var span = document.getElementsByTagName('span')[0];
    var btn = document.getElementsByTagName('button')[0];
    var frame = document.getElementById('child').contentWindow;// otherwindows 顾名思义(目标窗口)
	//var frame = window.frames['child2']
    btn.onclick = function () {
        var msg = input.value;
        frame.postMessage('收到信息:' + msg + ' --from 3000 port!?', 'http://localhost:3001');
    }

    function receiveMessage (event) {
        if (event.origin !== 'http://localhost:3001') {//防止来自其他origin的攻击
            return false
        }
        var data = event.data;
        span.innerHTML = data;
    }

    window.addEventListener('message', receiveMessage, false);//监听 message事件  调用receiveMessage 方法。
</script>

  • 一个投机取巧比较low的办法,就是通过表单提交来进行登陆。

在这里插入图片描述

  • 利用iframe 进行post跨域(隐藏的form表单提交)
const requestPost = ({url, data}) => {
  // 首先创建一个用来发送数据的iframe.
  const iframe = document.createElement('iframe')
  iframe.name = 'iframePost'
  iframe.style.display = 'none'
  document.body.appendChild(iframe)
  const form = document.createElement('form')
  const node = document.createElement('input')

  form.action = url
  // 在指定的iframe中执行form
  form.target = iframe.name
  form.method = 'post'
  for (let name in data) {
    node.name = name
    node.value = data[name].toString()
    form.appendChild(node.cloneNode())
  }
  // 表单元素需要添加到主文档中.
  form.style.display = 'none'
  document.body.appendChild(form)
  form.submit()

  // 表单提交后,就可以删除这个表单,不影响下次的数据发送.
  document.body.removeChild(form)
}
// 使用方式
requestPost({
  url: 'http://localhost:3001/vbi/login.do',
  data: {
    msg: 'this is vbi'
  }
})
以上部分例子是基于下面项目改的

https://github.com/FatDong1/cross-domain

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值