什么是跨域问题,怎么解决跨域问题?

什么是跨域?

当一个请求url的协议域名端口三者之间任意一个与当前页面url不同即为跨域

请注意localhost和127.0.0.1虽然都指向本机,但也属于跨域。

跨域会阻止什么操作?

        浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对Dom的查询

1.阻止接口请求

        阻止接口请求比较好理解,比如用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,且可看到返回数据

2.阻止dom获取和操作

        关于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,也被浏览器阻止了,所以就需要跨域
跨域的前提肯定是你和服务器是一伙的,你可以控制服务器返回的数据,否则跨域是无法完成的 。

解决跨域的方法

1、前端方法就用jsonp

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的核心则是动态添加

2、CorsFilter

SpringMVC

跨域有很多种解决方案,如果你在使用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>

springboot CORS 跨域请求解决方案

 请点击:springboot CORS 跨域请求解决方案

3、通过 java  Filter(CORS)解决跨域

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

4、后台HttpClinet请求转发

使用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();
}

5、通过Nginx反向代理 

        Nginx 解决跨域问题

6、通过修改document.domain来跨子域

此方法有介绍价值,因为关系到操作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";

这样这两个页面就可以互相操作了。也就是实现了同一基础域名之间的"跨域"。

7、通过window.name跨域

        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>

8、通过HTML5中新引进的window.postMessage方法来跨域传送数据

这个跨域主要是用于多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跨域

这里面详细介绍了应用场景和注意事项

9、使用SpringCloud网关

服务网关(zuul)又称路由中心,用来统一访问所有api接口,维护服务。
Spring Cloud Zuul通过与Spring Cloud Eureka的整合,实现了对服务实例的自动化维护,所以在使用服务路由配置的时候,我们不需要向传统路由配置方式那样去指定具体的服务实例地址,只需要通过Ant模式配置文件参数即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值