浏览器的同源策略与跨域处理
一、 同源策略
如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。
下表给出了相对<span>http://store.company.com/dir/page.html</span>
同源检测的示例:
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。
允许跨源访问的一些例子:
<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,...<video>
和<audio>
嵌入多媒体资源。<object>
,<embed>
和<applet>
的插件。@font-face
引入的字体。一些浏览器允许跨域字体(cross-origin fonts),一些需要同源字体(same-origin fonts)。<frame>
和<iframe>
载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。
使用CORS允许跨源访问。
跨源脚本API的访问
Javascript的APIs中,如iframe.contentWindow
,window.parent
,window.open
和window.opener
允许文档间直接相互引用。当两个文档的源不同时,这些引用方式将对Window和Location对象的访问添加限制。
可以使用window.postMessage作不同源文档之间的交流。
跨源数据存储访问
存储在浏览器中的数据,如localStorage和IndexedDB,以源进行分割。每个源都拥有自己单独的存储空间,一个源中的Javascript脚本不能对属于其它源的数据进行读写操作。
二、几种跨域解决方案
1、JSONP
Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。
Jsonp 的实现原理是利用<script> 标签可以获取不同源资源的特点,来达到跨域访问某个资源的目的。假如 <script> 元素内部的代码没有位于某个函数中,那么这些代码会在页面被加载时被立即执行。
应用实例:
服务端代码使用jFinal框架
package com.controller; import com.mazu.core.Controller; public class JsonPController extends Controller{ public void getUserInfo(){ String userId = getPara("userId"); String callBackFunc = getPara("callback"); if(userId!=null&&userId.equals("123")){ String userJson = "{\"userName\":\"Admin\",\"age\":26}"; callBackFunc += "('success',"+userJson+")";<strong>//拼接为callback(a,b)的形式返回给前端 </strong> renderJson(callBackFunc); }else{ renderJson(callBackFunc+"('error')"); } } }
客户端代码
<script> function callbackFunc(status,data){ if(status=="success"){ console.log(data.userName+":"+data.age); }else{ console.log(status); } } </script> <script type="text/javascript" src="http://localhost/TestJSONP/jsonp/getUserInfo?userId=123&callback=callbackFunc"></script>
chrome中执行结果
由于是本地测试,使用的是端口不同的两个项目,可以看到服务端响应后,返回给前端一个字符串callback(status,data),前端<script>接收到这段代码后立即执行,成功实现了跨域资源的访问。
在jQuery中如何通过JSONP来跨域获取数据
第一种方法是在ajax函数中设置dataType为'jsonp':
$.ajax({ dataType: 'jsonp', url: 'http://localhost/TestJSONP/jsonp/getUserInfo?userId=123', success: function(data){ console.log(data);<strong>//success</strong> } });
上面的方式只能返回一个参数,多参数时将无法得到第二个及之后的参数。
可以在传递过程中自定义函数名,使用jsonpCallback参数
jsonp:表示传递的参数,默认为callback,我们也可以自定义
jsonpCallback:表示传递的参数值,也就是回调的函数名称,这是自定义的名称
<script src="http://apps.bdimg.com/libs/jquery/1.8.3/jquery.js"></script> <script> function callbackFunc(status,data){ if(status=="success"){ // console.log(data.userName+":"+data.age); console.log("callback:"+data); }else{ console.log("callback:"+status); } } $.ajax({ type:"get", url: 'http://localhost/TestJSONP/jsonp/getUserInfo?userId=123', dataType: 'jsonp', jsonp:'callback', jsonpCallback:'callbackFunc', success: function(data){ console.log("ajax:"+data); } }); </script>
执行结果:
可以看到返回结果中,仍然获取不到第二个参数值。仔细debug了一下后台,返回的字符串也是正确的。
于是我改了下后台的返回值和回调函数的参数,得到了正确的测试结果。
由此可见,使用jQuery做JSONP跨域调用时,与ajax的success回调函数一样,服务端需要将传的内容封装在一个返回值中。
为什么使用jsonpCallback简单的绑定回调函数就能实现回调函数调用了呢?
仔细跟踪chrome中的执行过程,发现Jquery首先将回调函数赋值给一个内部函数,在服务端响应成功后,执行这个内部函数,然后销毁,起到一个临时代理的作用。
jQuery 的 $.ajax 只支持get方式获取跨域数据,并且它不支持出错时的回调。
jQuery-JSONP是一个支持 JSONP 调用的 jQuery 插件,使用它是因为它支持出错时的 ajax 回调。
<script src="jquery-3.2.1.js"></script> <script src="jquery-jsonp.js"></script> <script> $.jsonp({ url: 'http://localhost/TestJSONP/jsonp/getUserInfo', data: { userId: 123 }, callbackParameter: "callback", success: function (data, textStatus, xOptions) { console.log(data); }, error: function (xOptions, textStatus) { } }); </script>
第1个需要注意的地方是 callbackParameter,如果没有专门的 callback 函数,一定要写上 "callback";
第2个需要注意的地方是在 success 回调函数中,xOptions包含传给服务器的参数,如xOptions.data={userId:123}。
2、CORS
CORS全称是“跨域资源共享”(Cross-origin resource sharing),它是一种通过设置HTTP头信息来获取跨源服务器上的特定资源,主要是设置Access-Control-Allow-Origin字段的值。
服务端代码:
public void getUserInfo(){ String userId = getPara("userId"); String userJson = ""; if(userId!=null&&userId.equals("123")){ System.out.println("userId:"+userId); userJson = "{\"userName\":\"Admin\",\"age\":26}"; } //getResponse().setHeader("Access-Control-Allow-Origin", "*"); renderJson(userJson); }
客户端代码:
$.ajax({ type:"get", url: 'http://localhost/TestJSONP/jsonp/getUserInfo?userId=123', dataType: 'json', success: function(data){ console.log("ajax:"+data); } })
当服务端回应头信息中没有包含Access-Control-Allow-Origin
字段时,浏览器报错如下:
设置Access-Control-Allow-Origin为*,表示接受任意域名的请求,在实际开发中为了安全起见一般设为特定域名。
与JSONP的比较
CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET
请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
3、window.postMessage
HTML5跨文档消息传输Cross Document Messaging。下一代浏览器都将支持这个功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。
用postMessage支持基于web的实时消息传递。
window.postMessage()方法被调用时,会在所有页面脚本执行完毕之后(e.g., 在该方法之后设置的事件、之前设置的timeout 事件,etc.)向目标窗口派发一个 MessageEvent
消息。
该MessageEvent
消息有四个属性需要注意:
- message 属性表示该message 的类型;
- data 属性为window.postMessage的第一个参数;
- origin 属性表示调用window.postMessage()方法时调用页面的当前状态;
- source 属性记录调用window.postMessage()方法的窗口信息。
语法:
otherWindow.postMessage(message, targetOrigin, [transfer]);
参考:
- 浏览器的同源策略
- JSONP教程
- 浅谈浏览器端JavaScript跨域解决方法
- JavaScript CORS通信
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage