Spring的@CrossOrigin注解處理請求源碼解析
前言
@CrossOrigin源碼解析主要分為兩個階段:
① @CrossOrigin注釋的方法掃描注冊。
② 請求匹配@CrossOrigin注釋的方法。
本文針對第②階段從源碼角度進(jìn)行解析
請求匹配@CrossOrigin注釋的方法
請求匹配@CrossOrigin注釋的方法流程
1) DispatcherServlet.doDispatch(...)、DispatcherServlet.getHandler(...)、AbstractHandlerMapping.getHandler(...)方法。
2) AbstractHandlerMethodMapping.getCorsConfiguration(...)方法。
① 若處理器為CorsConfigurationSource類型,獲取處理器CorsConfiguration配置作為基礎(chǔ)配置。
② 若處理方法為預(yù)處理方法,則返回默認(rèn)配置CorsConfiguration,其配置均為*。
③ 若處理方法非預(yù)處理方法,根據(jù)處理方法查找CorsConfiguration配置,同時與①所得配置進(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; // 請求方法是預(yù)處理方法. if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) { return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG; } // 請求方法非預(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)前請求. * @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。
② 根據(jù)HandlerMethod從corsLookup中獲取CorsConfiguration配置。
/** * 返回CORS配置. * 線程安全并發(fā)使用. */ public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) { // 解析實際的HandlerMethod. HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod(); // 從corsLookup中獲取CorsConfiguration配置. return this.corsLookup.get(original != null ? original : handlerMethod); }
5) AbstractHandlerMapping.getCorsHandlerExecutionChain(...)方法。
① 若請求為預(yù)處理請求,AbstractHandlerMapping.PreFlightHandler作為處理器實現(xiàn)。
② 若請求非預(yù)處理請求,增加攔截器AbstractHandlerMapping.CorsInterceptor對請求進(jìn)行攔截處理。
/** * 為CORS相關(guān)處理更新HandlerExecutionChain. * 對于預(yù)處理請求,默認(rèn)實現(xiàn)用一個簡單的HttpRequestHandler替換所選的處理程序, * 該處理程序調(diào)用配置的setCorsProcessor. * 對于實際的請求,默認(rèn)實現(xiàn)插入一個HandlerInterceptor,它進(jìn)行CORS相關(guān)的檢查并添加CORS頭. * @param request 當(dāng)前請求. * @param chain 處理鏈. * @param config 適用的CORS配置(可能是null). */ protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, HandlerExecutionChain chain, @Nullable CorsConfiguration config) { // 是CORS預(yù)處理請求. if (CorsUtils.isPreFlightRequest(request)) { HandlerInterceptor[] interceptors = chain.getInterceptors(); chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors); } // 不是CORS預(yù)處理請求. else { chain.addInterceptor(new CorsInterceptor(config)); } return chain; }
6) AbstractHandlerMapping.PreFlightHandler、AbstractHandlerMapping.CorsInterceptor類。
① 若請求非CORS請求,則跳過處理邏輯。
② 若響應(yīng)已包含Access-Control-Allow-Origin頭,則跳過處理邏輯。
③ 若請求與服務(wù)同源,則跳過處理邏輯。
④ 當(dāng)CORS配置為null時,若請求為預(yù)處理請求,則拒絕請求,否則跳過處理邏輯。
/** * 處理請求. */ @Override @SuppressWarnings("resource") public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, HttpServletResponse response) throws IOException { // 非CORS請求,不進(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; } // 請求來自同源,不進(jìn)行處理. ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request); if (WebUtils.isSameOrigin(serverRequest)) { logger.debug("Skip CORS processing: request is from same origin"); return true; } // 是否預(yù)處理請求. 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。
③ 允許的源為空,則拒絕請求。
④ 獲取請求方法,檢查并獲取允許的方法。
⑤ 允許的方法為空,則拒絕請求。
⑥ 獲取請求頭,檢查并獲取請求的頭。
⑦ 若請求為預(yù)處理請求,且允許的頭為空,拒絕請求。
⑧ 設(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。
/** * 請求處理方法. */ 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); // 預(yù)處理請求,且允許的頭為空,拒絕請求. 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é)
只有在了解實現(xiàn)細(xì)節(jié)的情況下,才能解決那些棘手的問題。隨著前后端分離程序變得極為普遍,@CrossOrigin的應(yīng)用變得尤為重要。
源碼解析基于spring-framework-5.0.5.RELEASE版本源碼。
若文中存在錯誤和不足,歡迎指正!
到此這篇關(guān)于Spring的@CrossOrigin注解處理請求源碼解析的文章就介紹到這了,更多相關(guān)@CrossOrigin注解處理請求內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Shell重啟SpringBoot項目腳本的示例代碼(含服務(wù)守護(hù))
本文介紹了如何使用?Bash?腳本來管理和守護(hù)運(yùn)行服務(wù),將展示一個示例腳本,該腳本可以停止、啟動和守護(hù)運(yùn)行一個服務(wù),并提供了相應(yīng)的解釋和用法說明,文章通過代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11