##1. 什么是跨域(Cross-site)?
想了解跨域,必须先了解一下“同源策略(same origin policy)”。
1.1 同源策略
它限制了某个域下的文档或者js与另一个域中的资源交互的方式,它提供了一种安全机制,这种安全机制可以避免来自恶意网站的攻击。 同源策略要求浏览器允许来自某个网页上的js请求来自另一个网页的数据,当且仅当两个页面来自相同的域。
1.2 什么是域(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,hostname是www.xxx.com,port number是8080
由于1)和2)中的三部分都不相同,所以它们就是不同的域。
下面的图更好的解释了什么是同域:
PS:IE浏览器里可能不太一样,它不会把端口号作为判断依据。
1.3 为什么要有同源策略?
提出同源策略的目的是出于安全性考虑,它能够阻止来自恶意网站的脚本通过其他网站的DOM获取其他网站的信息。可以避免CSRF和XSS攻击。
1.4 同源策略是限制谁的?
1) 很多人可能搞不清楚这个问题,同源策略限制的是浏览器或者其他提供类似浏览器服务的软件,而且这仅仅是个规范,所以浏览器是否遵守这个规范也不一定,所以就会有上面的IE浏览器判断是否同源的时候并没有考虑端口号的问题。 2) 同源策略限制的是js,而图片,css这些是不存在同源策略限制的。
1.5 什么是跨域?
在某个网站的页面上通过js请求另外一个网站的数据,如果两个网站不满足同源策略,那么就存在跨域问题。
2. 为什么会有跨域问题?
由于在实际环境中,经常需要通过js获取一些数据,特别是ajax的流行,通过ajax加载某个网站的数据的场景就会经常遇到,而一旦有这样的需求,就可能会出现跨域的问题。
3. 如何判断我是否遇到了跨域问题?
一般来讲,如果你的请求被同源策略限制,浏览器的开发工具都会给出错误提示,在Chrome浏览器的console中,可能会有类似下面的提示:
4.如何解决跨域问题?
一般的思路是:通过一些妥协调整,绕过同源策略的限制。下面是我最近了解的一些方法。
为方便讲解,这里先举一个例子:
客户端采用H5开发,所有的数据都通过ajax请求从服务端获取。
客户端的页面都存放在静态文件服务器中,域名是http://static.demo.com
服务端提供接口供客户端调用,接口的参数和返回值都是JSON格式,服务端的域名是:http://server.demo.com。
如果不考虑跨域的问题,客户端与服务端的交互方式如下:
1.客户端post请求服务端,参数:{"key":"value"}
2.服务端返回结果:{"code":1,"data":"success"}
4.1 Jsonp方式
原理: 通过在页面中新增一个<script>标签,标签的src指向的是另外一个域的能够提供数据的url,同时将一个本地的callback方法传给服务端,服务端返回的时候将会自动执行callback方法。
实现举例:
1)服务端修改返回的数据类型为js,同时在请求参数中增加一个callback字段,这个字段用于客户端传递要执行的js方法名称。
2)客户端传递的参数中增加callback,同时将普通的ajax方法改成在页面中新增一个<script>节点的方式。
具体实现:
1.通过js在页面中append如下标签
<script type="application/javascript"
src="http://server.demo.com/Users/1234?callback=parseResponse"></script>
增加该标签之后,浏览器就会立即去请求这个url,由于<script src="">方式的是不受同源策略限制的,所以可以避免跨域限制。
2.服务端收到callback参数之后,将它拼接在返回的数据中,返回的数据如下:
parseResponse({"Name": "Foo", "Id": 1234, "Rank": 7});
3.这样返回之后,就调用页面上的parseResponse js方法,就达到了数据处理的目的。
4.最后将刚刚新增加到页面中的<script>元素删掉。
4.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){}
然后客户端页面加载的时候先去调用一下这个静态页面就好了。
4.3 CORS(Cross-Origin Resource Sharing)
原理MDN上讲的更清楚一些,点击这里。
其实简单来说就是服务端在响应头中添加一个Access-Control-Allow-Origin头部,头部的值为客户端的域名,比如:http://static.demo.com,这样就可以了。
但是需要注意的是:CROS分为两种,一种是简单请求,一种是复杂请求,简单请求按照上面的方式是可以的,如果是复杂请求,浏览器会进行两步,先发一个options请求,这个请求称之为“预请求”,预请求实际上是个OPTIONS请求,类似于一个探测作用,如果服务端返回的头部通过了预请求的内容,则浏览器才会发起第二个真实请求。这个后续我会有详细文章介绍。
4.4 客户端请求通过Nginx转发
原理:客户端的所有请求都直接发到客户端所在域名下,但是在客户端服务器增加一台nginx服务器,作为代理,如果是后端的url,直接代理转发到服务端,这样就不存在前端的跨域问题了。
举例:
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;
}
}
4.5 其他方式
1)WebSocket 2)Cross-document messaging
[参考资料]
- https://en.wikipedia.org/wiki/Same-origin_policy
- https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
- https://en.wikipedia.org/wiki/Cross-site_request_forgery
- https://en.wikipedia.org/wiki/Cross-site_scripting
- https://en.wikipedia.org/wiki/JSONP
- http://www.w3.org/TR/cors/
来源:oschina
链接:https://my.oschina.net/u/1029535/blog/550737