springboot系列文章之實現跨域請求(CORS)

pjmike_pj發表於2018-09-13

原文部落格:pjmike的部落格

CORS介紹

跨域資源共享向來都是熱門的需求,我們可以使用 CORS 來快速實現 跨域訪問,只需要在服務端進行授權即可,無需在前端新增額外的設定

簡單說,CORS是一種訪問機制,英文全稱: Cross-Origin Resource Sharing,即我們說的跨域資源共享。當一個資源從與該資源本身所在伺服器不同的域或埠請求一個資源時,資源會發起一個跨域HTTP請求。比如,在一個域名下的網頁中,呼叫另一個域名中的資源。

CORS的工作原理

CORS 實現跨域訪問並不是一蹴而就的,需要藉助瀏覽器的支援,從原理題圖我們可以看到,簡單的請求(通常指 GET/POST/HEAD 方式,並沒有去增加額外的請求頭資訊) 直接建立了跨域請求的 XMLHttpRequest物件,而非簡單請求(那種對伺服器有特殊要求的請求,比如請求方法是 PUTDELETE,或者 Content-Type欄位的型別是 application/json) 則要求先傳送一個 "預檢" 請求,待伺服器批准後才能真正發起跨域訪問請求。

cors

簡單請求

下面分析摘自 阮一峰的 跨域資源共享 CORS 詳解

對於簡單請求 (GET/POST/HEAD,瀏覽器直接發出 CORS請求,具體來說,就是在頭資訊之中,增加一個 Origin 欄位。如下圖所示:

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
複製程式碼

上面的 Origin 欄位用來說明,本次請求來自哪個源(協議+域名+埠)。伺服器根據這個值,決定是否同意這次請求。

如果 Origin 指定的源,不在許可範圍內,伺服器會返回一個正常的 HTTP響應。瀏覽器發現,這個回應的頭資訊沒有包含 Access-Control-Allow-Origin 欄位,就知道錯了,從而丟擲一個錯誤,被 XMLHttpRequestonerror回撥函式捕獲。

如果 Origin指定的域名在許可範圍內,伺服器返回的響應,會多出幾個頭資訊欄位

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
複製程式碼

下面總結下 簡單請求 與 CORS 有關的請求頭與響應頭

Request Headers

  • Origin :表示跨域請求的原始域

Response Headers

  • Access-Control-Allow-Origin : 表示允許哪些原始域進行跨域訪問,它的值要麼是請求時 Origin欄位的值,要麼是一個 *,表示接受任意域名的請求
  • Access-Control-Allow-Credentials: 表示是否允許客戶端傳送 Cookie,是一個布林值。預設情況下,Cookie不包括在 CORS 請求之中,設為 true,即表示伺服器明確許可,Cookie 可以包含在請求中,一起發給伺服器
  • Access-Control-Expose-Headers: CORS請求時,XMLHttpRequest物件的getResponseHeader()方法只能拿到6個基本欄位,自定義的header欄位是拿不到的,如果想拿到自定義的Header 欄位,就必須在 Access-Control-Expose-Headers裡面指定

非簡單請求

非簡單請求的 CORS 請求,會在正式通訊之前,增加一次 HTTP查詢請求,稱為 "預檢"請求。下面是一個預檢請求的HTTP頭資訊:

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
複製程式碼

除了 Origin欄位外,還包括兩個特殊欄位:

  • Access-Control-Request-Method: 用來列出瀏覽器的 CORS請求用到哪些HTTP方法
  • Access-Control-Request-Headers: 該欄位是一個逗號分隔的字串,指定瀏覽器CORS請求會額外傳送的頭資訊欄位,上例是X-Custom-Header

預檢請求的回應

伺服器收到預檢請求後,做出回應:

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
複製程式碼

下面總結下,預檢請求下的回應的與CORS相關的請求頭:

  • Access-Control-Allow-Methods: 逗號分隔的字串,表明伺服器支援的所有跨域請求的方法。注意是所有方法,不是單個瀏覽器請求時的那個方法,這是為了避免多次 "預檢"請求
  • Access-Control-Allow-Headers:如果瀏覽器請求包括 Access-Control-Request-Headers欄位,則 Access-Control-Allow-Headers是必須的,它表明伺服器支援的所有頭資訊欄位,不限於瀏覽器再預檢中請求的欄位
  • Access-Control-Max-Age: 該欄位可選,用來指定本次預檢請求的有效期,單位為秒。

實現 CORS 跨域請求的方式

對於 CORS的跨域請求,主要有以下幾種方式可供選擇:

  • 返回新的CorsFilter
  • 重寫 WebMvcConfigurer
  • 使用註解 @CrossOrigin
  • 手動設定響應頭 (HttpServletResponse)

注意:

  • CorFilter / WebMvConfigurer / @CrossOrigin 需要 SpringMVC 4.2以上版本才支援,對應於springBoot 1.3版本以上
  • 上面前兩種方式屬於全域性 CORS 配置,後兩種屬性區域性 CORS配置。如果使用了區域性跨域是會覆蓋全域性跨域的規則,所以可以通過 @CrossOrigin 註解來進行細粒度更高的跨域資源控制

1.返回新的 CorsFilter(全域性跨域)

在任意配置類,返回一個 新的 CorsFIlter Bean ,並新增對映路徑和具體的CORS配置路徑。

@Configuration
public class GlobalCorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        //1. 新增 CORS配置資訊
        CorsConfiguration config = new CorsConfiguration();
        //放行哪些原始域
        config.addAllowedOrigin("*");
        //是否傳送 Cookie
        config.setAllowCredentials(true);
        //放行哪些請求方式
        config.addAllowedMethod("*");
        //放行哪些原始請求頭部資訊
        config.addAllowedHeader("*");
        //暴露哪些頭部資訊
        config.addExposedHeader("*");
        //2. 新增對映路徑
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/**",config);
        //3. 返回新的CorsFilter
        return new CorsFilter(corsConfigurationSource);
    }
}

複製程式碼

2. 重寫 WebMvcConfigurer(全域性跨域)

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                //是否傳送Cookie
                .allowCredentials(true)
                //放行哪些原始域
                .allowedOrigins("*")
                .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
                .allowedHeaders("*")
                .exposedHeaders("*");
    }
}
複製程式碼

3. 使用註解 (區域性跨域)

在控制器上使用註解 @CrossOrigin:

@RestController
@CrossOrigin(origins = "*")
public class HelloController {
    @RequestMapping("/hello")
    public String hello() {
        return "hello world";
    }
}

複製程式碼

在方法上使用註解 @CrossOrigin:

@RequestMapping("/hello")
    @CrossOrigin(origins = "*")
    public String hello() {
        return "hello world";
    }
複製程式碼

4. 手動設定響應頭(區域性跨域)

使用 HttpServletResponse 物件新增響應頭(Access-Control-Allow-Origin)來授權原始域,這裡 Origin的值也可以設定為 "*",表示全部放行。

    @RequestMapping("/index")
    public String index(HttpServletResponse response) {
        response.addHeader("Access-Allow-Control-Origin","*");
        return "index";
    }
複製程式碼

參考資料 & 鳴謝

相關文章