??@CrossOrigin原始碼決議主要分為兩個階段:
??① @CrossOrigin注釋的方法掃描注冊,
??② 請求匹配@CrossOrigin注釋的方法,
??本文針對第②階段從原始碼角度進行決議,關于第①階段請參照《Spring 注解面面通 之 @CrossOrigin 注冊處理方法原始碼決議》,
??請求匹配@CrossOrigin注釋的方法
??請求匹配@CrossOrigin注釋的方法流程:

??1) DispatcherServlet.doDispatch(...)、DispatcherServlet.getHandler(...)、AbstractHandlerMapping.getHandler(...)方法,
??標題中所列方法在《Spring 注解面面通 之 @ModelAttribute 深入原始碼決議》均有介紹,可以查看進行參照,這里不再詳細贅述,
??2) AbstractHandlerMethodMapping.getCorsConfiguration(...)方法,
??① 若處理器為CorsConfigurationSource型別,獲取處理器CorsConfiguration配置作為基礎配置,
??② 若處理方法為預處理方法,則回傳默認配置CorsConfiguration,其配置均為*,
??③ 若處理方法非預處理方法,根據處理方法查找CorsConfiguration配置,同時與①所得配置進行合并,
/**
* 獲取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;
// 請求方法是預處理方法.
if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {
return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG;
}
// 請求方法非預處理方法.
else {
// 根據處理器查找CorsConfiguration配置.
CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod);
// 合并corsConfig和corsConfigFromMethod.
corsConfig = (corsConfig != null ? corsConfig.combine(corsConfigFromMethod) : corsConfigFromMethod);
}
}
return corsConfig;
}
??3) AbstractHandlerMapping.getCorsConfiguration(...)方法,
??若處理器為CorsConfigurationSource型別,獲取處理器CorsConfiguration配置作為基礎配置,
/**
* 檢索給定處理程式的CORS配置.
* @param handler 處理器.
* @param request 當前請求.
* @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決議實際的HandlerMethod,
??② 根據HandlerMethod從corsLookup中獲取CorsConfiguration配置,
/**
* 回傳CORS配置.
* 執行緒安全并發使用.
*/
public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {
// 決議實際的HandlerMethod.
HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();
// 從corsLookup中獲取CorsConfiguration配置.
return this.corsLookup.get(original != null ? original : handlerMethod);
}
??5) AbstractHandlerMapping.getCorsHandlerExecutionChain(...)方法,
??① 若請求為預處理請求,AbstractHandlerMapping.PreFlightHandler作為處理器實作,
??② 若請求非預處理請求,增加攔截器AbstractHandlerMapping.CorsInterceptor對請求進行攔截處理,
/**
* 為CORS相關處理更新HandlerExecutionChain.
* 對于預處理請求,默認實作用一個簡單的HttpRequestHandler替換所選的處理程式,
* 該處理程式呼叫配置的setCorsProcessor.
* 對于實際的請求,默認實作插入一個HandlerInterceptor,它進行CORS相關的檢查并添加CORS頭.
* @param request 當前請求.
* @param chain 處理鏈.
* @param config 適用的CORS配置(可能是null).
*/
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
// 是CORS預處理請求.
if (CorsUtils.isPreFlightRequest(request)) {
HandlerInterceptor[] interceptors = chain.getInterceptors();
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
}
// 不是CORS預處理請求.
else {
chain.addInterceptor(new CorsInterceptor(config));
}
return chain;
}
??6) AbstractHandlerMapping.PreFlightHandler、AbstractHandlerMapping.CorsInterceptor類,
??① 若請求非CORS請求,則跳過處理邏輯,
??② 若回應已包含Access-Control-Allow-Origin頭,則跳過處理邏輯,
??③ 若請求與服務同源,則跳過處理邏輯,
??④ 當CORS配置為null時,若請求為預處理請求,則拒絕請求,否則跳過處理邏輯,
/**
* 處理請求.
*/
@Override
@SuppressWarnings("resource")
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
HttpServletResponse response) throws IOException {
// 非CORS請求,不進行處理.
if (!CorsUtils.isCorsRequest(request)) {
return true;
}
// 回應已包含Access-Control-Allow-Origin,不進行處理.
ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
if (responseHasCors(serverResponse)) {
logger.debug("Skip CORS processing: response already contains \"Access-Control-Allow-Origin\" header");
return true;
}
// 請求來自同源,不進行處理.
ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
if (WebUtils.isSameOrigin(serverRequest)) {
logger.debug("Skip CORS processing: request is from same origin");
return true;
}
// 是否預處理請求.
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
if (config == null) {
if (preFlightRequest) {
rejectRequest(serverResponse);
return false;
}
else {
return true;
}
}
// 請求處理方法.
return handleInternal(serverRequest, serverResponse, config, preFlightRequest);
}
??① 獲取請求Origin頭,檢查并獲取允許的源,
??② 針對Vary頭,增加值:Origin、Access-Control-Request-Method、Access-Control-Request-Headers,
??③ 允許的源為空,則拒絕請求,
??④ 獲取請求方法,檢查并獲取允許的方法,
??⑤ 允許的方法為空,則拒絕請求,
??⑥ 獲取請求頭,檢查并獲取請求的頭,
??⑦ 若請求為預處理請求,且允許的頭為空,拒絕請求,
??⑧ 設定Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Expose-Headers、Access-Control-Allow-Credentials、Access-Control-Max-Age,
/**
* 請求處理方法.
*/
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config, boolean preFlightRequest) throws IOException {
// 獲取請求的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));
// 允許源為空,則拒絕請求.
if (allowOrigin == null) {
logger.debug("Rejecting CORS request because '" + requestOrigin + "' origin is not allowed");
rejectRequest(response);
return false;
}
// 獲取請求方法.
HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
// 檢查并獲取允許的方法.
List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
// 允許方法為空,則拒絕請求.
if (allowMethods == null) {
logger.debug("Rejecting CORS request because '" + requestMethod + "' request method is not allowed");
rejectRequest(response);
return false;
}
// 獲取請求頭.
List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
// 檢查并獲取請求的頭.
List<String> allowHeaders = checkHeaders(config, requestHeaders);
// 預處理請求,且允許的頭為空,拒絕請求.
if (preFlightRequest && allowHeaders == null) {
logger.debug("Rejecting CORS request because '" + requestHeaders + "' request headers are not allowed");
rejectRequest(response);
return false;
}
// 設定允許的源.
responseHeaders.setAccessControlAllowOrigin(allowOrigin);
// 設定Access-Control-Allow-Methods.
if (preFlightRequest) {
responseHeaders.setAccessControlAllowMethods(allowMethods);
}
// 設定Access-Control-Allow-Headers.
if (preFlightRequest && !allowHeaders.isEmpty()) {
responseHeaders.setAccessControlAllowHeaders(allowHeaders);
}
// 設定Access-Control-Expose-Headers.
if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
}
// 設定Access-Control-Allow-Credentials.
if (Boolean.TRUE.equals(config.getAllowCredentials())) {
responseHeaders.setAccessControlAllowCredentials(true);
}
// 設定Access-Control-Max-Age.
if (preFlightRequest && config.getMaxAge() != null) {
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
}
response.flush();
return true;
}
??總結
??只有在了解實作細節的情況下,才能解決那些棘手的問題,隨著前后端分離程式變得極為普遍,@CrossOrigin的應用變得尤為重要,
??原始碼決議基于spring-framework-5.0.5.RELEASE版本原始碼,
??若文中存在錯誤和不足,歡迎指正!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/253581.html
標籤:其他
