解讀SpringBoot中addCorsMappings配置跨域與攔截器互斥問題的原因
SpringBoot中addCorsMappings配置跨域與攔截器互斥
如題,前兩天在做前后端分離項(xiàng)目時(shí),碰到了這個(gè)問題,登錄token驗(yàn)證的攔截器使項(xiàng)目中配置的跨域配置失效,導(dǎo)致瀏覽器拋出跨域請求錯(cuò)誤,跨域配置如下:
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(origins)
.allowedHeaders("*")
.allowCredentials(true)
.allowedMethods("*")
.maxAge(3600);
}
};
}
通過在網(wǎng)上的查詢,發(fā)現(xiàn)了如下解釋
- 但是使用此方法配置之后再使用自定義攔截器時(shí)跨域相關(guān)配置就會失效。
- 原因是請求經(jīng)過的先后順序問題,當(dāng)請求到來時(shí)會先進(jìn)入攔截器中,而不是進(jìn)入Mapping映射中,所以返回的頭信息中并沒有配置的跨域信息。瀏覽器就會報(bào)跨域異常。
然后參考了網(wǎng)上給出的方法,重新引入了跨域過濾器配置,解決了這個(gè)問題。
那最終這個(gè)問題產(chǎn)生的原因是什么的,真的如上訴所說嗎,我通過調(diào)試與研究源碼,找了原因。
在springMvc中,我們都知道路徑的映射匹配是通過DispatcherServlet這個(gè)類來實(shí)現(xiàn)的,最終的函數(shù)執(zhí)行在doDispatch()這個(gè)方法中:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
(1)mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
(2)HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
(3)if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
(4)mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
(5)mappedHandler.applyPostHandle(processedRequest, response, mv);
}
在這個(gè)類中,我們關(guān)注(1)-(5)這幾句代碼,基本上整個(gè)映射執(zhí)行的邏輯就明了了:
- (1)根據(jù)請求request獲取執(zhí)行器鏈(包括攔截器和最終執(zhí)行方法Handler)
- (2)根據(jù)Handler獲取handlerAdapter;
- (3)執(zhí)行執(zhí)行器鏈中的攔截方法(preHandle);
- (4)執(zhí)行handler方法;
- (5)執(zhí)行執(zhí)行器鏈中的攔截方法(postHandle);
在這個(gè)函數(shù)中我們并沒有看到什么時(shí)候執(zhí)行addCorsMappings這一配置內(nèi)容,那它到底是什么時(shí)候添加的呢,那就需要仔細(xì)分析步驟(1)了:獲取整個(gè)執(zhí)行器鏈。
通過調(diào)試定位,我發(fā)現(xiàn)getHandle()最終執(zhí)行的AbstractHandlerMapping這個(gè)類的函數(shù)
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
(1)Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
(2)HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
(3) executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
這個(gè)函數(shù)中我也標(biāo)記了(1)、(2)、(3)這三條語句:
- (1)獲取request所需執(zhí)行的handler,具體邏輯不再細(xì)說,有興趣的可以參考我的另一篇文章;
- (2)獲取執(zhí)行器鏈,簡單來說就是把具體的執(zhí)行器和整個(gè)攔截器鏈組成一個(gè)鏈隊(duì)形,方便后續(xù)執(zhí)行;
- (3)這個(gè)就是關(guān)鍵點(diǎn),可能有的同學(xué)已經(jīng)看明白了,addCorsMapping配置就是在這塊引入的;
進(jìn)入這個(gè)方法后,一切都明了了;
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, 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;
}
先判斷request是否是預(yù)檢請求(不明白什么是預(yù)檢請求的可以自身搜索相關(guān)解釋,很多,不再贅述),是預(yù)檢請求則生成個(gè)預(yù)檢執(zhí)行器PreFlightHandler,然后在doDispatch函數(shù)(4)中執(zhí)行;
否則生成一個(gè)跨域攔截器加入攔截器鏈中,最終再doDispatch函數(shù)(3)處執(zhí)行,而因?yàn)閿r截器是順序執(zhí)行的,如果前面執(zhí)行失敗異常返回后,后面的則不再執(zhí)行。
所以當(dāng)跨越請求在攔截器那邊處理后就異常返回了,那么響應(yīng)的response報(bào)文頭部關(guān)于跨域允許的信息就沒有被正確設(shè)置,導(dǎo)致瀏覽器認(rèn)為服務(wù)不允許跨域,而造成錯(cuò)誤;而當(dāng)我們使用過濾器時(shí),過濾器先于攔截器執(zhí)行,那么無論是否被攔截,始終有允許跨域的頭部信息,就不會出問題了。
另注:
對于預(yù)檢請求,一般token驗(yàn)證時(shí)是不會攔截此請求的,因?yàn)轭A(yù)檢請求不會附帶任何參數(shù)信息,也就沒有所需的token信息,所以攔截時(shí)需過濾預(yù)檢請求
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
MyBatis中的@SelectProvider注解源碼分析
這篇文章主要介紹了MyBatis中的@SelectProvider注解源碼分析,@SelectProvider功能就是用來單獨(dú)寫一個(gè)class類與方法,用來提供一些xml或者注解中不好寫的sql,今天就來說下這個(gè)注解的具體用法與源碼,需要的朋友可以參考下2024-01-01
Java超詳細(xì)講解多線程中的Process與Thread
進(jìn)程process:在一定的環(huán)境下,把靜態(tài)的程序代碼運(yùn)行起來,通過使用不同的資源,來完成一定的任務(wù);線程thread:是程序中一個(gè)單一的順序控制流程。在單個(gè)進(jìn)程中同時(shí)運(yùn)行多個(gè)線程完成不同的工作,稱為多線程2022-05-05
Java 遞歸查詢部門樹形結(jié)構(gòu)數(shù)據(jù)的實(shí)踐
本文主要介紹了Java 遞歸查詢部門樹形結(jié)構(gòu)數(shù)據(jù)的實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
java中的動(dòng)態(tài)代理與責(zé)任鏈模式詳解
這篇文章主要介紹了java中的動(dòng)態(tài)代理與責(zé)任鏈模式詳解,動(dòng)態(tài)代理提供了一種靈活且非侵入式的方式,可以對對象的行為進(jìn)行定制和擴(kuò)展,它在代碼重用、解耦和業(yè)務(wù)邏輯分離、性能優(yōu)化以及系統(tǒng)架構(gòu)中起到了重要的作用,需要的朋友可以參考下2023-08-08
Java通過JsApi方式實(shí)現(xiàn)微信支付
本文講解了Java如何實(shí)現(xiàn)JsApi方式的微信支付,代碼內(nèi)容詳細(xì),文章思路清晰,需要的朋友可以參考下2015-07-07
java獲取登錄者IP和登錄時(shí)間的兩種實(shí)現(xiàn)代碼詳解
這篇文章主要介紹了java獲取登錄者IP和登錄時(shí)間的實(shí)現(xiàn)代碼,本文通過兩種結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07

