什么是跨域(Cross-site)?
想了解跨域,必须先了解一下“同源策略(same origin policy)”。
同源策略
它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。它限制了某个域下的文档或者js与另一个域中的资源交互的方式,它提供了一种安全机制,这种安全机制可以避免来自恶意网站的攻击。 同源策略要求浏览器当且仅当两个页面来自相同的域才允许其中一个网页上的js请求另一个网页的数据
什么是域(origin)?
域是由三部分组合而成:
- URI Schema(协议类型),
- host name(域名),
- port number(端口号)
举个例子:
- 1) http://www.domain.com 这个页面,
URI Schema
是http,host name
是www.domain.com,port;number
是默认的80 - 2) https://www.xxx.com:8080/xxx/yyy
URI Schema
是https,host name
是www.xxx.com,port number
是8080
由于1)和2)中的三部分都不相同,所以它们就是不同的域。 下面的图更好的解释了什么是同域
PS:IE浏览器里可能不太一样,它不会把端口号作为判断依据。
为什么要有同源策略?
提出同源策略的目的是出于安全性考虑,它能够阻止来自恶意网站的脚本通过其他网站的DOM获取其他网站的信息。可以避免CSRF和XSS攻击。
同源策略限制了啥?
- 同源策略限制的是浏览器或者其他提供类似浏览器服务的软件,而且这仅仅是个规范;
- 同源策略只是限制JavaScript,而图片,css这些是不存在同源策略限制的。
什么是跨域?
在A网站的页面上通过js请求B网站的数据,如果A和B两个网站不满足同源策略,那么就存在跨域问题。
为什么会有跨域问题?
由于在实际环境中,经常需要通过js获取一些数据,特别是ajax的流行,通过ajax加载某个网站的数据的场景就会经常遇到,而一旦有这样的需求,就可能会出现跨域的问题。
如何判断我是否遇到了跨域问题?
一般来讲,如果你的请求被同源策略限制,浏览器的开发工具都会给出错误提示,在Chrome浏览器的console中,可能会有类似下面的提示
如何解决跨域问题?
1、Jsonp方式
什么是JSONP?
JSONP(JSON with Padding)是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。
JSONP有什么用?
由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。
如何使用JSONP?
下边这一DEMO实际上是JSONP的简单表现形式,在客户端声明回调函数之后,客户端通过script标签向服务器跨域请求数据,然后服务端返回相应的数据并动态执行回调函数。
【html示例代码一】
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<script type="text/javascript">
function jsonpCallback(result) {
//alert(result);
for(var i in result) {
alert(i+":"+result[i]);//循环输出a:1,b:2,etc.
}
}
var JSONP=document.createElement("script");
JSONP.type="text/javascript";
JSONP.src="http://crossdomain.com/services.php?callback=jsonpCallback";
document.getElementsByTagName("head")[0].appendChild(JSONP);
</script>
【html示例代码二】
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<script type="text/javascript">
function jsonpCallback(result) {
alert(result.a);
alert(result.b);
alert(result.c);
for(var i in result) {
alert(i+":"+result[i]);//循环输出a:1,b:2,etc.
}
}
</script>
<script type="text/javascript" src="http://crossdomain.com/services.php?callback=jsonpCallback"></script>
【js示例代码一】
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
$.getJSON("http://crossdomain.com/services.php?callback=?",
function(result) {
for(var i in result) {
alert(i+":"+result[i]);//循环输出a:1,b:2,etc.
}
});
</script>
【js示例代码二】
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
$.ajax({
url:"http://crossdomain.com/services.php",
dataType:'jsonp',
data:'',
jsonp:'callback',
success:function(result) {
for(var i in result) {
alert(i+":"+result[i]);//循环输出a:1,b:2,etc.
}
},
timeout:3000
});
</script>
【js示例代码三】
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
$.get('http://crossdomain.com/services.php?callback=?', {name: encodeURIComponent('tester')}, function (json) { for(var i in json) alert(i+":"+json[i]); }, 'jsonp');
</script>
Jsonp原理:
首先在客户端注册一个callback, 然后把callback的名字传给服务器。
此时,服务器先生成 json 数据。
然后以 javascript 语法的方式,生成一个function , function 名字就是传递上来的参数 jsonp.
最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。
客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里.(动态执行回调函数)
2、设置document.domain属性
如果两个页面或者frame可以将document.domain属性设置成相同的值,那么也可以绕过同源策略限制。 假设两个页面分别是static.demo.com
和server.demo.com
,两个页面加载之后都通过js将document.domain设置成demo.com,这样接下来的ajax请求就可以绕过同源策略限制了。
但是:如果两个页面存在端口,比如static.demo.com:8080 和 server.demo.com:8090,由于document.domain只能设置域名,所以就不起作用。
这种方法针对页面,如果服务端返回的是json,而不是一个页面,所以没法将自己的域名设置成demo.com,但是可以通过另外一种方式,即在服务端增加一个静态页面,页面中放如下js代码:
document.domain=demo.com
或者如下代码:
try{document.domain = window.location.hostname.split('.').reverse().slice(0,2).reverse().join('.');}catch(e){}
然后客户端页面加载的时候先去调用一下这个静态页面就好了。
3、 CORS(Cross-Origin Resource Sharing)
原理MDN上讲的更清楚一些,点击这里。
其实简单来说就是服务端在响应头中添加一个Access-Control-Allow-Origin
头部,头部的值为客户端的域名,比如:http://static.demo.com
,
这样就可以了。但是需要注意的是:CROS分为两种,一种是简单请求,一种是复杂请求,简单请求按照上面的方式是可以的,如果是复杂请求,浏览器会进行两步,先发一个options请求,这个请求称之为“预请求”,预请求实际上是个OPTIONS请求,类似于一个探测作用,如果服务端返回的头部通过了预请求的内容,则浏览器才会发起第二个真实请求。
4、客户端请求通过Nginx转发
原理:客户端的所有请求都直接发到客户端所在域名下,但是在客户端服务器增加一台nginx服务器,作为反向代理,如果是后端的url,直接代理转发到服务端,这样就不存在前端的跨域问题了。
nginx配置如下
server {
listen 80;
server_name static.demo.com; #可配置多个主机头
charset utf-8,gbk,gb2312,gb18030; #可以实现多种编码识别
location / {
root /home/wy/www/static.demo.com/ROOT; #网站文件路径
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
index default.html;
}
#所有/server/开头的请求都会走这里
location /server/ {
proxy_pass http://server.demo.com:8080; ##转发到server
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}