Spring的@CrossOrigin注解處理請(qǐng)求源碼解析
前言
@CrossOrigin源碼解析主要分為兩個(gè)階段:
① @CrossOrigin注釋的方法掃描注冊(cè)。
② 請(qǐng)求匹配@CrossOrigin注釋的方法。
本文針對(duì)第②階段從源碼角度進(jìn)行解析
請(qǐng)求匹配@CrossOrigin注釋的方法
請(qǐng)求匹配@CrossOrigin注釋的方法流程

1) DispatcherServlet.doDispatch(...)、DispatcherServlet.getHandler(...)、AbstractHandlerMapping.getHandler(...)方法。
2) AbstractHandlerMethodMapping.getCorsConfiguration(...)方法。
① 若處理器為CorsConfigurationSource類型,獲取處理器CorsConfiguration配置作為基礎(chǔ)配置。
② 若處理方法為預(yù)處理方法,則返回默認(rèn)配置CorsConfiguration,其配置均為*。
③ 若處理方法非預(yù)處理方法,根據(jù)處理方法查找CorsConfiguration配置,同時(shí)與①所得配置進(jìn)行合并。
/**
* 獲取CorsConfiguration.
*/
@Override
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {
// 若處理器為CorsConfigurationSource類型,直接獲取處理器CorsConfiguration配置.
CorsConfiguration corsConfig = super.getCorsConfiguration(handler, request);
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 請(qǐng)求方法是預(yù)處理方法.
if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {
return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG;
}
// 請(qǐng)求方法非預(yù)處理方法.
else {
// 根據(jù)處理器查找CorsConfiguration配置.
CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod);
// 合并corsConfig和corsConfigFromMethod.
corsConfig = (corsConfig != null ? corsConfig.combine(corsConfigFromMethod) : corsConfigFromMethod);
}
}
return corsConfig;
}3) AbstractHandlerMapping.getCorsConfiguration(...)方法。
若處理器為CorsConfigurationSource類型,獲取處理器CorsConfiguration配置作為基礎(chǔ)配置。
/**
* 檢索給定處理程序的CORS配置.
* @param handler 處理器.
* @param request 當(dāng)前請(qǐng)求.
* @return 處理程序的CORS配置,或者null(如果沒有).
*/
@Nullable
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {
Object resolvedHandler = handler;
if (handler instanceof HandlerExecutionChain) {
resolvedHandler = ((HandlerExecutionChain) handler).getHandler();
}
if (resolvedHandler instanceof CorsConfigurationSource) {
return ((CorsConfigurationSource) resolvedHandler).getCorsConfiguration(request);
}
return null;
}4) AbstractHandlerMethodMapping.MappingRegistry.getCorsConfiguration(...)方法。
① 從HandlerMethod解析實(shí)際的HandlerMethod。
② 根據(jù)HandlerMethod從corsLookup中獲取CorsConfiguration配置。
/**
* 返回CORS配置.
* 線程安全并發(fā)使用.
*/
public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {
// 解析實(shí)際的HandlerMethod.
HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();
// 從corsLookup中獲取CorsConfiguration配置.
return this.corsLookup.get(original != null ? original : handlerMethod);
}5) AbstractHandlerMapping.getCorsHandlerExecutionChain(...)方法。
① 若請(qǐng)求為預(yù)處理請(qǐng)求,AbstractHandlerMapping.PreFlightHandler作為處理器實(shí)現(xiàn)。
② 若請(qǐng)求非預(yù)處理請(qǐng)求,增加攔截器AbstractHandlerMapping.CorsInterceptor對(duì)請(qǐng)求進(jìn)行攔截處理。
/**
* 為CORS相關(guān)處理更新HandlerExecutionChain.
* 對(duì)于預(yù)處理請(qǐng)求,默認(rèn)實(shí)現(xiàn)用一個(gè)簡(jiǎn)單的HttpRequestHandler替換所選的處理程序,
* 該處理程序調(diào)用配置的setCorsProcessor.
* 對(duì)于實(shí)際的請(qǐng)求,默認(rèn)實(shí)現(xiàn)插入一個(gè)HandlerInterceptor,它進(jìn)行CORS相關(guān)的檢查并添加CORS頭.
* @param request 當(dāng)前請(qǐng)求.
* @param chain 處理鏈.
* @param config 適用的CORS配置(可能是null).
*/
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
// 是CORS預(yù)處理請(qǐng)求.
if (CorsUtils.isPreFlightRequest(request)) {
HandlerInterceptor[] interceptors = chain.getInterceptors();
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
}
// 不是CORS預(yù)處理請(qǐng)求.
else {
chain.addInterceptor(new CorsInterceptor(config));
}
return chain;
}6) AbstractHandlerMapping.PreFlightHandler、AbstractHandlerMapping.CorsInterceptor類。
① 若請(qǐng)求非CORS請(qǐng)求,則跳過(guò)處理邏輯。
② 若響應(yīng)已包含Access-Control-Allow-Origin頭,則跳過(guò)處理邏輯。
③ 若請(qǐng)求與服務(wù)同源,則跳過(guò)處理邏輯。
④ 當(dāng)CORS配置為null時(shí),若請(qǐng)求為預(yù)處理請(qǐng)求,則拒絕請(qǐng)求,否則跳過(guò)處理邏輯。
/**
* 處理請(qǐng)求.
*/
@Override
@SuppressWarnings("resource")
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
HttpServletResponse response) throws IOException {
// 非CORS請(qǐng)求,不進(jìn)行處理.
if (!CorsUtils.isCorsRequest(request)) {
return true;
}
// 響應(yīng)已包含Access-Control-Allow-Origin,不進(jìn)行處理.
ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
if (responseHasCors(serverResponse)) {
logger.debug("Skip CORS processing: response already contains \"Access-Control-Allow-Origin\" header");
return true;
}
// 請(qǐng)求來(lái)自同源,不進(jìn)行處理.
ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
if (WebUtils.isSameOrigin(serverRequest)) {
logger.debug("Skip CORS processing: request is from same origin");
return true;
}
// 是否預(yù)處理請(qǐng)求.
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
if (config == null) {
if (preFlightRequest) {
rejectRequest(serverResponse);
return false;
}
else {
return true;
}
}
// 請(qǐng)求處理方法.
return handleInternal(serverRequest, serverResponse, config, preFlightRequest);
}① 獲取請(qǐng)求Origin頭,檢查并獲取允許的源。
② 針對(duì)Vary頭,增加值:Origin、Access-Control-Request-Method、Access-Control-Request-Headers。
③ 允許的源為空,則拒絕請(qǐng)求。
④ 獲取請(qǐng)求方法,檢查并獲取允許的方法。
⑤ 允許的方法為空,則拒絕請(qǐng)求。
⑥ 獲取請(qǐng)求頭,檢查并獲取請(qǐng)求的頭。
⑦ 若請(qǐng)求為預(yù)處理請(qǐng)求,且允許的頭為空,拒絕請(qǐng)求。
⑧ 設(shè)置Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Expose-Headers、Access-Control-Allow-Credentials、Access-Control-Max-Age。
/**
* 請(qǐng)求處理方法.
*/
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config, boolean preFlightRequest) throws IOException {
// 獲取請(qǐng)求的Origin頭.
String requestOrigin = request.getHeaders().getOrigin();
// 檢查并獲取允許源.
String allowOrigin = checkOrigin(config, requestOrigin);
HttpHeaders responseHeaders = response.getHeaders();
// 增加Vary頭,其值為:Origin、Access-Control-Request-Method、Access-Control-Request-Headers.
responseHeaders.addAll(HttpHeaders.VARY, Arrays.asList(HttpHeaders.ORIGIN,
HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));
// 允許源為空,則拒絕請(qǐng)求.
if (allowOrigin == null) {
logger.debug("Rejecting CORS request because '" + requestOrigin + "' origin is not allowed");
rejectRequest(response);
return false;
}
// 獲取請(qǐng)求方法.
HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
// 檢查并獲取允許的方法.
List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
// 允許方法為空,則拒絕請(qǐng)求.
if (allowMethods == null) {
logger.debug("Rejecting CORS request because '" + requestMethod + "' request method is not allowed");
rejectRequest(response);
return false;
}
// 獲取請(qǐng)求頭.
List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
// 檢查并獲取請(qǐng)求的頭.
List<String> allowHeaders = checkHeaders(config, requestHeaders);
// 預(yù)處理請(qǐng)求,且允許的頭為空,拒絕請(qǐng)求.
if (preFlightRequest && allowHeaders == null) {
logger.debug("Rejecting CORS request because '" + requestHeaders + "' request headers are not allowed");
rejectRequest(response);
return false;
}
// 設(shè)置允許的源.
responseHeaders.setAccessControlAllowOrigin(allowOrigin);
// 設(shè)置Access-Control-Allow-Methods.
if (preFlightRequest) {
responseHeaders.setAccessControlAllowMethods(allowMethods);
}
// 設(shè)置Access-Control-Allow-Headers.
if (preFlightRequest && !allowHeaders.isEmpty()) {
responseHeaders.setAccessControlAllowHeaders(allowHeaders);
}
// 設(shè)置Access-Control-Expose-Headers.
if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
}
// 設(shè)置Access-Control-Allow-Credentials.
if (Boolean.TRUE.equals(config.getAllowCredentials())) {
responseHeaders.setAccessControlAllowCredentials(true);
}
// 設(shè)置Access-Control-Max-Age.
if (preFlightRequest && config.getMaxAge() != null) {
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
}
response.flush();
return true;
}總結(jié)
只有在了解實(shí)現(xiàn)細(xì)節(jié)的情況下,才能解決那些棘手的問題。隨著前后端分離程序變得極為普遍,@CrossOrigin的應(yīng)用變得尤為重要。
源碼解析基于spring-framework-5.0.5.RELEASE版本源碼。
若文中存在錯(cuò)誤和不足,歡迎指正!
到此這篇關(guān)于Spring的@CrossOrigin注解處理請(qǐng)求源碼解析的文章就介紹到這了,更多相關(guān)@CrossOrigin注解處理請(qǐng)求內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java如何實(shí)現(xiàn)簡(jiǎn)單后臺(tái)訪問并獲取IP
這篇文章主要介紹了Java如何實(shí)現(xiàn)簡(jiǎn)單后臺(tái)訪問并獲取IP,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
SpringBoot登錄判斷過(guò)程代碼實(shí)例
這篇文章主要介紹了SpringBoot登錄判斷代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Shell重啟SpringBoot項(xiàng)目腳本的示例代碼(含服務(wù)守護(hù))
本文介紹了如何使用?Bash?腳本來(lái)管理和守護(hù)運(yùn)行服務(wù),將展示一個(gè)示例腳本,該腳本可以停止、啟動(dòng)和守護(hù)運(yùn)行一個(gè)服務(wù),并提供了相應(yīng)的解釋和用法說(shuō)明,文章通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11
Java 反射調(diào)用靜態(tài)方法的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇Java 反射調(diào)用靜態(tài)方法的簡(jiǎn)單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-06-06
如何批量測(cè)試Mybatis項(xiàng)目中的Sql是否正確詳解
這篇文章主要給大家介紹了關(guān)于如何批量測(cè)試Mybatis項(xiàng)目中Sql是否正確的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12
java中棧和隊(duì)列的實(shí)現(xiàn)和API的用法(詳解)
下面小編就為大家?guī)?lái)一篇java中棧和隊(duì)列的實(shí)現(xiàn)和API的用法(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05

