跨域问题

同源策略

很多人对跨域有一种误解,以为这是前端的事,和后端没关系,其实不是这样的,说到跨域,就不得不说说浏览器的同源策略。

同源策略是由Netscape提出的一个著名的安全策略,它是浏览器最核心也最基本的安全功能,现在所有支持JavaScript的浏览器都会使用这个策略。所谓同源是指协议、域名以及端口要相同。同源策略是基于安全方面的考虑提出来的,这个策略本身没问题,但是我们在实际开发中,由于各种原因又经常有跨域的需求,传统的跨域方案是JSONP,JSONP虽然能解决跨域但是有一个很大的局限性,那就是只支持GET请求,不支持其他类型的请求,而今天我们说的CORS(跨域源资源共享)(CORS,Cross-origin resource sharing)是一个W3C标准,它是一份浏览器技术的规范,提供了Web服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,这是JSONP模式的现代版。

在Spring框架中,对于CORS也提供了相应的解决方案,今天我们就来看看SpringBoot中如何实现CORS。

  • 同源策略是一种约定,它是浏览器最核心也会是最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。它约定请求的url地址,必须与浏览器的url地址处于同域上,也就是域名,端口,协议都相同。如果不同,就会报错:

  • 实际上的结果是,请求已经被发送过去了,目标服务器也对请求做出了响应,只是浏览器对非同源请求的返回结果做了拦截。

一些内嵌资源不受限制

如:

  • <script src="..."></script> 标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。

  • <link rel="stylesheet" href="..."> 标签嵌入CSS。

  • <img> 嵌入图片。

  • <video><audio> 嵌入多媒体资源。

  • <object>, <embed><applet>的插件。

  • @font-face 引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。

  • <frame><iframe>载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。

限制范围

非同源的网站,主要有3种行为受到限制

  1. 无法共享 cookie, localStorage, indexDB

  2. 无法操作彼此的 DOM 元素

  3. 无法发送 Ajax 请求

jsonp

jsonp 全称是JSON with Padding,是为了解决跨域请求资源而产生的解决方案,是一种依靠开发人员创造出的一种非官方跨域数据交互协议。

jsonp的原理就是借助HTML中的<script>标签可以跨域引入资源。所以动态创建一个<srcipt>标签,src为目的接口 + get数据包 + 处理数据的函数名。后台收到GET请求后解析并返回函数名(数据)给前端,前端<script>标签动态执行处理函数。

缺陷: 仅get请求支持

  • 基于script标签实现的跨域

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
     
    <script type="text/javascript">
        var messagetow = function(data){
            alert(data);
        };
        var url = "http://192.168.31.137/train/test/jsonpthree?callback=messagetow";
        var script = document.createElement('script');
        script.setAttribute('src', url);
        document.getElementsByTagName('body')[0].appendChild(script);
    </script>
    </head>
    <body>
    </body>
    </html>
  • 基于jquery跨域

    $.ajax({
         type : 'get',
         url:'http://192.168.31.137/train/test/testjsonp',
         data : {
            name : name,
            sex : sex,
            address : address,
            looks : looks,
        },
        cache :false,
        jsonp: "callback",
        jsonpCallback:"success",
        dataType : 'jsonp',
        success:function(data){
            alert(data);
        },
        error:function(data){
            alert('error');
        }       
    });

跨域资源共享(CORS)

CORS(Cross-Origin Resource Sharing跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。

<script type="text/javascript">
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "/trigkit4",true);
    xhr.send();
</script>

以上的trigkit4是相对路径,如果我们要使用CORS,相关Ajax代码可能如下所示:

<script type="text/javascript">
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "http://segmentfault.com/u/trigkit4/",true);
    xhr.send();
</script>

代码与之前的区别就在于相对路径换成了其他域的绝对路径,也就是你要跨域访问的接口地址。

服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。

  • CORS(Cross-origin resource sharing,跨域资源共享)是一个 W3C 标准,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通(需要客户端和服务端协同处理)

  • CORS有两种请求,简单请求和非简单请求。

