同源策略
很多人对跨域有一种误解,以为这是前端的事,和后端没关系,其实不是这样的,说到跨域,就不得不说说浏览器的同源策略。
同源策略是由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种行为受到限制
-
无法共享 cookie, localStorage, indexDB
-
无法操作彼此的 DOM 元素
-
无法发送 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");
使用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去访问后台,把结果那会,在转给前台
缺点: 部署服务,做配置