java、ajax 跨域请求解决方案('Access-Control-Allow-Origin' header is present on the requested resou...

 

1.情景展现

  ajax调取java服务器请求报错javascript

  报错信息以下:html

  'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. 前端

  可是,请求状态倒是成功:200,这是怎么回事? java

2.缘由分析

  ajax请求跨域:ajax出现请求跨域错误问题是由于浏览器的“同源策略”。web

  同源策略:1995年,同源政策由 Netscape 公司引入浏览器。目前,全部浏览器都实行这个政策。ajax

  所谓"同源"指的是"三个相同":协议相同&域名相同&端口相同,这三个要求必须一致,不然就叫跨域。json

  同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据(好比:同源策略能够限制不一样网站cookie共享的问题,经过cors设置照样能够实现cookie共享)。segmentfault

  同源政策规定,AJAX请求只能发给同源的网址,不然就报错。 跨域

3.ajax解决方案

  方法一:JSONP浏览器

  简述:网页经过添加一个<script>元素,向服务器请求JSON数据,这种作法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

  可是,只能发送get请求,比较鸡肋,其优点在于支持老式浏览器,以及能够向不支持CORS的网站请求数据。

  方法二:WebSocket

  WebSocket是一种通讯协议,使用ws://(非加密)和wss://(加密)做为协议前缀。该协议不实行同源政策,只要服务器支持,就能够经过它进行跨源通讯。

  方法三:CORS(推荐使用)

  CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS支持全部类型的HTTP请求。

  它容许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

  CORS须要浏览器和服务器同时支持。目前,全部浏览器都支持该功能,IE浏览器不能低于IE10。

  整个CORS通讯过程,都是浏览器自动完成,不须要用户参与。对于开发者来讲,CORS通讯与同源的AJAX通讯没有差异,代码彻底同样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感受。

  所以,实现CORS通讯的关键是服务器,只要服务器实现了CORS接口,就能够跨源通讯。

  java服务器设置

  第一步:jar包配置

  所需jar包:cors-filter-2.8.jar和java-property-utils-1.9.1.jar(这两个库文件放到对应项目的WEB-INF/lib/下) 

  若是是maven项目,将以下代码添加到pom.xml中

<dependency>
    <groupId>com.thetransactioncompany</groupId>
    <artifactId>cors-filter</artifactId>
    <version>[ version CORS过滤器的最新的稳定版本 ]</version>
</dependency>

  第二步:添加CORS配置到项目的web.xml中( App/WEB-INF/web.xml)

<!-- 跨域配置CORS--> 
<filter>
    <!-- The CORS filter with parameters -->
    <filter-name>CORSFilter</filter-name>
    <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
    <!-- Note: All parameters are options, if omitted the CORS Filter will 
        fall back to the respective default values. -->
    <!-- 是否容许http请求 -->
    <init-param>
        <param-name>cors.allowGenericHttpRequests</param-name>
        <param-value>true</param-value>
    </init-param>
    <!--
        容许跨域的域名(发送请求至该项目的地址、请求源)
        构成(http://域名:端口号),好比(http://192.168.191.115:8080)
    -->
    <init-param>
        <param-name>cors.allowOrigin</param-name>
        <!--
            *,表示:容许全部跨域请求,这样的后果是:当须要客户端请求携带cookie时,浏览器没法携带cookie至服务器
            能够在servlet中动态设置:response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
            这,一样是容许全部跨域请求,servlet的设置会覆盖该属性设置。
       可是,当实际测试后发现,这里的*永远指向的是请求头的Origin的值,因此不须要再进行额外的设置。 --> <param-value>*</param-value> </init-param> <!-- 容许子域 --> <init-param> <param-name>cors.allowSubdomains</param-name> <param-value>false</param-value> </init-param> <!-- 容许的请求方式(非简单请求必须添加OPTIONS,由于"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。) --> <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>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value> </init-param> <!-- 自定义暴露本身的请求头(自定义设置后Response Headers里会显示Access-Control-Expose-Headers及值) CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma; 若是想拿到其余字段,就必须在Access-Control-Expose-Headers里面指定。 --> <init-param> <param-name>cors.exposedHeaders</param-name> <!--这里能够添加一些本身的暴露Headers --> <param-value>X-Test-1, X-Test-2</param-value> </init-param> <!-- 容许客户端给服务器发送cookie,若是不容许,删除该属性便可。(携带证书访问) --> <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> <!-- CORS Filter mapping --> <filter-mapping> <filter-name>CORSFilter</filter-name> <!-- 可自定义设置可供访问的项目路径 --> <url-pattern>/*</url-pattern> </filter-mapping> 

  注意:当web.xml文件中配置了多个filter时,须要将以上配置放在最前面,即:做为第一个filter存在。

4.效果展现

  ajax代码(html须要引入jQuery)

  servlet处理

  请求完成 

  没有添加cors配置前

  http的请求头 

  ajax的请求头

  通过对比发现,咱们会发现两点不一样: 

  http请求不存在跨域问题,ajax出现跨域会报错(跨域提醒且没法实现数据交互),输出在浏览器的控制台上;

  ajax请求会在Request Headers请求头会增长一个头部属性:Origin,值为当前网页地址,形如:http://localhost:8070,可是,当浏览器检测出该AJAX请求是跨域请求时,它的值会设置为null。

  Origin字段用来讲明:本次请求来自哪一个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否赞成此次请求。

  若是Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见),就知道出错了,从而抛出一个错误,被XMLHttpRequestonerror回调函数捕获。

  注意:这种错误没法经过状态码识别,由于HTTP回应的状态码有多是200(事实上,我们本次的状态码就是200)。

  添加cors配置后

  ajax能够实现跨域请求,即:Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

  Response Headers会多出如下字段:  

  Access-Control-Allow-Credentials:true,表示:容许客户端发送cookie,若是服务器没有配置该字段,则不会返回;(可返回项)
  Access-Control-Allow-Origin:null,表示:容许发送请求的客户端的域名,它的值要么是请求时 Origin字段的值,要么是一个 *,表示接受任意域名的请求。(必返回项)  
  Access-Control-Expose-Headers:X-Test-2, X-Test-1,表示:客户端能够获取的非简单响应标头或者自定义的响应头对应的值。(可返回项,若是不指定,则浏览器只能从headers中获取:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma的值)

  注意:若是是非简单请求,必返回的字段是Access-Control-Request-Method,。

  小结:

  第一,单纯的http请求不存在跨域问题,ajax请求存在跨域问题。(而也是使用java发送http请求至服务器不存在跨域问题的根本缘由)

  第二,跨域请求,请求头会自动加上Origin字段;当服务器添加cors配置后,响应头必返回Access-Control-Allow-Origin字段;

  若是是非简单请求可能会返回:Access-Control-Request-Method,表示:服务器端容许接受的http请求方式(当请求数据格式指定为application/json时,并无出现该字段,按理说该出来的)。

4.如何复现跨域问题?

  咱们如今知道:协议、域名、端口三者只要有一个不一样,就叫跨域。那么,要重现跨域场景,就须要两台服务器,即同一台电脑,跑两个tomcat服务器便可(两个tomcat的端口号必须不一样,不然端口冲突)。

  这里只介绍最简单的一种跨域请求方式:

  将ajax代码写到一个单独的html文件中,运行你的tomcat服务器, 使用浏览器打开该网页文件,就可重现ajax跨域问题啦。

  一旦服务器经过了"预检"请求,之后每次浏览器正常的CORS请求,就都跟简单请求同样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

  如上图所示,重现跨域问题后,按照第三步的cors方案,就能解决跨域问题啦。

5.浏览器如何向服务器跨域传送cookie?

  错误示例:

  本机运行了两个项目对应两个tomcat,端口号分别是8080和8070,

  如上图所示,Host表明的是服务端,Origin表明的是客户端,二者的域名不一样,其结果就是:

  虽然能够实现跨域,可是,没法实现cookie共享(服务端照样能够返回cookie且浏览器能接收到,可是,浏览器发送请求至服务器时却没法携带cookie)。

   因而可知,当域名彻底不一致时,cookie没法实现跨域(子域不一样的状况我没有进行测试)。

   这让我误觉得,cookie跨域共享是没法实现的,可是,事实并不是如此。

  正确示例:

  localhost:8080的服务器也能接收到实际的数据

  打印结果

  具体实现: 

window.onload = function(){
    // 前端往项目当中添加cookie
    document.cookie = "name=Marydon;path=/";
    $.ajax({
	    url:'http://localhost:8080/test/crossServlet',
	    method:'post',
            xhrFields: {
                withCredentials: true
            },// cookie可以传过去的关键所在
            /*crossDomain: true,*/
	    data:{name:'张三'},
	    success:function(data) {
		alert(data);	
	    }
	});   
}

  前端设置:关键就在于,ajax须要添加参数:xhrFields:{withCredentials:true};

  后台设置:(添加响应头部信息设置,response.setHeader())

  配置Access-Control-Allow-Credentials,值为true,对应cors的cors.supportsCredentials;

  配置Access-Control-Allow-Origin,值为不能为 '*',对应cors的cors.allowOrigin,但通过实践发现,其值为*时并无产生影响。

    说明:

  在前端添加cookie时,必须设置路径,否则,cookie只做用于当前页面;

  在默认状况下,只有设置 cookie的网页才能读取该 cookie。若是想让一个页面读取另外一个页面设置的cookie,必须设置cookie的路径。

  cookie的路径用于设置可以读取 cookie的顶级目录。将这个路径设置为网站的根目录(/),这样全部网页都能读取到该cookie,

  这也是js设置cookie后,后台取不到值的缘由。

  另外,在前端是获取不到JSESSIONID这个cookie的,由于它设置了httpOnly属性,即:只有后台可以获得该cookie。

  小结:
  cookie的跨域共享不是无条件的,即:请求和响应的IP彻底不相同时,没法实现cookie共享,这就至关于A网站不能访问B网站的cookie一个道理。

  当请求发起方和接收方的域名(IP)彻底一致,端口号不一样时,浏览器是能够携带cookie的,也就是:服务器能接收到前端所传来的cookie。

  当IP的顶级域名相同时,没有进行测试。

  通过上述实践发现:跨域cookie共享的局限性很大,还不如不用,有实际使用场景的大佬,欢迎留言。

6.http请求Headers详细说明

写在最后

  哪位大佬如若发现文章存在纰漏之处或须要补充更多内容,欢迎留言!!!

 相关推荐:

相关文章
相关标签/搜索