記一次跨域問題的解決方案

moonlightL發表於2017-09-26

原文地址:記一次跨域問題的解決方案
部落格地址:www.extlight.com

一、背景

在公司空暇時間,筆者使用 AngularJS + SSM 方案編寫一套許可權控制系統。由於採用的是前後端分離的思想,前端頁面服務啟動的埠和後端服務啟動埠不一致導致請求跨域問題的出現。在此,寫下解決問題的流程。

二、基礎知識

2.1 什麼是同源

URL 由協議、域名、埠和路徑組成,如果兩個 URL 的協議、域名和埠相同,則表示它們同源。

2.2 什麼是同源策略

瀏覽器的同源策略,限制了來自不同源的 document 或指令碼,對當前 document 讀取或設定某些屬性。其目的是為了保證使用者資訊的安全,防止惡意的網站竊取資料。

另外,同源策略只對網頁的 HTML 文件做了限制,對載入的其他靜態資源如 javascript、css、圖片等仍然認為屬於同源。

2.3 什麼是跨域

跨域,指的是瀏覽器不能執行其他網站的指令碼。同源策略規定,AJAX 請求只能發給同源的網址,否則就報錯。

建議讀者先瀏覽文章末尾提供的參考資料進一步瞭解跨域相關的內容,再結合本文案例思考和理解

三、解決案例

CORS 是一個 W3C 標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源伺服器,發出 XMLHttpRequest 請求,從而克服了 AJAX 只能同源使用的限制。

筆者採用 CORS 方案解決問題。

3.1 前端頁面設定

3.1.1 設定請求引數

在 ajax 請求引數中新增 2 個引數設定:

xhrFields: { withCredentials: true }
crossDomain: true複製程式碼

3.1.2 原始碼演示

$.ajaxSetup({
    dataType: "json",
    cache: false,
    xhrFields: { withCredentials: true },//設定後,請求會攜帶cookie
    crossDomain: true,
    complete: function(xhr) {
        if (xhr.responseJSON) {
            if (xhr.responseJSON.code == 401) {
                layer.msg(xhr.responseJSON.msg);
                setTimeout(function() {
                    window.location.href = "login.html";
                }, 1500);
            }
        } else {
            layer.msg(xhr.responseText);
        }
    }
});複製程式碼

3.2 後端伺服器設定

3.2.1 設定跨域請求過濾器

public class SimpleCORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) resp;
        HttpServletRequest request=(HttpServletRequest)req;
        // 處理簡單請求
        // 跨域請求預設不攜帶cookie,如果要攜帶cookie,需要設定下邊2個響應頭
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));// 必選,所有有效的跨域響應都必須包含這個請求頭, 沒有的話會導致跨域請求失敗
        response.setHeader("Access-Control-Allow-Credentials", "true");//可選,此處設定為true,對應前端 xhr.withCredentials = true;
        //處理非簡單請求
        //  非簡單請求:瀏覽器會傳送兩個請求, 第一個請求(成為預檢請求)會像伺服器確定是否接受這個跨域請求, 第二個才是真正的發出請求. 瀏覽器自動的處理這兩個請求, 同時預檢請求也是可以被快取的, 而不用每次請求都需要傳送預檢請求.
        //  預檢請求是在實際的請求發出前先向伺服器確認是否能夠處理這個請求. 伺服器應該檢查上邊兩個請求頭的值, 來判斷這個請求是否有效.
        response.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE");// 必選
        response.setHeader("Access-Control-Allow-Headers",
                "Origin,No-Cache,X-Requested-With,If-Modified-Since," +
                        "Pragma,Last-Modified,Cache-Control,Expires,Content-Type,X-E4M-With,userId,token");

        response.setHeader("Access-Control-Max-Age", "0");// 可選,在每個請求前面都傳送一個預檢請求是很浪費資源的, 這個值允許你設定預檢請求的快取時間, 單位是秒.
        response.setHeader("XDomainRequestAllowed","1");
        chain.doFilter(req,resp);
    }

    public void init(FilterConfig filterConfig) {}

    public void destroy() {}
}複製程式碼

3.2.2 配置 web.xml 檔案

<filter>
    <filter-name>cors</filter-name>
    <filter-class>com.light.system.web.filter.SimpleCORSFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>cors</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>複製程式碼

四、參考資料

相關文章