javascript
webmvcconfigurer配置跨域_为什么加了 Spring Security 会导致 Spring Boot 跨域失效呢?...
點(diǎn)擊上方?IT牧場(chǎng)?,選擇?置頂或者星標(biāo)
技術(shù)干貨每日送達(dá)
作者:歐陽(yáng)我去
鏈接:https://segmentfault.com/a/1190000019485883
作為一個(gè)后端開(kāi)發(fā),我們經(jīng)常遇到的一個(gè)問(wèn)題就是需要配置?CORS,好讓我們的前端能夠訪問(wèn)到我們的 API,并且不讓其他人訪問(wèn)。而在 Spring 中,我們見(jiàn)過(guò)很多種?CORS?的配置,很多資料都只是告訴我們可以這樣配置、可以那樣配置,但是這些配置有什么區(qū)別?
1、CORS 是什么
首先我們要明確,CORS?是什么,以及規(guī)范是如何要求的。這里只是梳理一下流程。
CORS?全稱是?Cross-Origin Resource Sharing,直譯過(guò)來(lái)就是跨域資源共享。要理解這個(gè)概念就需要知道域、資源和同源策略這三個(gè)概念。
- 域,指的是一個(gè)站點(diǎn),由?protocal、host?和?port?三部分組成,其中?host?可以是域名,也可以是 ip ;port 如果沒(méi)有指明,則是使用?protocal?的默認(rèn)端口.
- 資源,是指一個(gè)?URL?對(duì)應(yīng)的內(nèi)容,可以是一張圖片、一種字體、一段 HTML 代碼、一份 JSON 數(shù)據(jù)等等任何形式的任何內(nèi)容。
- 同源策略,指的是為了防止?XSS,瀏覽器、客戶端應(yīng)該僅請(qǐng)求與當(dāng)前頁(yè)面來(lái)自同一個(gè)域的資源,請(qǐng)求其他域的資源需要通過(guò)驗(yàn)證。
了解了這三個(gè)概念,我們就能理解為什么有 CORS 規(guī)范了:從站點(diǎn) A 請(qǐng)求站點(diǎn) B 的資源的時(shí)候,由于瀏覽器的同源策略的影響,這樣的跨域請(qǐng)求將被禁止發(fā)送;為了讓跨域請(qǐng)求能夠正常發(fā)送,我們需要一套機(jī)制在不破壞同源策略的安全性的情況下、允許跨域請(qǐng)求正常發(fā)送,這樣的機(jī)制就是 CORS。
2、預(yù)檢請(qǐng)求
在?CORS?中,定義了一種預(yù)檢請(qǐng)求,即?preflight request,當(dāng)實(shí)際請(qǐng)求不是一個(gè)?簡(jiǎn)單請(qǐng)求?時(shí),會(huì)發(fā)起一次預(yù)檢請(qǐng)求。預(yù)檢請(qǐng)求是針對(duì)實(shí)際請(qǐng)求的 URL 發(fā)起一次 OPTIONS 請(qǐng)求,并帶上下面三個(gè) headers :
- Origin:值為當(dāng)前頁(yè)面所在的域,用于告訴服務(wù)器當(dāng)前請(qǐng)求的域。如果沒(méi)有這個(gè) header,服務(wù)器將不會(huì)進(jìn)行 CORS 驗(yàn)證。
- Access-Control-Request-Method:值為實(shí)際請(qǐng)求將會(huì)使用的方法。
- Access-Control-Request-Headers:值為實(shí)際請(qǐng)求將會(huì)使用的 header 集合。
如果服務(wù)器端?CORS?驗(yàn)證失敗,則會(huì)返回客戶端錯(cuò)誤,即 4xx 的狀態(tài)碼。
否則,將會(huì)請(qǐng)求成功,返回?200?的狀態(tài)碼,并帶上下面這些 headers:
- Access-Control-Allow-Origin:允許請(qǐng)求的域,多數(shù)情況下,就是預(yù)檢請(qǐng)求中的 Origin 的值。
- Access-Control-Allow-Credentials:一個(gè)布爾值,表示服務(wù)器是否允許使用 cookies。
- Access-Control-Expose-Headers:實(shí)際請(qǐng)求中可以出現(xiàn)在響應(yīng)中的 headers 集合。
- Access-Control-Max-Age:預(yù)檢請(qǐng)求返回的規(guī)則可以被緩存的最長(zhǎng)時(shí)間,超過(guò)這個(gè)時(shí)間,需要再次發(fā)起預(yù)檢請(qǐng)求。
- Access-Control-Allow-Methods:實(shí)際請(qǐng)求中可以使用到的方法集合瀏覽器會(huì)根據(jù)預(yù)檢請(qǐng)求的響應(yīng),來(lái)決定是否發(fā)起實(shí)際請(qǐng)求。
2.1 小結(jié)
到這里, 我們就知道了跨域請(qǐng)求會(huì)經(jīng)歷的故事:
接下來(lái),我們看看在 Spring 中,我們是如何讓 CORS 機(jī)制在我們的應(yīng)用中生效的。
3、三種配置的方式
Spring 提供了多種配置?CORS?的方式,有的方式針對(duì)單個(gè) API,有的方式可以針對(duì)整個(gè)應(yīng)用;有的方式在一些情況下是等效的,而在另一些情況下卻又出現(xiàn)不同。我們這里例舉幾種典型的方式來(lái)看看應(yīng)該如何配置。
假設(shè)我們有一個(gè) API:
@RestControllerclass?HelloController?{
????@GetMapping("hello")
????fun?hello():?String?{
????????return?"Hello,?CORS!"
????}
}
3.1 @CrossOrigin?注解
使用 @CorssOrigin?注解需要引入?Spring Web?的依賴,該注解可以作用于方法或者類,可以針對(duì)這個(gè)方法或類對(duì)應(yīng)的一個(gè)或多個(gè) API 配置 CORS 規(guī)則:
@RestControllerclass?HelloController?{
????@GetMapping("hello")
????@CrossOrigin(origins?=?["http://localhost:8080"])
????fun?hello():?String?{
????????return?"Hello,?CORS!"
????}
}
3.2 實(shí)現(xiàn)?WebMvcConfigurer.addCorsMappings?方法
WebMvcConfigurer?是一個(gè)接口,它同樣來(lái)自于 Spring Web。我們可以通過(guò)實(shí)現(xiàn)它的 addCorsMappings 方法來(lái)針對(duì)全局 API 配置 CORS 規(guī)則:
@Configuration@EnableWebMvc
class?MvcConfig:?WebMvcConfigurer?{
????override?fun?addCorsMappings(registry:?CorsRegistry)?{
????????registry.addMapping("/hello")
????????????????.allowedOrigins("http://localhost:8080")
????}
}
3.3 注入?CorsFilter
CorsFilter?同樣來(lái)自于?Spring Web,但是實(shí)現(xiàn)?WebMvcConfigurer.addCorsMappings?方法并不會(huì)使用到這個(gè)類,具體原因我們后面來(lái)分析。我們可以通過(guò)注入一個(gè) CorsFilter 來(lái)使用它:
@Configurationclass?CORSConfiguration?{
????@Bean
????fun?corsFilter():?CorsFilter?{
????????val?configuration?=?CorsConfiguration()
????????configuration.allowedOrigins?=?listOf("http://localhost:8080")
????????val?source?=?UrlBasedCorsConfigurationSource()
????????source.registerCorsConfiguration("/hello",?configuration)
????????return?CorsFilter(source)
????}
}
注入?CorsFilter?不止這一種方式,我們還可以通過(guò)注入一個(gè)?FilterRegistrationBean?來(lái)實(shí)現(xiàn),這里就不給例子了。
在僅僅引入?Spring Web?的情況下,實(shí)現(xiàn)?WebMvcConfigurer.addCorsMappings?方法和注入?CorsFilter?這兩種方式可以達(dá)到同樣的效果,二選一即可。它們的區(qū)別會(huì)在引入 Spring Security 之后會(huì)展現(xiàn)出來(lái),我們后面再來(lái)分析。
4、Spring Security 中的配置
在引入了?Spring Security?之后,我們會(huì)發(fā)現(xiàn)前面的方法都不能正確的配置?CORS,每次?preflight request?都會(huì)得到一個(gè)?401?的狀態(tài)碼,表示請(qǐng)求沒(méi)有被授權(quán)。這時(shí),我們需要增加一點(diǎn)配置才能讓 CORS 正常工作:
@Configurationclass?SecurityConfig?:?WebSecurityConfigurerAdapter()?{
????override?fun?configure(http:?HttpSecurity?)?{
????????http?.cors()
????}
}
或者,干脆不實(shí)現(xiàn)?WebMvcConfigurer.addCorsMappings?方法或者注入?CorsFilter?,而是注入一個(gè)?CorsConfigurationSource?,同樣能與上面的代碼配合,正確的配置 CORS:
@Beanfun?corsConfigurationSource():?CorsConfigurationSource?{
????val?configuration?=?CorsConfiguration()
????configuration.allowedOrigins?=?listOf("http://localhost:8080")
????val?source?=?UrlBasedCorsConfigurationSource()
????source.registerCorsConfiguration("/hello",?configuration)
????return?source
}
到此,我們已經(jīng)看過(guò)了幾種典型的例子了,我們接下來(lái)看看 Spring 到底是如何實(shí)現(xiàn) CORS 驗(yàn)證的。
5、這些配置有什么區(qū)別
我們會(huì)主要分析實(shí)現(xiàn)?WebMvcConfigurer.addCorsMappings?方法和調(diào)用?HttpSecurity.cors?方法這兩種方式是如何實(shí)現(xiàn)?CORS?的,但在進(jìn)行之前,我們要先復(fù)習(xí)一下 Filter 與 Interceptor 的概念。
5.1 Filter 與 Interceptor
上圖很形象的說(shuō)明了?Filter?與?Interceptor?的區(qū)別,一個(gè)作用在?DispatcherServlet?調(diào)用前,一個(gè)作用在調(diào)用后。
但實(shí)際上,它們本身并沒(méi)有任何關(guān)系,是完全獨(dú)立的概念。
Filter?由?Servlet?標(biāo)準(zhǔn)定義,要求?Filter?需要在?Servlet?被調(diào)用之前調(diào)用,作用顧名思義,就是用來(lái)過(guò)濾請(qǐng)求。在 Spring Web 應(yīng)用中,DispatcherServlet 就是唯一的 Servlet 實(shí)現(xiàn)。
Interceptor?由 Spring 自己定義,由?DispatcherServlet?調(diào)用,可以定義在 Handler 調(diào)用前后的行為。這里的 Handler ,在多數(shù)情況下,就是我們的 Controller 中對(duì)應(yīng)的方法。
對(duì)于?Filter?和?Interceptor?的復(fù)習(xí)就到這里,我們只需要知道它們會(huì)在什么時(shí)候被調(diào)用到,就能理解后面的內(nèi)容了。
5.2 WebMvcConfigurer.addCorsMappings?方法做了什么
我們從?WebMvcConfigurer.addCorsMappings?方法的參數(shù)開(kāi)始,先看看?CORS?配置是如何保存到 Spring 上下文中的,然后在了解一下 Spring 是如何使用的它們。
5.2.1 注入 CORS 配置
5.2.1.1 CorsRegistry 和 CorsRegistration
WebMvcConfigurer.addCorsMappings?方法的參數(shù)?CorsRegistry?用于注冊(cè)?CORS?配置,它的源碼如下:
public?class?CorsRegistry?{????private?final?List?registrations?=?new?ArrayList<>();public?CorsRegistration?addMapping(String?pathPattern)?{
????????CorsRegistration?registration?=?new?CorsRegistration(pathPattern);this.registrations.add(registration);return?registration;
????}protected?Map?getCorsConfigurations()?{
????????Map?configs?=?new?LinkedHashMap<>(this.registrations.size());for?(CorsRegistration?registration?:?this.registrations)?{
????????????configs.put(registration.getPathPattern(),?registration.getCorsConfiguration());
????????}return?configs;
????}
}
我們發(fā)現(xiàn)這個(gè)類僅僅有兩個(gè)方法:
- addMapping?接收一個(gè)?pathPattern,創(chuàng)建一個(gè)?CorsRegistration?實(shí)例,保存到列表后將其返回。在我們的代碼中,這里的 pathPattern 就是?/hello。
- getCorsConfigurations 方法將保存的 CORS 規(guī)則轉(zhuǎn)換成 Map 后返回。
CorsRegistration?這個(gè)類,同樣很簡(jiǎn)單,我們看看它的部分源碼:
public?class?CorsRegistration?{????private?final?String?pathPattern;
????private?final?CorsConfiguration?config;
????public?CorsRegistration(String?pathPattern)?{
????????this.pathPattern?=?pathPattern;
????????this.config?=?new?CorsConfiguration().applyPermitDefaultValues();
????}
????public?CorsRegistration?allowedOrigins(String...?origins)?{
????????this.config.setAllowedOrigins(Arrays.asList(origins));
????????return?this;
????}
}
不難發(fā)現(xiàn),這個(gè)類僅僅保存了一個(gè)?pathPattern?字符串和?CorsConfiguration,很好理解,它保存的是一個(gè) pathPattern 對(duì)應(yīng)的 CORS 規(guī)則。
在它的構(gòu)造函數(shù)中,調(diào)用的 CorsConfiguration.applyPermitDefaultValues 方法則用于配置默認(rèn)的 CORS 規(guī)則:
- allowedOrigins 默認(rèn)為所有域。
- allowedMethods 默認(rèn)為 GET 、HEAD 和 POST。
- allowedHeaders 默認(rèn)為所有。
- maxAge 默認(rèn)為 30 分鐘。
- exposedHeaders 默認(rèn)為 null,也就是不暴露任何 header。
- credentials 默認(rèn)為 null。
創(chuàng)建?CorsRegistration?后,我們可以通過(guò)它的?allowedOrigins、allowedMethods?等方法修改它的?CorsConfiguration,覆蓋掉上面的默認(rèn)值。
現(xiàn)在,我們已經(jīng)通過(guò)?WebMvcConfigurer.addCorsMappings?方法配置好?CorsRegistry?了,接下來(lái)看看這些配置會(huì)在什么地方被注入到 Spring 上下文中。
5.2.1.2 WebMvcConfigurationSupport
CorsRegistry.getCorsConfigurations?方法,會(huì)被?WebMvcConfigurationSupport.getConfigurations?方法調(diào)用,這個(gè)方法如下:
protected?final?Map?getCorsConfigurations()?{????if?(this.corsConfigurations?==?null)?{
????????CorsRegistry?registry?=?new?CorsRegistry();
????????addCorsMappings(registry);
????????this.corsConfigurations?=?registry.getCorsConfigurations();
????}
????return?this.corsConfigurations;
}
addCorsMappings(registry)?調(diào)用的是自己的方法,由子類?DelegatingWebMvcConfiguration?通過(guò)委托的方式調(diào)用到?WebMvcConfigurer.addCorsMappings?方法,我們的配置也由此被讀取到。
getCorsConfigurations?是一個(gè)?protected?方法,是為了在擴(kuò)展該類時(shí),仍然能夠直接獲取到 CORS 配置。而這個(gè)方法在這個(gè)類里被四個(gè)地方調(diào)用到,這四個(gè)調(diào)用的地方,都是為了注冊(cè)一個(gè) HandlerMapping 到 Spring 容器中。每一個(gè)地方都會(huì)調(diào)用 mapping.setCorsConfigurations 方法來(lái)接收 CORS 配置,而這個(gè)?setCorsConfigurations?方法,則由?AbstractHandlerMapping?提供,CorsConfigurations 也被保存在這個(gè)抽象類中。
到此,我們的?CORS?配置借由?AbstractHandlerMapping?被注入到了多個(gè)?HandlerMapping?中,而這些?HandlerMapping?以 Spring 組件的形式被注冊(cè)到了 Spring 容器中,當(dāng)請(qǐng)求來(lái)臨時(shí),將會(huì)被調(diào)用。
5.2.2 獲取 CORS 配置
還記得前面關(guān)于 Filter 和 Interceptor 那張圖嗎?當(dāng)請(qǐng)求來(lái)到 Spring Web 時(shí),一定會(huì)到達(dá) DispatcherServlet 這個(gè)唯一的 Servlet。
在?DispatcherServlet.doDispatch?方法中,會(huì)調(diào)用所有 HandlerMapping.getHandler 方法。好巧不巧,這個(gè)方法又是由 AbstractHandlerMapping 實(shí)現(xiàn)的:
@Override@Nullable
public?final?HandlerExecutionChain?getHandler(HttpServletRequest?request)?throws?Exception?{
????//?省略代碼
????if?(CorsUtils.isCorsRequest(request))?{
????????CorsConfiguration?globalConfig?=?this.corsConfigurationSource.getCorsConfiguration(request);
????????CorsConfiguration?handlerConfig?=?getCorsConfiguration(handler,?request);
????????CorsConfiguration?config?=?(globalConfig?!=?null???globalConfig.combine(handlerConfig)?:?handlerConfig);
????????executionChain?=?getCorsHandlerExecutionChain(request,?executionChain,?config);
????}
????return?executionChain;
}
在這個(gè)方法中,關(guān)于 CORS 的部分都在這個(gè) if 中。我們來(lái)看看最后這個(gè) getCorsHandlerExecutionChain 做了什么:
protected?HandlerExecutionChain?getCorsHandlerExecutionChain(HttpServletRequest?request,????????HandlerExecutionChain?chain,?@Nullable?CorsConfiguration?config)?{
????if?(CorsUtils.isPreFlightRequest(request))?{
????????HandlerInterceptor[]?interceptors?=?chain.getInterceptors();
????????chain?=?new?HandlerExecutionChain(new?PreFlightHandler(config),?interceptors);
????}
????else?{
????????chain.addInterceptor(new?CorsInterceptor(config));
????}
????return?chain;
}
可以看到:
- 針對(duì)?preflight request,由于不會(huì)有對(duì)應(yīng)的?Handler?來(lái)處理,所以這里就創(chuàng)建了一個(gè) PreFlightHandler 來(lái)作為這次請(qǐng)求的 handler。
- 對(duì)于其他的跨域請(qǐng)求,因?yàn)闀?huì)有對(duì)應(yīng)的?handler,所以就在?handlerExecutionChain?中加入一個(gè)?CorsInterceptor?來(lái)進(jìn)行?CORS?驗(yàn)證
這里的?PreFlightHandler?和?CorsInterceptor?都是?AbstractHandlerMapping?的內(nèi)部類,實(shí)現(xiàn)幾乎一致,區(qū)別僅僅在于一個(gè)是?HttpRequestHandler,一個(gè)是 HandlerInterceptor;它們對(duì) CORS 規(guī)則的驗(yàn)證都交由 CorsProcessor 接口完成,這里采用了默認(rèn)實(shí)現(xiàn) DefaultCorsProcessor。
DefaultCorsProcessor?則是依照?CORS?標(biāo)準(zhǔn)來(lái)實(shí)現(xiàn),并在驗(yàn)證失敗的時(shí)候打印 debug 日志并拒絕請(qǐng)求。我們只需要關(guān)注一下標(biāo)準(zhǔn)中沒(méi)有定義的驗(yàn)證失敗時(shí)的狀態(tài)碼:
protected?void?rejectRequest(ServerHttpResponse?response)?throws?IOException?{????response.setStatusCode(HttpStatus.FORBIDDEN);
????response.getBody().write("Invalid?CORS?request".getBytes(StandardCharsets.UTF_8));
}
CORS?驗(yàn)證失敗時(shí)調(diào)用這個(gè)方法,并設(shè)置狀態(tài)碼為 403。
5.2.3 小結(jié)
通過(guò)對(duì)源碼的研究,我們發(fā)現(xiàn)實(shí)現(xiàn)?WebMvcConfigurer.addCorsMappings?方法的方式配置?CORS,會(huì)在 Interceptor 或者 Handler 層進(jìn)行 CORS 驗(yàn)證。
5.3 HtttpSecurity.cors?方法做了什么
在研究這個(gè)方法的行為之前,我們先來(lái)回想一下,我們調(diào)用這個(gè)方法解決的是什么問(wèn)題。
前面我們通過(guò)某種方式配置好?CORS?后,引入?Spring Security,CORS?就失效了,直到調(diào)用這個(gè)方法后,CORS 規(guī)則才重新生效。
下面這些原因,導(dǎo)致了?preflight request?無(wú)法通過(guò)身份驗(yàn)證,從而導(dǎo)致 CORS 失效:
接下來(lái)我們就來(lái)看看 HttpSecurity.cors 方法是如何解決這個(gè)問(wèn)題的。
5.3.1 CorsConfigurer 如何配置 CORS 規(guī)則
HttpSecurity.cors 方法中其實(shí)只有一行代碼:
public?CorsConfigurer?cors()?throws?Exception?{????return?getOrApply(new?CorsConfigurer<>());
}
這里調(diào)用的?getOrApply?方法會(huì)將?SecurityConfigurerAdapter?的子類實(shí)例加入到它的父類?AbstractConfiguredSecurityBuilder?維護(hù)的一個(gè)?Map?中,然后一個(gè)個(gè)的調(diào)用 configure 方法。所以,我們來(lái)關(guān)注一下 CorsConfigurer.configure 方法就好了。
@Overridepublic?void?configure(H?http)?throws?Exception?{
????ApplicationContext?context?=?http.getSharedObject(ApplicationContext.class);
????CorsFilter?corsFilter?=?getCorsFilter(context);
????if?(corsFilter?==?null)?{
????????throw?new?IllegalStateException(
????????????????"Please?configure?either?a?"?+?CORS_FILTER_BEAN_NAME?+?"?bean?or?a?"
????????????????????????+?CORS_CONFIGURATION_SOURCE_BEAN_NAME?+?"bean.");
????}
????http.addFilter(corsFilter);
}
這段代碼很好理解,就是在當(dāng)前的 Spring Context 中找到一個(gè)?CorsFilter,然后將它加入到 http 對(duì)象的 filters 中。由上面的 HttpSecurity.cors 方法可知,這里的 http 對(duì)象實(shí)際類型就是 HttpSecurity。
5.3.2 getCorsFilter 方法做了什么
也許你會(huì)好奇,HttpSecurity 要如何保證 CorsFilter 一定在 Spring Security 的 Filters 之前調(diào)用。但是在研究這個(gè)之前,我們先來(lái)看看同樣重要的?getCorsFilter?方法,這里可以解答我們前面的一些疑問(wèn)。
private?CorsFilter?getCorsFilter(ApplicationContext?context)?{????if?(this.configurationSource?!=?null)?{
????????return?new?CorsFilter(this.configurationSource);
????}
????boolean?containsCorsFilter?=?context
????????????.containsBeanDefinition(CORS_FILTER_BEAN_NAME);
????if?(containsCorsFilter)?{
????????return?context.getBean(CORS_FILTER_BEAN_NAME,?CorsFilter.class);
????}
????boolean?containsCorsSource?=?context
????????????.containsBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME);
????if?(containsCorsSource)?{
????????CorsConfigurationSource?configurationSource?=?context.getBean(
????????????????CORS_CONFIGURATION_SOURCE_BEAN_NAME,?CorsConfigurationSource.class);
????????return?new?CorsFilter(configurationSource);
????}
????boolean?mvcPresent?=?ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR,
????????????context.getClassLoader());
????if?(mvcPresent)?{
????????return?MvcCorsFilter.getMvcCorsFilter(context);
????}
????return?null;
}
這是?CorsConfigurer?尋找?CorsFilter?的全部邏輯,我們用人話來(lái)說(shuō)就是:
上面的第 2、3、4 步能解答我們前面的配置為什么生效,以及它們的區(qū)別。
注冊(cè)?CorsFilter?的方式,這個(gè) Filter 最終會(huì)被直接注冊(cè)到 Servlet container 中被使用到。
注冊(cè)?CorsConfigurationSource?的方式,會(huì)用這個(gè) source 創(chuàng)建一個(gè) CorsFiltet 然后注冊(cè)到 Servlet container 中被使用到。
而第四步的情況比較復(fù)雜。HandlerMappingIntrospector 是 Spring Web 提供的一個(gè)類,實(shí)現(xiàn)了?CorsConfigurationSource?接口,所以在?MvcCorsFilter?中,它被直接用于創(chuàng)建 CorsFilter。它實(shí)現(xiàn)的 getCorsConfiguration 方法,會(huì)經(jīng)歷:
所以得到的?CorsConfigurationSource?實(shí)例,實(shí)際上就是前面講到的 CorsInterceptor 或者 PreFlightHandler。
所以第四步實(shí)際上匹配的是實(shí)現(xiàn) WebMvcConfigurer.addCorsMappings 方法的方式。
由于在?CorsFilter?中每次處理請(qǐng)求時(shí)都會(huì)調(diào)用?CorsConfigurationSource.getCorsConfiguration?方法,而?DispatcherServlet?中也會(huì)每次調(diào)用?HandlerMapping.getHandler?方法,再加上這時(shí)的?HandlerExecutionChain?中還有?CorsInterceptor,所以使用這個(gè)方式相對(duì)于其他方式,做了很多重復(fù)的工作。所以 WebMvcConfigurer.addCorsMappings + HttpSecurity.cors 的方式降低了我們代碼的效率,也許微乎其微,但能避免的情況下,還是不要使用。
5.3.3 HttpSecurity 中的 filters 屬性
在?CorsConfigurer.configure?方法中調(diào)用的?HttpSecurity.addFilter?方法,由它的父類?HttpSecurityBuilder?聲明,并約定了很多 Filter 的順序。然而 CorsFilter 并不在其中。不過(guò)在 Spring Security 中,目前還只有?HttpSecurity?這一個(gè)實(shí)現(xiàn),所以我們來(lái)看看這里的代碼實(shí)現(xiàn)就知道 CorsFilter 會(huì)排在什么地方了。
public?HttpSecurity?addFilter(Filter?filter)?{????Class?extends?Filter>?filterClass?=?filter.getClass();
????if?(!comparator.isRegistered(filterClass))?{
????????throw?new?IllegalArgumentException("...");
????}
????this.filters.add(filter);
????return?this;
}
我們可以看到,Filter?會(huì)被直接加到?List?中,而不是按照一定的順序來(lái)加入的。但同時(shí),我們也發(fā)現(xiàn)了一個(gè)?comparator?對(duì)象,并且只有被注冊(cè)到了該類的 Filter 才能被加入到 filters 屬性中。這個(gè) comparator 又是用來(lái)做什么的呢?
在 Spring Security 創(chuàng)建過(guò)程中,會(huì)調(diào)用到?HttpSeciryt.performBuild?方法,在這里我們可以看到 filters 和 comparator 是如何被使用到的。
protected?DefaultSecurityFilterChain?performBuild()?throws?Exception?{????Collections.sort(filters,?comparator);
????return?new?DefaultSecurityFilterChain(requestMatcher,?filters);
}
可以看到,Spring Security 使用了這個(gè)?comparator?在獲取?SecurityFilterChain?的時(shí)候來(lái)保證?filters?的順序,所以,研究這個(gè) comparator 就能知道在 SecurityFilterChain 中的那些 Filter 的順序是如何的了。
這個(gè)?comparator?的類型是?FilterComparator?,從名字就能看出來(lái)是專用于?Filter?比較的類,它的實(shí)現(xiàn)也并不神秘,從構(gòu)造函數(shù)就能猜到是如何實(shí)現(xiàn)的:
FilterComparator()?{????Step?order?=?new?Step(INITIAL_ORDER,?ORDER_STEP);
????put(ChannelProcessingFilter.class,?order.next());
????put(ConcurrentSessionFilter.class,?order.next());
????put(WebAsyncManagerIntegrationFilter.class,?order.next());
????put(SecurityContextPersistenceFilter.class,?order.next());
????put(HeaderWriterFilter.class,?order.next());
????put(CorsFilter.class,?order.next());
??//?省略代碼
}
可以看到?CorsFilter?排在了第六位,在所有的 Security Filter 之前,由此便解決了 preflight request 沒(méi)有攜帶認(rèn)證信息的問(wèn)題。
5.3.4 小結(jié)
引入?Spring Security?之后,我們的?CORS?驗(yàn)證實(shí)際上是依然運(yùn)行著的,只是因?yàn)?preflight request?不會(huì)攜帶認(rèn)證信息,所以無(wú)法通過(guò)身份驗(yàn)證。使用 HttpSecurity.cors 方法會(huì)幫助我們?cè)诋?dāng)前的 Spring Context 中找到或創(chuàng)建一個(gè) CorsFilter 并安排在身份驗(yàn)證的 Filter 之前,以保證能對(duì) preflight request 正確處理。
6、總結(jié)
研究了 Spring 中 CORS 的代碼,我們了解到了這樣一些知識(shí):
- 實(shí)現(xiàn)?WebMvcConfigurer.addCorsMappings?方法來(lái)進(jìn)行的?CORS?配置,最后會(huì)在 Spring 的 Interceptor 或 Handler 中生效。
- 注入 CorsFilter 的方式會(huì)讓 CORS 驗(yàn)證在 Filter 中生效。
- 引入?Spring Security?后,需要調(diào)用 HttpSecurity.cors 方法以保證 CorsFilter 會(huì)在身份驗(yàn)證相關(guān)的 Filter 之前執(zhí)行。
- HttpSecurity.cors?+?WebMvcConfigurer.addCorsMappings?是一種相對(duì)低效的方式,會(huì)導(dǎo)致跨域請(qǐng)求分別在 Filter 和 Interceptor 層各經(jīng)歷一次 CORS 驗(yàn)證。
- HttpSecurity.cors + 注冊(cè) CorsFilter 與 HttpSecurity.cors + 注冊(cè) CorsConfigurationSource 在運(yùn)行的時(shí)候是等效的。
- 在 Spring 中,沒(méi)有通過(guò) CORS 驗(yàn)證的請(qǐng)求會(huì)得到狀態(tài)碼為 403 的響應(yīng)。
干貨分享
最近將個(gè)人學(xué)習(xí)筆記整理成冊(cè),使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤地址,無(wú)套路領(lǐng)取!
?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;?003:《Java面試寶典》?004:《Docker開(kāi)源書》?005:《Kubernetes開(kāi)源書》?006:《DDD速成(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)速成)》?007:全部?008:加技術(shù)群討論
近期熱文
?LinkedBlockingQueue vs ConcurrentLinkedQueue?解讀Java 8 中為并發(fā)而生的 ConcurrentHashMap?Redis性能監(jiān)控指標(biāo)匯總?最全的DevOps工具集合,再也不怕選型了!?微服務(wù)架構(gòu)下,解決數(shù)據(jù)庫(kù)跨庫(kù)查詢的一些思路?聊聊大廠面試官必問(wèn)的 MySQL 鎖機(jī)制
關(guān)注我
喜歡就點(diǎn)個(gè)"在看"唄^_^
總結(jié)
以上是生活随笔為你收集整理的webmvcconfigurer配置跨域_为什么加了 Spring Security 会导致 Spring Boot 跨域失效呢?...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux 快捷键
- 下一篇: js JSON转Excel并导出