同源策略:
是由NetScape提出的著名的安全策略,所有支持javaScript的浏览器都使用这个策略。同源策略限制了一个源中加载文本或脚本与来自其它源中资源的交互方式。
IE特例:
授信范围(Trust Zones):两个相互之间高度互信的域名,如公司域名(corporate domains),不遵守同源策略的限制。
端口:IE未将端口号加入到同源策略的组成部分之中,因此 http://company.com:81/index.html 和http://company.com/index.html 属于同源并且不受任何限制。
以上参考自MDN。
所谓同源是指:域名、协议、端口都相同。同源策略是浏览器最核心也最基本的安全功能
。这是知乎上关于同源策略的一篇文章。。但是浏览器在安全性和实用性上做出了让步,img/script/style/iframe等有src属性的都可以跨域引用资源。
方案一——通过JSONP跨域
jsonp是JSON with padding的简写,看起来与json差不多,但是包含在函数调用中的json,利用动态script元素来使用(具有src属性的如img,iframe,srcipt都不受同源策略的影响)。该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
如果使用jquery,可以在type为get的时候dataType设为jsonp,就可以了。
$.ajax({
url: 'http://www.qdaily.com/get_user_and_radar.json?winWidth=1280&winHeight=800',
type: 'get',
dataType: 'jsonp',
data: {},
})
.done(function(data) {
console.log(data.status);
})
然后,很幸运的就可以看到这个:
但是可以看出其实是请求到数据的,只是没有执行我们的callBack函数。
因为前面有提到:服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,也就是服务端的返回需要拼接成这样(比如函数名叫做haddleData):
haddleData({"status":true,subscribes:["12","23"]})
另外:jquery的ajax方法可以传入两个参数
jsonp : "callback"//设置这个会替换浏览器发送请求时地址后面自动添加的?callback=xxx中的callback这个字,一般情况下不用传这个参数
jsonpCallback: "success_jsonpCallback"//这个值将用来取代jQuery自动生成的随机函数名,也就是上句话中的'xxx'。
【这儿遗留了一个问题,还请各位解决】如果jsonpCallback不手动设置的话,jquery是会自动生成个随机的字符串的,但是服务器那边的函数名要写什么,回调函数才会正确的执行呢?
看一个运行成功的实例:
$.ajax({
url: 'http://localhost:8000/remote/remote.js',
type: 'get',
dataType: 'jsonp',
jsonp: 'back',
jsonpCallback: "haddleData",
data: {},
})
.done(function(data) {
console.log(data.status);
})
//这段程序运行在8080端口的
remote.js作为远端其中写了句
haddleData({"status":true,subscribes:["12","23"]})
对于大部分js初学者,大多使用jquery,而对原生不太熟悉。但是须知,ajax和jsonp其实本质上是不同的东西,ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本,利用脚本下载下来后立即执行的事实实现callback方法的调用。
原生js的jsonp简单实现:
<script>
function haddleData(data){
console.log(data.status);
}
</script>
<script type="text/javascript" src="http://localhost:8000/remote/remote.js"></script>
当然有多个本地函数需要处理的时候,加上回调函数名才是方便的,像这样:
<script type="text/javascript" src="http://localhost:8000/remote/remote.js?callback=haddleData"></script>
加上callback参数,可以使得服务器端根据前端指定的方法名cb动态返回cb(data)
;而不是都写死handleData(data)
;
【总结下jsonp】:
- 优点:
简单,函数回调在本地处理; - 缺点:
1、安全性(存在注入漏洞,如CSRF,XSS);
2、如果出现错误,不会像http请求那样有状态码;
3、只能使用get请求;
方案二——CORS(Cross-Origin Resource Sharing)
这是一个W3C标准(显然比jsonp背景深厚许多),同样需要浏览器和服务器同时支持,但是整个通信过程,都是浏览器自动完成,不需要用户参与,就像平时写Ajax一样(如果使用的是jquery的话)。
下面是原生js实现CORS的代码
function createCORSRequest(method,url){
var xhr=new XMLHttpRequest();
if("withCredentials" in xhr){
xhr.open(method,url,true);
}else if(typeof XDomainRequest != "undefined"){//IE10之前的版本使用XDmainRequest支持CORS
xhr=new XDomainRequest();
xhr.open(method,url);
}else{
xhr=null;
}
return xhr;
}
var request=createCORSRequest("get","待访问的地址");
if(request){
request.onload=function(data){
//do sth
};
request.send();
}
适用场景:
承载的信息量大,get形式搞不定,需选用post传输。CORS支持所有类型的传输。
兼容性:
移动端全面支持(除opera mini),PC上IE8+。
CORS思想:
使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。(请求和响应都不包含cookie)
当然如果设置成下面这样,所有的跨域都可以实现了,但这样毕竟太不安全。
"Access-Control-Allow-Origin:*";//允许任何域向我们的服务器发送请求
一般情况下,浏览器发送一个额外的Origin头部(由浏览器自动生成发送)
Origin:http://localhost:8080//本地网址
然后由服务器发送一个响应表头:Access-Control-Allow-Origin,如果服务器接收该请求,返回值(只能是通配符或单域名。)就和请求值一样。
Access-Control-Allow-Origin:http://localhost:8080
=>CORS方案的重点其实就在于服务器端的配置。
简单请求
- 只使用 GET, HEAD 或者 POST 请求方法。
如果使用 POST 向服务器端传送数据,则数据类型(Content-Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一种。
- 不会使用自定义请求头(类似于 X-Modified 这种)。
HTTP头部信息不超出以下{Accept,Accept-Language,Content-Language,Last-Event-ID,content-type(只限于上面提到的3种类型)}
对于简单请求,浏览器直接发出CORS请求。浏览器会自动在头信息(Request Headers)中,添加一个Origin 字段,来表明本次请求来自哪个域。
如果这个源不在许可范围内,会报错: No 'Access-Control-Allow-Origin' header is present on the requested resource.
如果Origin指定的域名在许可范围内(必须是跨域了的),Response Headers中会多出几个头信息字段。
Access-Control-Allow-Credentials:true//值为true表示允许发送cookie
Access-Control-Allow-Methods:GET, POST, OPTIONS
Access-Control-Allow-Origin:http://localhost:8080
Access-Control-Max-Age:1728000
withCredentials属性
因为CORS默认不发送cookie和http认证,如果要把Cookie发到服务器,就要指定Access-Control-Allow-Credentials:true;
另外AJAX中也要打开withCredentials属性。
var xhr=new XMLHttpRequest();
xhr.withCredentials=true;
jquery ajax请求参数中加入
xhrFields: {
withCredentials: true
}
非简单请求
除了上面说的简单请求外都是非简单请求,比如:请求方法是PUT
或DELETE,或者Content-Type字段的类型是application/json,又或者有自定义请求头Access-Control-Request-Headers: X-Custom-Header。
比如,我添加自定义请求头
xhr.setRequestHeader('Some-Custom-Response-Header', 'value');
就会发现连续向同一地址请求了两次,而第一次请求什么值也没拿到
第二次请求
这是因为浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。"预检"请求之后,浏览器球会进行正常CORS请求。
服务器端配置(rails为例)
1、可以参考这个方法:为 RESTful API 配置 CORS 实现跨域请求,写的挺详细的。然而对于rails并不算熟悉的我来说,有gem包,怎么可能放着不用呢~
2、这是Rack CORS 中间件的项目地址,按照文档的说明1分钟就可以基本搞定。
【问题:】
当我请求方式为put时,却出现了404错误,是因为没有为options提供路由么?
rails中使用CORS中的功能和细节还有很多,等到需要时再行挖掘吧,欢迎使用过的各位一起交流问题。
【参考】
http://mp.weixin.qq.com/s?__biz=MjM5MTA1MjAxMQ==&mid=402804565&idx=1&sn=415fc9eab30edcb66227ed5e099f9a66&scene=1&srcid=0413psVAfbsElTaWvrVas6xM#rd
http://www.ruanyifeng.com/blog/2016/04/cors.html
http://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery.html
http://www.cnblogs.com/know/archive/2011/10/09/2204005.html
http://blog.csdn.net/xiaoxian8023/article/details/27817861