同源策略,它是由netscape提出的一个著名的安全策略。现在所有支持javascript的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。同源策略是浏览器的行为,是为了保护本地数据不被javascript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。
同源:协议 + 域名 + 端口。所以,怎么才算跨域呢?
什么是跨域,简单地理解就是因为javascript同源策略的限制,a.com 域名下的js无法操作b.com或是c.a.com域名下的对象。更详细的说明可以看下表:
第一,如果是协议和端口造成的跨域问题“前台”是无能为力的,
第二:在跨域问题上,域仅仅是通过“url的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。
“url的首部”指window.location.protocol +window.location.host,也可以理解为“domains, protocols and ports must match”。
接下来简单地总结一下在“前台”一般处理跨域的办法
对于主域相同而子域不同的例子,可以通过设置docume在网上找工作nt.domain的办法来解决。具体的做法是可以在http://www.a.com/a.html和http://script.a.com/b.html两个文件中分别加上document.domain = ‘a.com’;然后通过a.html文件中创建一个iframe,去控制iframe的contentdocument,这样两个js文件之间就可以“交互”了。当然这种办法只能解决主域相同而二级域名不同的情况,如果你异想天开的把script.a.com的domian设为alibaba.com那显然是会报错地!代码如下:
www.a.com上的a.html
document.domain = 'a.com';var ifr = document.createelement('iframe');ifr.src = 'http://script.a.com/b.html';ifr.style.display = 'none';document.body.appendchild(ifr);ifr.onload = function(){ var doc = ifr.contentdocument || ifr.contentwindow.document; // 在这里操纵b.html alert(doc.getelementsbytagname("h1")[0].childnodes[0].nodevalue);};
script.a.com上的b.html
document.domain = 'a.com';
这种方式适用于{www.jb51.com, jb51.com, script.jb51.com, css.jb51.com}中的任何页面相互通信。
备注:某一页面的domain默认等于window.location.hostname。主域名是不带www的域名,例如a.com,主域名前面带前缀的通常都为二级域名或大专可以转学校吗多级域名,例如www.a.com其实是二级域名。 domain只能设置为主域名,不可以在b.a.com中将domain设置为c.a.com。
问题:
1、安全性,当一个站点(b.a.com)被攻击后,另一个站点(c.a.com)会引起安全漏洞。
2、如果一个页面中引入多个iframe,要想能够操作所有iframe,必须都得设置相同domain。
虽然浏览器默认禁止了跨域访问,但并不禁止在页面中引用其他域的js文件,并可以自由执行引入的js文件中的function(包括操作cookie、dom等等)。根据这一点,可以方便地通过创建script节点的方法来实现完全跨域的通信。
这里判断script节点加载完毕还是蛮有意思的:ie只能通过script的readystatechange属性,其它浏览器是script的load事件。以下是部分判断script加载完毕的方法。
js.onload = js.onreadystatechange = function() { if (!this.readystate || this.readystate === 'loaded' || this.readystate === 'complete') { // callback在此处执行 js.onload = js.onreadystatechange = null; }};
这个办法比较绕,但是可以解决完全跨域情况下的脚步置换问题。原理是利用location.hash来进行传值。在url: http://a.com#helloword中的‘#helloworld’就是location.hash,改变hash并不会导致页面刷新,所以可以利用hash值来进行数据传递,当然数据容量是有限的。假设域名a.com下的文件cs1.html要和jb51.net域名下的cs2.html传递信息,cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向jb51.net域名下的cs2.html页面,这时的hash值可以做参数传递用。cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据(由于两个页面不在同一个域下ie、chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe;firefox可以修改)。同时在cs1.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一点有变化则获取获取hash值。代码如下:
先是a.com下的文件cs1.html文件:
function startrequest(){ var ifr = document.createelement('iframe'); ifr.style.display = 'none'; ifr.src = '//www.jb51.net/lab/cscript/cs2.html#paramdo'; document.body.appendchild(ifr);}function checkhash() { try { var data = location.hash ? location.hash.substring(1) : ''; if (console.log) { console.log('now the data is '+data); } } catch(e) {};}tinterval(checkhash, 2000);
jb51.net域名下的cs2.html:
//模拟一个简单的参数处理操作switch(location.hash){ ca '#paramdo': callback(); break; ca '#paramt': //do something…… break;}function callback(){ try { parent.location.hash = 'somedata'; } catch (e) { // ie、chrome的安全机制无法修改parent.location.hash, // 所以要利用一个中间的cnblogs域下的代理iframe var ifrproxy = document.createelement('iframe'); ifrproxy.style.display = 'none'; ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata'; // 注意该文件在"a.com"域下 document.body.appendchild(ifrproxy); }}
a.com下的域名cs3.html
//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值parent.parent.location.hash = lf.location.hash.substring(1);
当然这样做也存在很多缺点,诸如数据直接暴露在了url中,数据容量和类型都有限等……
文章较长列在此处不便于阅读,详细请看。
html5中最酷的新功能之一就是跨文档消息传输cross document messaging。下一代浏览器都将支持这个功能:chrome 2.0+、internet explorer 8.0+, firefox 3.0+, opera 9.6+, 和 safari 4.0+ 。 facebook已经使用了这个功能,用postmessage支持基于web的实时消息传递。
otherwindow.postmessage(message, targetorigin);
otherwindow:对接收信息页面的window的引用。可以是页面中iframe的contentwindow属性;window.open的返回值;通过name或下标从window.frames取到的值。
message:所要发送的数据,string类型。
targetorigin:用于限制otherwindow,“*”表示不作限制
a.com/index.html中的代码:
<iframe id="ifr" src="b.com/index.html"></iframe><script type="text/javascript">window.onload = function() { var ifr = document.getelementbyid('ifr'); var targetorigin = 'http://b.com'; // 若写成'http://b.com/c/proxy.html'效果一样 // 若写成'http://c.com'就不会执行postmessage了 ifr.contentwindow.postmessage('i was there!', targetorigin);};</script>
b.com/index.html中的代码:
<script type="text/javascript"> window.addeventlistener('message', function(event){ // 通过origin属性判断消息来源地址 if (event.origin == 'http://a.com') { alert(event.data); // 弹出"i was there!" alert(event.source); // 对a.com、index.html中window对象的引用 // 但由于同源策略,这里event.source不可以访问window对象 } }, fal);</script>
cors
这是w3c的标准,全称是”跨域资源共享”(cross-origin resource sharing)。
//指定允许其他域名访问‘access-control-allow-origin:http://172.20.0.206'//一般用法(,指定域,动态设置),3是因为不允许携带认证头和cookies//是否允许后续请求携带认证信息(cookies),该值只能是true,否则不返回‘access-control-allow-credentials:true'
上面第一行说到的access-control-allow-origin有多种设置方法:
设置是最简单粗暴的,但是服务器出四级听力怎么听于安全考虑,肯定不会这么干,而且,如果是的话,游览器将不会发送cookies,即使你的xhr设置了withcredentials
指定域,如上图中的http://172.20.0.206,一般的系统中间都有一个nginx,所以推荐这种
动态设置为请求域,多人协作时,多个前端对接一个后台,这样很方便。
respon支持跨域
从上面控制台的输出可以看到,错误原因是请求的资源(接口)的header中没有”access-control-allow-origin“,那我们可以给它加上。在哪加?既然说是请求的资源没有,那当然是在请求的资源上加,也就是服务端。
@springbootapplication@configuration@restcontrollerpublic class applicationa { public static void main(string[] args) { springapplication.run(applicationa.class, args); } @requestmapping("/test") public object test(httprvletrequest request, httprvletrespon respon) { // 跨域支持 respon.theader("access-control-allow-origin", "*"); respon.theader("access-control-allow-methods", "post,get,put,delete"); respon.theader("access-control-max-age", "3600"); respon.theader("access-control-allow-headers", "*"); respon.theader("access-control-allow-credentials", "true"); map<string, object> map = new hashmap<>(); map.put("success", true); map.put("msg", "我来自服务端"); return map; }}
springboot支持跨域
测试用例是一个springboot项目,可以用更简单的方式。通过一个继承了webmvcconfigureradapter的bean,重写addcorsmappings方法,在方法里配置。
@springbootapplication@configuration@restcontrollerpublic class applicationa extends webmvcconfigureradapter { public static void main(string[] args) { springapplication.run(applicationa.class, args); } @requestmapping("/test") public object test(httprvletrequest request, httprvletrespon respon) { map<string, object> map = new hashmap<>(); map.put("success", true); map.put("msg", "我来自服务端"); return map; } // 跨域支持 @override public void addcorsmappings(corsregistry registry) { registry.addmapping("/**") .allowedorigins("*") .allowcredentials(true) .allowedmethods("get", "post", "delete", "put") .maxage(3600); }
1、如果服务端是java开发的,添加如下设置允许跨域即可,但是这样做是允许所有域名都可以访问,不够安全。
respon.theader(“access-control-allow-orig十旬休假in”,”*”);
2、为保证安全性,可以只添加部分域名允许访问,添加位置可以在下面三处任选一个。
(1)可以在过滤器的filter的dofilter()方法种设置。
(2)可以在rvlet的get或者post方法里面设置。
(3)可以放在访问的jsp页面第一行。
3、在此用第一种方法,注意web.xml配置过滤器(filter)。
public void dofilter(rvletrequest req, rvletrespon res,filterchain chain) throws ioexception, rvletexception { // 将rvletrespon转换为httprvletrespon httprvletrespon httprespon = (httprvletrespon) res; // 如果不是80端口,需要将端口加上,如果是集群,则用nginx的地址,同理不是80端口要加上端口string [] allowdomain= {"http://www.baidu.com","http://123.456.789.10","http://123.16.12.23:8080"};t allowedorigins= new hasht(arrays.aslist(allowdomain));string originheader=((httprvletrequest) req).getheader("origin");if (allowedorigins.contains(originheader)){ httprespon.theader("access-control-allow-origin", originheader); httprespon.tcontenttype("application/json;chart=utf-8"); httprespon.theader("access-control-allow-methods", "post, get, options, delete"); httprespon.theader("access-control-max-age", "3600"); httprespon.theader("access-control-allow-headers", "content-type,access-token"); // 如果要把cookie发到服务器,需要指定access-control-allow-credentials字段为true httprespon.theader("access-control-allow-credentials", "true"); httprespon.theader("access-control-expo-headers", "*"); } chain.dofilter(req, res);}
目前很多请求都不是直接暴露的,很多通过nginx做反向代理,因此可以使用nginx配置固定请求的access-control-allow-origin,实现跨域访问。方式是在被请求的接口,配置location代理,添加header实现。
#活动访问接口跨域配置 location /promotion/activitypro { proxy_pass http://fronthost/promotion/activitypro; add_header 'access-control-allow-origin' '*'; #add_header 'access-control-allow-methods' 'get, post'; #add_header 'access-control-allow-credentials' "true"; #add_header 'access-control-max-age' 86400; #add_header 'access-control-allow-header' 'content-type,*'; }
有前端经验的童鞋知道,有时我们会在自己的代码里直接引入其它域名的js、css等静态文件。为啥这些静态文件没被浏览器限制呢?通常为了减轻web服务器的压力,我们会把js、css,img等静态资源分离到另一台独立域名的服务器上,使其和前端分离开。基于这个原因,浏览器并没有限制这类静态资源的跨域访问。
我们可以动态地创建一个script,让浏览器以为我们要获取静态资源,从而网开一面。而服务器端也需要做一点改变,不能直接返回json,而是返回一个立即执行的函数,而前端请求的结果就作为函数的参数。
后端接口返回
@springbootapplication@configuration@restcontrollerpublic class applicationa { public static void main(string[] args) { springapplication.run(applicationa.class, args); } @requestmapping("/test") public string test(httprvletrequest request, httprvletrespon respon, string callback) throws ioexception { map<string, object> map = new hashmap<>(); map.put("success", true); map.put("msg", "我来自服务端"); // 返回值如下: // callback({"msg":"我来自服务端","success":true}); return string.format("%s(%s);", callback, jsonutil.tojson(map)); }
function test() { // 外部域名,参数是和后端接口约定的callback指定接口返回后的回调函数 url = "http://localhost:8882/test?callback=_ajax_callback"; // 创建一个script元素 var script = document.createelement('script'); script.type = 'text/javascript'; script.src = url; document.head.appendchild(script);}// 接口回调function _ajax_callback(res) { console.log("被回调了"); console.log(res);}
$.ajax({ url: 'http://localhost:8882/test', type: 'get', datatype: 'jsonp', // 请求方式 jsonpcallback: "_ajax_callback", // 回调函数名 data: {}});
this.$http.jsonp(‘http://localhost:8882/test', {undefinedparams: {},jsonp: ‘_ajax_callback'}).then((res) => {undefinedconsole.log(res);})
优点:它不像xmlhttprequest对象实现的ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要xmlhttprequest或activex的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
缺点:它只支持get请求而不支持post等其它类型的http请求;它只支持跨域http请求这种情况,不能解决不同域的两个页面之间如何进行javascript调用的问题。
其它方式支持跨域
nginx二手车协议反向代理:前端访问相同域名,nginx再根据需要把请求转发到外部域名;
后端代理:在后端接口里先请求外部资源(比如用httpclient),然后把结果返回给前端,这样就不是跨域了;
其它:借助iframe、postmessage等也可实现跨域。
document.domain,window.name,web sockets
本文发布于:2023-04-04 20:43:31,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/639455936f2951898c7b977187e558ac.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:JS跨域(Access.doc
本文 PDF 下载地址:JS跨域(Access.pdf
留言与评论(共有 0 条评论) |