先說解決方案吧,我們代碼里的解決方案:
.antMatchers("/examRoom/find").permitAll() .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() // 關鍵這行,就是允許Prefight預檢請求
.antMatchers(HttpMethod.POST, "/login").permitAll()
Prefight預檢請求就是一個 options 請求,我們可以點進去看源碼:
public static boolean isPreFlightRequest(HttpServletRequest request) { return HttpMethod.OPTIONS.matches(request.getMethod()) && request.getHeader("Access-Control-Request-Method") != null; }
requestMatchers(CorsUtils::isPreFlightRequest).permitAll()的作用是將PreflightRequest不做攔截。
一、為什么需要preflight request
我們都知道瀏覽器的同源策略,就是出於安全考慮,瀏覽器會限制從腳本發起的跨域HTTP請求,像XMLHttpRequest和Fetch都遵循同源策略。
瀏覽器限制跨域請求一般有兩種方式:(1)瀏覽器限制發起跨域請求;(2)跨域請求可以正常發起,但是返回的結果被瀏覽器攔截了。
一般瀏覽器都是第二種方式限制跨域請求,那就是說請求已到達服務器,並有可能對數據庫里的數據進行了操作,但是返回的結果被瀏覽器攔截了,那么我們就獲取不到返回結果,這是一次失敗的請求,但是可能對數據庫里的數據產生了影響。
為了防止這種情況的發生,規范要求,對這種可能對服務器數據產生副作用的HTTP請求方法,瀏覽器必須先使用OPTIONS方法發起一個預檢請求,從而獲知服務器是否允許該跨域請求:如果允許,就發送帶數據的真實請求;如果不允許,則阻止發送帶數據的真實請求。
瀏覽器將CORS請求分成兩類:簡單請求和非簡單請求。
瀏覽器對這兩種請求的處理是不一樣的,非簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。
二、簡單請求與非簡單請求
詳見之前總結的這篇博客:淺析http簡單請求與復雜請求
CORS請求,瀏覽器與服務器交互的過程,這上面的坑主要就是Preflight。如果我們的后台用了安全管理框架(比如Spring Security),並且沒有對Preflight這個請求做出相應的處理,那么這個請求會導致權限管控失敗(比如無法登錄)。
因為Preflight不攜帶Cookie,即不攜帶JSESSIONID,因此Spring Security攔截器會認為你沒有登錄。
由於 Prefight 預檢請求,實際就是一個 options 請求,所以我們也可以允許所有 options 請求,這樣解決這個問題。
@Override protected void configure(HttpSecurity http) throws Exception { http.cors().and() //跨域請求會先進行一次options請求,允許 options 請求
.antMatchers(HttpMethod.OPTIONS).permitAll() ... }
三、簡單請求與非簡單請求的請求流程
詳細內容看這篇文檔,官方描述:Cross-Origin Resource Sharing (CORS):https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
1、簡單請求
這在客戶端和服務器之間執行簡單的交換,使用 CORS 標頭來處理權限:
2、預檢請求
與“簡單請求”(上面討論過)不同,對於“預檢”請求,瀏覽器首先使用該OPTIONS
方法向另一個源上的資源發送 HTTP 請求,以確定實際請求是否可以安全發送。跨站點請求是這樣預檢的,因為它們可能會對用戶數據產生影響。