简单请求

  • 什么是简单请求?

    只要同时满足以下两大条件,就属于简单请求。
    ​
    (1)请求方法是以下三种方法之一:
        - HEAD
        - GET
        - POST
    (2)HTTP的头信息不超出以下几种字段:
        - Accept
        - Accept-Language
        - Content-Language
        - Last-Event-ID
        - Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
  • 简单请求实现cors

    1.前端不需要进行特殊的操作
    2.后端需要加入请求头Access-Control-Allow-Origin,代码如下:
        def get_time(request):
            if request.method == 'GET':
                ntime = time.strftime('%Y-%m-%d %X')
                obj = HttpResponse(ntime)
                obj['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8001'
                return obj

复杂请求

  • 两次请求,在发送数据之前会先发第一次请求做预检,只有预检通过后在发一次请求作为数据传输。

    - 请求方式:OPTIONS
    - “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
    - 如何“预检”
        => Access-Control-Allow-Origin(必含)
           设置为'*',允许所有域名访问
           一般设置为指定域名,如:'Access-Control-Allow-Origin:http://192.168.12.87'
        => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
            Access-Control-Request-Method
        => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
            Access-Control-Request-Headers
  • 复杂请求实现

    前端

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
        <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
        <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    </head>
    <body>
    <button id="a" class="btn btn-success">go</button>
    <script>
        $("#a").click(function () {
            $.ajax({
                url:'http://127.0.0.1:8000/money/',
                type:'post',
                contentType:'application/json',
                data:{"name":"yjh"},
                success:function (data) {
                    alert(data)
                }
                }
            )
        })
    </script>
    </body>
    </html>

    后台

    from django.http import QueryDict
    def get_money(request,response):
        if request.method == 'OPTIONS':
            response['Access-Control-Allow-Origin'] = '*' # 允许所有域名访问,一般会设置成指定的域名
            response['Access-Control-Allow-Headers'] = 'Content-Type'  # 复杂请求添加的头信息
        response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8001'
        return response

如何使用

  • 客户端只需按规范设置请求头。

  • 服务端按规范识别并返回对应响应头,或者安装相应插件,修改相应框架配置文件等。具体视服务端所用的语言和框架而定

一个spring boot项目中关于CORS配置的一段代码

HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        String temp = request.getHeader("Origin");
        httpServletResponse.setHeader("Access-Control-Allow-Origin", temp);
        // 允许的访问方法
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
//         Access-Control-Max-Age 用于 CORS 相关配置的缓存
        httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
        httpServletResponse.setHeader("Access-Control-Allow-Headers",
                "Origin, X-Requested-With, Content-Type, Accept,token");
        httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");

在传统的 SSM 框架中使用

使用window.name来进行跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的

使用HTML5的window.postMessage方法跨域

window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。

注意这跨域的局限性在于必须在同一个window对象上,也就是说哪个window发送消息,只有本window才能接收到。因为收发消息都是js代码,所以这本质是用js的方法跨域,双方都要写js

在同一window中,h5有个api叫postMessage,从字面意思就知道是“发送消息”的意思。

前提:跨域和被跨域的一方都是你可以控制的,一方写发送消息的,另一方写接收消息方法

1.发送消息:

window.postMessage('这是要发送的消息',''); 有两个参数,第一个参数是要发送的信息,可以是字符串也可以是json字符串,可传递较多信息。第二个参数是可以指定域名,也就是说只有符合这个域名才他发消息,如果写“”的话不管域名是啥,只要在同一个window就给它发消息。

var message={
    event:"saveSuccess",
    data:{
        channelId:self.marketingform.type,
        beginDate:self.marketingform.startTime,
        endDate:self.marketingform.endTime,
        svcType:obj.resultData.svcType
    }
}
window.postMessage(JSON.stringify(message),'http://www.xxx.com');

2.接收消息:

window.parent.addEventListener("message",function(res){
    console.log(res)
})

接收到的消息是一个对象,数据在data中 postMessage跨域的精髓就是不管你俩是否跨域,只要你俩能跟一个window扯上关系,那就能传消息,收到消息就可以处理下一步事物

比如我开头的场景,因为iframe的window和我的页面的window不是一个window,所以iframe保存完发消息要用window.parent.postMessage发送,而我的页面直接用window.addEventListener接收,接收到保存成功的消息后,将返回的id保存。

两个同域的window如果不是同一个window也是不能收发消息的,如 : 两个选项卡之间是不通的。

窗口间的通讯只能是一个页面嵌入多个iframe,多个iframe因为能靠主页面找到同一个window,才能做到多页面消息传递

nginx

通过nginx (部署) -- 解决跨域问题 -- 反向代理机制 类似 中间商 ,把访问后台的请求 转换访问自己, 让那个nginx去访问后台,把结果那会,在转给前台

缺点: 部署服务,做配置

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值