赞
踩
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域 。
请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。
浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对Dom的查询
阻止接口请求比较好理解,比如用ajax从http://192.168.100.150:8020/实验/jsonp.html页面向http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp发起请求,由于两个url端口不同,所以属于跨域,在console打印台会报No 'Access-Control-Allow-Origin' header is present on the requested resource
值得说的是虽然浏览器禁止用户对请求返回数据的显示和操作,但浏览器确实是去请求了,如果服务器没有做限制的话会返回数据的,在调试模式的network中可以看到返回状态为200,且可看到返回数据
关于iframe中对象的获取方式请看:js iframe获取documen中的对象为空问题_lianzhang861的博客-CSDN博客_获取iframe的document
比如a页面中嵌入了iframe,src为不同源的b页面,则在a中无法操作b中的dom,也没有办法改变b中dom中的css样式。
而如果ab是同源的话是可以获取并操作的。
-
- <html>
- <head>
- <meta charset="UTF-8">
- <title></title>
- <style type="text/css">
- iframe{
- width:100%;height:800px;
- }
- </style>
- </head>
- <body>
- <!--<iframe src="http://192.168.100.150:8081/zhxZone/webmana/attachment/imageManager" frameborder="0" id="iframe"></iframe>-->
- <iframe src="http://192.168.100.150:8020/实验/jsonp.html" frameborder="0" id="iframe"></iframe>
- <script type="text/javascript">
- var i=document.getElementById("iframe");
- i.onload=function(){
- /*console.log(i.contentDocument)
- console.log(i.contentWindow.document.getElementById("text").innerHTML)*/
- var b=i.contentWindow.document.getElementsByTagName("body")[0];
- i.contentWindow.document.getElementById("text").style.background="gray";
- i.contentWindow.document.getElementById("text").innerHTML="111";
- }
- </script>
- </body>
- </html>
改变了iframe中的元素
甚至是可以获取iframe中的cookie
- var i=document.getElementById("iframe");
- i.onload=function(){
- console.log(i.contentDocument.cookie)
- }
不用说也知道这是极为危险的,所以浏览器才会阻止非同源操作dom,浏览器的这个限制虽然不能保证完全安全,但是会增加攻击的困难性
虽然安全机制挺好,可以抵御坏人入侵,但有时我们自己需要跨域请求接口数据或者操作自己的dom,也被浏览器阻止了,所以就需要跨域
跨域的前提肯定是你和服务器是一伙的,你可以控制服务器返回的数据,否则跨域是无法完成的 。
jsonp是前端解决跨域最实用的方法
原理:html中 的link,href,src属性都是不受跨域影响的,link可以调用远程的css文件,href可以链接到随便的url上,图片的src可以随意引用图片,script的src属性可以随意引入不同源的js文件
看下面代码,a.html页面中有一个func1方法,打印参数ret
-
- <body>
- <script type="text/javascript">
- function func1(ret){
- console.log(ret)
- }
- </script>
- <script src="http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp.js" type="text/javascript" charset="utf-8"></script>
- </body>
而引入的jsonp.js中的代码为:
func1(111)
可想而知结果会打印出 111,也就是说a页面获取到了jsonp.js中的数据,数据是以调用方法并将数据放到参数中返回来的
但是这样获取数据,必须a.html中的方法名与js中的引用方法名相同,这样就是麻烦很多,最好是a.html能将方法名动态的传给后台,后台返回的引入方法名就用我传给后台的方法名,这样就做到了由前台控制方法名
总之要做到的就是前台像正常调接口一样,后台要返回回来js代码即可
现在改为动态方法名:我请求的接口传入callback参数,值为方法名func1
- <body>
- <script type="text/javascript">
- function func1(ret){
- console.log(ret)
- }
-
- </script>
- <script src="http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp?callback=func1" type="text/javascript" charset="utf-8"></script>
- </body>
后台返回不同编程语言不同,我使用的是java,所以我展示一下java返回的方法
- //jsonp测试
- @ResponseBody
- @RequestMapping(value = "jsonp", produces = "text/plain;charset=UTF-8")
- public void jsonp(String callback, HttpServletRequest req, HttpServletResponse res) {
- List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
- RetBase ret=new RetBase();
- try {
- res.setContentType("text/plain");
- res.setHeader("Pragma", "No-cache");
- res.setHeader("Cache-Control", "no-cache");
- res.setDateHeader("Expires", 0);
- Map<String,Object> params = new HashMap<String,Object>();
- list = dictService.getDictList(params);
- ret.setData(list);
- ret.setSuccess(true);
- ret.setMsg("获取成功");
- PrintWriter out = res.getWriter();
- //JSONObject resultJSON = JSONObject.fromObject(ret); //根据需要拼装json
- out.println(callback+"("+JSON.toJSONString(ret)+")");//返回jsonp格式数据
- out.flush();
- out.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
上述java代码相当于我返回了 " func1(数据) "的代码,所以返回数据成功打印,完成了跨域请求
到这里,每次请求数据还要引入一个js才行,代码有些杂乱,前端可以继续优化代码,动态的生成script标签
- <script type="text/javascript">
- function func1(ret){
- console.log(ret)
- }
- var url="http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp?callback=func1";
- var s=document.createElement("script");
- s.setAttribute("src",url);
- document.getElementsByTagName("head")[0].appendChild(s);
-
- </script>
这样,原生的jsonp跨域就基本完成了,但是用起来不是很方便
我推荐使用jquery封装的jsonp,使用起来相对简单
使用起来跟使用ajax类似,只是dataType变成了jsonp,且增加了jsonp参数,参数就是上述的callback参数,不需要管他是啥值,因为jq自动给你起了个名字传到后台,并自动帮你生成回调函数并把数据取出来供success属性方法来调用
jq jsonp标准写法:
- $.ajax({
- type: 'get',
- url: "http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp",
- dataType: 'jsonp',
- jsonp:"callback",
- async:true,
- data:{
-
- },
- success: function(ret){
- console.log(ret)
- },
- error:function(data) {
- },
- });
jq jsonp的简便写法:
- $.getJSON("http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp?callback=?",function(ret){
- console.log(ret)
- })
后台接收到的callback参数,jq自己起的名字
这样使用起来就跟ajax一样顺手了,把返回的值在success中操作即可,只不过是可以跨域了
这里针对ajax与jsonp的异同再做一些补充说明:
1、ajax和jsonp这两种技术在调用方式上”看起来”很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery框架都把jsonp作为ajax的一种形式进行了封装。
2、但ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加
跨域有很多种解决方案,如果你在使用SpringMVC来开发服务器的话,这个方法会对你有所帮助。它定义了一个Filter来实现跨域请求的问题,实现从不同的服务器上获取HTTP请求资源。cors-filter为第三方组件,官网地址
pom.xml引入依赖:
- <dependency>
- <groupId>com.thetransactioncompany</groupId>
- <artifactId>cors-filter</artifactId>
- <version>2.5</version>
- </dependency>
web.xml配置:
- <filter>
- <filter-name>CORS</filter-name>
- <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
- //init-param都是初始化参数配置
- <init-param>
- <param-name>cors.allowGenericHttpRequests</param-name>
- <param-value>true</param-value>
- </init-param>
- <init-param>
- <param-name>cors.allowOrigin</param-name>
- <param-value>*</param-value>
- </init-param>
- <init-param>
- <param-name>cors.allowSubdomains</param-name>
- <param-value>false</param-value>
- </init-param>
- <init-param>
- <param-name>cors.supportedMethods</param-name>
- <param-value>GET, HEAD, POST,OPTIONS,PUT</param-value>
- </init-param>
- <init-param>
- <param-name>cors.supportedHeaders</param-name>
- <param-value>*</param-value>
- </init-param>
- <init-param>
- <param-name>cors.exposedHeaders</param-name>
- <param-value>X-Test-1, X-Test-2</param-value>
- </init-param>
- <init-param>
- <param-name>cors.supportsCredentials</param-name>
- <param-value>true</param-value>
- </init-param>
- <init-param>
- <param-name>cors.maxAge</param-name>
- <param-value>3600</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>CORS</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
CORS背后的基本思想是使用自定义的HTTP头部允许浏览器和服务器相互了解对方,从而决定请求或响应成功与否.
*这其实和第2中方法(后台配置)基本相同,都是通过过滤器在response中返回头部,使服务器和浏览器可互通
Access-Control-Allow-Origin:指定授权访问的域
Access-Control-Allow-Methods:授权请求的方法(GET, POST, PUT, DELETE,OPTIONS等)适合设置单一的(或全部)授权访问域,所有配置都是固定的,特简单。也没根据请求的类型做不同的处理
- public class SimpleCORSFilter implements Filter{
-
- @Override
- public void destroy() {
-
- }
-
- @Override
- public void doFilter(ServletRequest req, ServletResponse res,
- FilterChain chain) throws IOException, ServletException {
- HttpServletResponse response = (HttpServletResponse) res;
- response.setHeader("Access-Control-Allow-Origin", "*"); //允许所有请求进行跨域请求
- response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
- response.setHeader("Access-Control-Max-Age", "3600");
- response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
- chain.doFilter(req, res);
-
- }
-
- @Override
- public void init(FilterConfig arg0) throws ServletException {
-
- }
-
- }
在web.xml中需要添加如下配置:
- <filter>
- <filter-name>cors</filter-name>
- <filter-class>com.ssm.web.filter.SimpleCORSFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>cors</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- </filter>
为单个方法提供跨域访问,直接添加请求头:
- response.setHeader("Access-Control-Allow-Origin", "*");
- response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
- response.setHeader("Access-Control-Max-Age", "3600");
- response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
或者 :只允许某一个地址进行跨域访问
- response.addHeader("Access-Control-Allow-Origin", "http://192.168.100.150:8020");
- //response.addHeader("Access-Control-Allow-Origin", "*");
- response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
- response.addHeader("Access-Control-Allow-Headers", "Content-Type");
- response.addHeader("Access-Control-Max-Age", "1800");//30 min
使用HttpClinet转发进行转发(简单的例子 不推荐使用这种方式)
- try {
- HttpClient client = HttpClients.createDefault(); //client对象
- HttpGet get = new HttpGet("http://localhost:8080/test"); //创建get请求
- CloseableHttpResponse response = httpClient.execute(get); //执行get请求
- String mes = EntityUtils.toString(response.getEntity()); //将返回体的信息转换为字符串
- System.out.println(mes);
- } catch (ClientProtocolException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
此方法有介绍价值,因为关系到操作dom方面的跨域
上述方法都只能解决请求跨域,而无法解决跨域操作dom,因为想操作dom条件比较苛刻,必须这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致。
@(StuRep)
document.domain
用来得到当前网页的域名。
比如在地址栏里输入:
- 代码如下:
- javascript:alert(document.domain); //www.jb51.net
我们也可以给document.domain属性赋值,不过是有限制的,你只能赋成当前的域名或者基础域名。
比如:
- 代码如下:
- javascript:alert(document.domain = "jb51.net"); //jb51.net
- javascript:alert(document.domain = "www.jb51.net"); //www.jb51.net
上面的赋值都是成功的,因为www.jb51.net是当前的域名,而jb51.net是基础域名。
但是下面的赋值就会出来"参数无效"的错误:
- 代码如下:
- javascript:alert(document.domain = "cctv.net"); //参数无效
- javascript:alert(document.domain = "www.jb51.net"); //参数无效
因为cctv.net与www.jb51.net不是当前的域名也不是当前域名的基础域名,所以会有错误出现。这是为了防止有人恶意修改document.domain来实现跨域偷取数据。
利用document.domain 实现跨域:
前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域.
Javascript出于对安全性的考虑,而禁止两个或者多个不同域的页面进行互相操作。
相同域的页面在相互操作的时候不会有任何问题。
比如在:aaa.com的一个网页(a.html)里面 利用iframe引入了一个bbb.com里的一个网页(b.html)。
这时在a.html里面可以看到b.html里的内容,但是却不能利用javascript来操作它。因为这两个页面属于不同的域,在操作之前,js会检测两个页面的域是否相等,如果相等,就允许其操作,如果不相等,就会拒绝操作。
这里不可能把a.html与b.html利用JS改成同一个域的。因为它们的基础域名不相等。(强制用JS将它们改成相等的域的话会报跟上面一样的"参数无效错误。")
所以如果在a.html里引入aaa.com里的另一个网页,是不会有这个问题的,因为域相等。
有另一种情况,两个子域名:
aaa.xxx.com
bbb.xxx.com
aaa里的一个网页(a.html)引入了bbb 里的一个网页(b.html),
这时a.html里同样是不能操作b.html里面的内容的。
因为document.domain不一样,一个是aaa.xxx.com,另一个是bbb.xxx.com。
这时我们就可以通过Javascript,将两个页面的domain改成一样的,
需要在a.html里与b.html里都加入:
- 代码如下:
- document.domain = "xxx.com";
这样这两个页面就可以互相操作了。也就是实现了同一基础域名之间的"跨域"。
window对象有一个name属性,该属性有一个特征:即在一个窗口的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每一个页面对window.name都有读写的权限,window.name是持久的存在于一个窗口载入的所有页面中的,并不会因为新的页面的载入而被重置。
下面为a.html中代码:
- <script>
- window.name = '我是页面a中设置的值';
- setInterval(function(){
- window.location = 'b.html';
- },2000)//两秒后把一个新页面b.html载入到当前的window中
- </script>
b.html中的代码
- <script>
- console.log(window.name);//读取window.name的值
- </script>
上诉代码形成的效果就是:a.html页面载入2s后,跳转到b.html页面,b.html会在控制台输出‘我是页面a中设置的值’;
我们可以看到b.html页面上成功输出了a.html页面中设置的window.name的值,如果在之后所有载入的页面都没对window.name进行修改的话,那么所有的这些页面获取到的window.name的值都是a.html页面中设置的那个值;
不过要注意的是:window.name的值只能是字符串的形式,这个字符串的大小最大只能允许2M左右,具体取决于不同的浏览器,但是一般是够用了。
上面的例子中,我们用到的页面a.html和b.html是处于同一个域的,但是即使a.html与b.html处于不同的域中,上述结论同样是适用的,这也正是利用window.name进行跨域的原理;
实例说明:下面就来看一看具体是如何通过window.name来跨域获取数据的;
比如有一个www.example.com/a.html页面。需要通过a.html页面里的js来获取另一个位于不同域上的页面www.cnblogs.com/data.html中的数据。
data.html页面中的设置一个window.name即可,代码如下:
- <script>
- window.name = "我是data.html中设置的a页面想要的数据";
- </script>
那么接下来问题来了,我们怎么把data.html页面载入进来呢,显然我们不能直接在a.html页面中通过改变window.location来载入data.html页面(因为我们现在需要实现的是a.html页面不跳转,但是也能够获取到data.html中的数据);
具体的实现其实就是在a.html页面中使用一个隐藏的iframe来充当一个中间角色,由iframe去获取data.html的数据,然后a.html再去得到iframe获取到的数据。充当中间人的iframe想要获取到data.html中通过window.name设置的数据,只要要把这个iframe的src设置为www.cnblogs.com/data.html即可,然后a.html想要得到iframe所获取到的数据,也就是想要得到iframe的widnow.name的值,还必须把这个iframe的src设置成跟a.html页面同一个域才行,不然根据同源策略,a.html是不能访问到iframe中的window.name属性的。
以下为a.html中的代码:
- <body>
- <iframe id="proxy" src="http://www.cnblogs.com/data.html" style="display: none;" onload = "getData()">
-
- <script>
- function getData(){
- var iframe = document.getElementById('proxy);
- iframe.onload = function(){
- var data = iframe.contentWindow.name;
- //上述即为获取iframe里的window.name也就是data.html页面中所设置的数据;
- }
- iframe.src = 'b.html';
- }
- </script>
- </body>
这个跨域主要是用于多iframe窗口之间消息传递或者父窗口与iframe传递消息的,属于比较狭义的跨域。比如在a界面修改内容,点击保存后b页面的表格自动刷新就可以使用这个。或者子iframe做了事件,父在跨域的情况下无法获取子的事件,但通过消息传递就可以间接获取到事件。
前提:跨域和被跨域的一方都是你可以控制的,一方写发送消息的,另一方写接收消息方法
注意这跨域的局限性在于必须在同一个window对象上,也就是说哪个window发送消息,只有本window才能接收到。
主要语法:
发送message:
window.postMessage(message, targetOrigin);
监听并接收message:
- window.parent.addEventListener("message",function(res){
- console.log(res)
- })
详细介绍看这个 html5 postMessage的跨域问题_lianzhang861的博客-CSDN博客_postmessage跨域
这里面详细介绍了应用场景和注意事项
服务网关(zuul)又称路由中心,用来统一访问所有api接口,维护服务。
Spring Cloud Zuul通过与Spring Cloud Eureka的整合,实现了对服务实例的自动化维护,所以在使用服务路由配置的时候,我们不需要向传统路由配置方式那样去指定具体的服务实例地址,只需要通过Ant模式配置文件参数即可。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。