1、同源策略
同源策略是有 Netscape 提出的一个著名的安全策略,现在所有的支持 javascript 的浏览器都会使用这个策略。
所谓同源是指主机名、协议、端口相同:
- 相同的主机名
- 相同的协议
- 相同的端口
三者必须同时满足,只要主机名、协议、端口三者其中之一不同,就为不同的源。
同源策略限制了一个源中加载文本或者脚本与来自其他源中资源的交互方式,简而言之就是一个源上的 js 只能访问当前源的资源。
同源策略以源为边界,把资源分隔开,从而保护用户的信息安全。
2、跨域的方式
那么,在如今微服务兴起的情况下,往往很多业务调用必须跨越同源限制。比如,某商城服务部署在 www.xmall.com,而其支付服务可能部署在 www.xpay.com,因此必须要有方式可以绕过同源策略这堵墙。
常用的 js 跨域方式主要有:
- 修改 document.domain 跨子域
- 通过 jsonp 跨域
- 通过 html5 的 window.postMessage 跨域
- 通过 CORS 跨域
另外,还可以通过 iframe + window.name 或者 iframe + window.location.hash 进行跨域。
以下涉及示例均使用 http://www.aaa.com/a.html 跨域访问 http://www.bbb.com/b.html 的数据。
3、修改 document.domain 跨子域
www.aaa.com 和 pay.aaa.com 是不同域的,要使他们可以跨域访问,可以通过修改 document.domain 来实现。即在两个页面中都设置:
document.domain = "aaa.com";
这里有个限制就是,document.domain 只能向父域修改,也就是说 www.aaa.com 改为 aaa.com 是允许的,但 aaa.com 改为 www.abc.xyz 则是不被允许的。这也限制了修改 document.domain 方式只能用于跨子域访问。
4、通过 jsonp 跨域
js 脚本的“源”与它存储的地址无关,而是取决于脚本被加载的页面。例如我们在 http://www.aaa.com/a.html 中引入:
<script src="http://cdn.staticfile.org/jquery/1.11.1/jquery.min.js"></script>
那么脚本与 a.html 页面是同源的,也就是说,脚本的源是 http://www.aaa.com.
PS: 除了 script, img, iframe, link 等都具有跨域加载资源的能力。
jsonp 正是利用 script 标签没有跨域限制的特性,通过在 src 的 url 的参数上附加回调函数名字,然后服务器接收回调函数名字并返回一个包含数据的回调函数。
如:
<script>
function callback(data) {
alert(data.message);
}
</script>
<script src="http://www.bbb.com/b.html?callback=callback"></script>
我们只要服务器端 b.html 输出:
callback({"message":"test jsonp ok"})
页面即会执行并弹出 "test jsonp ok"。
jQuery 对此做了很好的支持:
$.ajax({
url:'http://www.bbb.com/b.html',
dataType:"jsonp",
jsonp:"callback",
success:function(data){
// callback logic
}
});
// 或者简化方式
$.getJSON("http://www.bbb.com/b.html?callback=?", null,
function(data) {
// callback logic
}
});
** <span style="color:red">需要特别强调的是,jsonp 方式只能用于 GET 方法! </span>**
5、通过html5 window.postMessage 进行跨域
假设在a.html里嵌套个
<iframe src="http://www.bbbb.com/b.html" frameborder="0"></iframe>
在这两个页面里互相通信
a.html
window.onload = function() {
window.addEventListener("message", function(e) {
alert(e.data);
});
window.frames[0].postMessage("b data", "http://www.b.com/b.html");
}
b.html
window.onload = function() {
window.addEventListener("message", function(e) {
alert(e.data);
});
window.parent.postMessage("a data", "http://www.a.com/a.html");
}
这样打开 a.html 页面就先弹出 a data,再弹出 b data.
6、通过 CORS 进行跨域
CORS 是W3C XMLHttpRequest Level 2 里规定的一种跨域方式。CORS 规范请参考 CORS 规范
CORS 旨在定义一种规范让浏览器在接收到从提供者获取的资源时能够正决定是否应该将此资源分发给消费者作进一步处理。CROS利用资源提供者的显式授权来决定目标资源是否应该与消费者共享。换句话说,浏览器需要得到提供者的授权之后才会将其提供的资源分发给消费者。那么,资源的提供者如何进行资源的授权,并将授权的结果告诉浏览器呢?
一个 CORS 请求大致过程如下:
消费者发送一个 Origin 报头到提供者端:Origin: http://www.bbb.com;
提供者发送一个 Access-Control-Allow-Origin 响应报头给消费者,如果值为 * 或 Origin 对应的站点,则表示同意共享资源给消费者,如果值为 null 或者不存在 Access-Control-Allow-Origin 报头,则表示不同意共享资源给消费者;
浏览器根据提供者的响应报文判断是否允许消费者跨域访问到提供者源。
例如,一个Servlet C0RS响应图示如下:
CORS 是在支持这个规范的浏览器里,javascript 的写法和不跨域的 ajax 写法一模一样。
6.1、CORS 的浏览器支持
6.2、CORS 的服务器端配置
6.2.1 Apache
Header set Access-Control-Allow-Origin "*"
6.2.2 Nginx
#
# Wide-open CORS config for nginx
#
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
#
# Om nom nom cookies
#
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}
6.2.3 ExpressJS
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.get('/', function(req, res, next) {
// Handle the get for this route
});
app.post('/', function(req, res, next) {
// Handle the post for this route
});
6.2.4 Tomcat
Tomcat 7.0.41 版本之后提供了一个 CorsFilter 以支持 CORS,详情猛击
http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#CORS_Filter
下面是一段最简单的 CORS 配置:
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
如果希望自己实现一个 CORS 过滤器,可参考 tomcat 的实现或者下面这个链接:
https://github.com/eBay/cors-filter/blob/master/src/main/java/org/ebaysf/web/cors/CORSFilter.java