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詳細說明

寫在最後

  哪位大佬如若發現文章存在紕漏之處或須要補充更多內容,歡迎留言!!!

 相關推薦:

相關文章
相關標籤/搜索