java、ajax 跨域請求解決方案('Access-Control-Allow-Origin' header is present on the requested resource. Origin '請求源' is therefore not allowed access.)


1.情景展示

  ajax調取java服務器請求報錯

  報錯信息如下:

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

  但是,請求狀態卻是成功:200,這是怎么回事? 

2.原因分析

  ajax請求跨域:ajax出現請求跨域錯誤問題是因為瀏覽器的“同源策略”。

  同源策略:1995年,同源政策由 Netscape 公司引入瀏覽器。目前,所有瀏覽器都實行這個政策。

  所謂"同源"指的是"三個相同":協議相同&域名相同&端口相同,這三個要求必須一致,否則就叫跨域。

  同源政策的目的,是為了保證用戶信息的安全,防止惡意的網站竊取數據(比如:同源策略可以限制不同網站cookie共享的問題,通過cors設置照樣可以實現cookie共享)。

  同源政策規定,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詳細說明

寫在最后

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

 相關推薦:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM