前後端分離專案,如何解決跨域問題?

語言: CN / TW / HK

跨域問題是前後端分離專案中非常常見的一個問題,舉例來說,程式設計學習網站的前端服務跑在 8080 埠下,後端服務跑在 9002 埠下,那麼前端在請求後端介面的時候就會出現跨域問題。

403 Forbidden 是HTTP協議中的一個狀態碼(Status Code),意味著後端服務雖然成功解析了請求,但前端卻沒有訪問該資源的許可權。

那怎麼解決這個問題呢?通常有兩個思路:

  • 前端使用 Nodejs 代理(開發環境下,生產環境下可以用 Nginx 替代)
  • 或者後端開啟跨域資源共享

一、關於跨域

跨域對於前後端開發者來說,就像一塊狗皮膏藥,無論是面試還是開發中,都會經常遇到。

之所以出現跨域問題,是因為瀏覽器的同源策略,為了隔離潛在的惡意檔案,為了防禦來自歪門邪道的攻擊,瀏覽器限制了從同一個源載入的文件或指令碼與來自另一個源的資源進行互動。

前面我們提到了,前端跑在 8080 埠下,後端跑在 9002 埠下,這種情況就屬於不同的源(域名不同,協議不同,埠不同),所以 8080 埠下的前端請求直接訪問 9002 埠下的後端介面時就訪問失敗了。

那正確的開啟方式是什麼呢?我們前面也提到了,前端使用 Nodejs 代理或者後端開啟跨域資源共享,我們一一來實踐下。

二、Nodejs 代理

在 Nodejs 出現之前,JavaScript 編寫的程式通常需要在使用者的瀏覽器上執行,Node.js 出現後,JavaScript 也能用於服務端程式設計了。Nodejs 一系列的內建模組使得程式可以脫離 IIS、Apache 這種 Web 服務作為獨立的伺服器執行。

我們使用 Nodejs 來解決跨域問題的思路就是,在本地建立一個虛擬伺服器,對 8080 埠下的前端請求進行代理,同時接收 9002 埠下的伺服器端響應,這樣服務端和服務端進行資料的互動就不會出現跨域問題了。

第一步,配置 Nodejs 代理服務

module.exports = {
  dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {
      '/api': {
        target: 'http://localhost:9002', // 你請求的第三方介面
        changeOrigin: false, // 在本地會建立一個虛擬服務端,然後傳送請求的資料,並同時接收請求的資料,這樣服務端和服務端進行資料的互動就不會有跨域問題
        pathRewrite: { // 路徑重寫,
          '^/api': '' // 替換target中的請求地址,也就是說以後你在請求http://api.codingmore.top/v2/XXXXX這個地址的時候直接寫成/api即可。
        }
      },
    },
}
複製程式碼

第二步,配置前端訪問請求路徑

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  VUE_APP_BASE_API: '"/api"'
  // VUE_APP_BASE_API: '"http://localhost:9002"'
})
複製程式碼

第三步,重啟前端服務

再次點選「登入」按鈕,可以看到請求的 URL 發生了改變,原來是 http://localhost:9002/users/login,現在是 http://localhost:8080/api/users/login。與此同時,可以看到多了一個 Remote Address,埠也是 8080,也就是說經過 Nodejs 的代理,前後端的互動在同一個源下面了,這樣就不會發生跨域問題了。

同時,可以看得到,伺服器端返回的狀態碼變成了 200,表示請求成功。

三、開啟跨域資源共享

跨域資源共享,也就是 Cross-Origin Resource Sharing,簡拼為 CORS,是一種基於 HTTP 頭資訊的機制,通過允許伺服器標識除了它自己以外的資源,從而實現跨域訪問。

第一步,開啟 CORS 支援

在 Spring Boot 應用中,加入 CORS 的支援簡單到不忍直視,新增一個配置類就可以了。

@Configuration
public class GlobalCorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        // 設定你要允許的網站域名
        config.addAllowedOrigin("http://localhost:8080");
        //允許跨域傳送cookie
        config.setAllowCredentials(true);
        //放行全部原始頭資訊
        config.addAllowedHeader("*");
        //允許所有請求方法跨域呼叫
        config.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}
複製程式碼

第二步,重啟後端服務,再次點選登入按鈕,發現請求已經可以正常訪問了。

本例中,後端返回 Access-Control-Allow-Origin: http://localhost:8080 就表示,跑在 9002 埠下的後端介面可以被 8080 埠的前端請求訪問。

如果允許所有域名進行跨域呼叫的話,只需改變一行程式碼即可。

//允許所有域名進行跨域呼叫
config.addAllowedOriginPattern("*");
// 設定你要允許的網站域名
//        config.addAllowedOrigin("http://localhost:8080");
複製程式碼

對於 login 這種簡單的請求來說,它們是不會觸發 CORS 預檢的,因此不需要在伺服器端增加其他配置就可以了。那什麼是簡單請求呢?

1)請求方法是以下三種方法之一:

  • HEAD
  • GET
  • POST

2)HTTP 的頭資訊不超出以下幾種欄位:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限於三個值 application/x-www-form-urlencoded、multipart/form-data、text/plain

那對於會觸發 CORS 預檢的非簡單請求(比如說請求方法是 PUT 或 DELETE,或者 Content-Type 欄位的型別是 application/json,或者請求訊息頭包含了一些自定義的欄位),該怎麼辦呢?

非簡單請求在正式通訊之前,會增加一次 HTTP 查詢請求,稱為“預檢”請求。預檢請求通過後,才會返回正常的響應內容。

拿程式設計貓的文章管理頁來舉例,該頁面會向後端發起一個 posts/queryPageable 的分頁查詢,該請求包含了一個自定義的訊息頭 Authorization,於是瀏覽器認為該請求是一個非簡單請求,然後就會自動發起一次 OPTIONS 請求,但由於我們的 Spring Boot 專案整合了 SpringsScurity 安全管理框架,沒有對OPTIONS請求放開登入認證,導致驗證失敗,文章分頁請求的響應資料就沒有返回回來。

第三步,通過以下程式碼給 OPTIONS 請求放行。

public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
                .authorizeRequests();
        //允許跨域請求的OPTIONS請求
        registry.antMatchers(HttpMethod.OPTIONS)
                .permitAll();
    }
}
複製程式碼

再次重啟後端服務,重新訪問文章列表介面,發現有響應資料了。

非簡單請求必須首先使用 OPTIONS 請求方法發起一個預檢請求到伺服器端,以獲知伺服器是否允許該實際請求。"預檢請求“的使用,避免了跨域請求對伺服器的使用者資料造成未預期的影響。

我們來通過兩張圖片簡單總結一下預檢請求的整個過程,第一張,發起 OPTIONS 預檢請求:

第二章,發起正式請求:

最後

如果你覺得此文對你有一丁點幫助,點個贊。或者可以加入我的開發交流群:1025263163相互學習,我們會有專業的技術答疑解惑

如果你覺得這篇文章對你有點用的話,麻煩請給我們的開源專案點點star:http://github.crmeb.net/u/defu不勝感激 !

PHP學習手冊:http://doc.crmeb.com
技術交流論壇:http://q.crmeb.com