spring MVC cors跨域實現源碼解析 CorsConfiguration UrlBasedCorsConfigurationSource

spring MVC cors跨域實現源碼解析

spring MVC cors跨域實現源碼解析

名詞解釋:跨域資源共享(Cross-Origin Resource Sharing)html

簡單說就是隻要協議、IP、http方法任意一個不一樣就是跨域。java

spring MVC自4.2開始添加了跨域的支持。git

跨域具體的定義請移步mozilla查看github

使用案例

spring mvc中跨域使用有3種方式:web

在web.xml中配置CorsFilterspring

<filter> <filter-name>cors</filter-name> <filter-class>org.springframework.web.filter.CorsFilter</filter-class> </filter> <filter-mapping> <filter-name>cors</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

在xml中配置api

// 簡單配置,未配置的均使用默認值,就是全面放開
<mvc:cors> <mvc:mapping path="/**" /> </mvc:cors> // 這是一個全量配置 <mvc:cors> <mvc:mapping path="/api/**" allowed-origins="http://domain1.com, http://domain2.com" allowed-methods="GET, PUT" allowed-headers="header1, header2, header3" exposed-headers="header1, header2" allow-credentials="false" max-age="123" /> <mvc:mapping path="/resources/**" allowed-origins="http://domain1.com" /> </mvc:cors> 

使用註解跨域

@CrossOrigin(maxAge = 3600) @RestController @RequestMapping("/account") public class AccountController { @CrossOrigin("http://domain2.com") @RequestMapping("/{id}") public Account retrieve(@PathVariable Long id) { // ... } } 

涉及概念

  • CorsConfiguration 具體封裝跨域配置信息的pojospring-mvc

  • CorsConfigurationSource request與跨域配置信息映射的容器markdown

  • CorsProcessor 具體進行跨域操做的類

  • 諾幹跨域配置信息初始化類

  • 諾幹跨域使用的Adapter

涉及的java類:

  • 封裝信息的pojo

    CorsConfiguration

  • 存儲request與跨域配置信息的容器

    CorsConfigurationSource、UrlBasedCorsConfigurationSource

  • 具體處理類

    CorsProcessor、DefaultCorsProcessor

  • CorsUtils

  • 實現OncePerRequestFilter接口的Adapter

    CorsFilter

  • 校驗request是否cors,並封裝對應的Adapter

    AbstractHandlerMapping、包括內部類PreFlightHandler、CorsInterceptor

  • 讀取CrossOrigin註解信息

    AbstractHandlerMethodMapping、RequestMappingHandlerMapping

  • 從xml文件中讀取跨域配置信息

    CorsBeanDefinitionParser

  • 跨域註冊輔助類

    MvcNamespaceUtils

debug分析

要看懂代碼咱們須要先了解下封裝跨域信息的pojo--CorsConfiguration

這邊是一個很是簡單的pojo,除了跨域對應的幾個屬性,就只有combine、checkOrigin、checkHttpMethod、checkHeaders。

屬性都是多值組合使用的。

// CorsConfiguration public static final String ALL = "*"; // 容許的請求源 private List<String> allowedOrigins; // 容許的http方法 private List<String> allowedMethods; // 容許的請求頭 private List<String> allowedHeaders; // 返回的響應頭 private List<String> exposedHeaders; // 是否容許攜帶cookies private Boolean allowCredentials; // 預請求的存活有效期 private Long maxAge;

combine是將跨域信息進行合併

3個check方法分別是覈對request中的信息是否包含在容許範圍內

配置初始化

在系統啓動時經過CorsBeanDefinitionParser解析配置文件;

加載RequestMappingHandlerMapping時,經過InitializingBean的afterProperties的鉤子調用initCorsConfiguration初始化註解信息;

配置文件初始化

在CorsBeanDefinitionParser類的parse方法中打一個斷點。

CorsBeanDefinitionParser中的斷點

CorsBeanDefinitionParser的調用棧

CorsBeanDefinitionParser的調用棧

經過代碼能夠看到這邊解析中的定義信息。

跨域信息的配置能夠以path爲單位定義多個映射關係。

解析時若是沒有定義則使用默認設置

// CorsBeanDefinitionParser if (mappings.isEmpty()) { // 最簡配置時的默認設置 CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(DEFAULT_ALLOWED_ORIGINS); config.setAllowedMethods(DEFAULT_ALLOWED_METHODS); config.setAllowedHeaders(DEFAULT_ALLOWED_HEADERS); config.setAllowCredentials(DEFAULT_ALLOW_CREDENTIALS); config.setMaxAge(DEFAULT_MAX_AGE); corsConfigurations.put("/**", config); }else { // 單個mapping的處理 for (Element mapping : mappings) { CorsConfiguration config = new CorsConfiguration(); if (mapping.hasAttribute("allowed-origins")) { String[] allowedOrigins = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-origins"), ","); config.setAllowedOrigins(Arrays.asList(allowedOrigins)); } // ... }

解析完成後,經過MvcNamespaceUtils.registerCorsConfiguratoions註冊

這邊走的是spring bean容器管理的統一流程,如今轉化爲BeanDefinition而後再實例化。

// MvcNamespaceUtils public static RuntimeBeanReference registerCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations, ParserContext parserContext, Object source) { if (!parserContext.getRegistry().containsBeanDefinition(CORS_CONFIGURATION_BEAN_NAME)) { RootBeanDefinition corsConfigurationsDef = new RootBeanDefinition(LinkedHashMap.class); corsConfigurationsDef.setSource(source); corsConfigurationsDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); if (corsConfigurations != null) { corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations); } parserContext.getReaderContext().getRegistry().registerBeanDefinition(CORS_CONFIGURATION_BEAN_NAME, corsConfigurationsDef); parserContext.registerComponent(new BeanComponentDefinition(corsConfigurationsDef, CORS_CONFIGURATION_BEAN_NAME)); } else if (corsConfigurations != null) { BeanDefinition corsConfigurationsDef = parserContext.getRegistry().getBeanDefinition(CORS_CONFIGURATION_BEAN_NAME); corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations); } return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME); }

註解初始化

在RequestMappingHandlerMapping的initCorsConfiguration中掃描使用CrossOrigin註解的方法,並提取信息。

RequestMappingHandlerMapping

RequestMappingHandlerMapping_initCorsConfiguration

// RequestMappingHandlerMapping @Override protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { HandlerMethod handlerMethod = createHandlerMethod(handler, method); CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), CrossOrigin.class); CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class); if (typeAnnotation == null && methodAnnotation == null) { return null; } CorsConfiguration config = new CorsConfiguration(); updateCorsConfig(config, typeAnnotation); updateCorsConfig(config, methodAnnotation); // ... 設置默認值 return config; } 

跨域請求處理

HandlerMapping在正常處理完查找處理器後,在AbstractHandlerMapping.getHandler中校驗是不是跨域請求,若是是分兩種進行處理:

  • 若是是預請求,將處理器替換爲內部類PreFlightHandler

  • 若是是正常請求,添加CorsInterceptor攔截器

拿處處理器後,經過請求頭是否包含Origin判斷是否跨域,若是是跨域,經過UrlBasedCorsConfigurationSource獲取跨域配置信息,並委託getCorsHandlerExecutionChain處理

UrlBasedCorsConfigurationSource是CorsConfigurationSource的實現,從類名就能夠猜出這邊request與CorsConfiguration的映射是基於url的。getCorsConfiguration中提取request中的url後,逐一驗證配置是否匹配url。

// UrlBasedCorsConfigurationSource public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); for(Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) { if (this.pathMatcher.match(entry.getKey(), lookupPath)) { return entry.getValue(); } } return null; } // AbstractHandlerMapping public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Object handler = getHandlerInternal(request); // ... HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; } // HttpHeaders public static final String ORIGIN = "Origin"; // CorsUtils public static boolean isCorsRequest(HttpServletRequest request) { return (request.getHeader(HttpHeaders.ORIGIN) != null); }

經過請求頭的http方法是否options判斷是否預請求,若是是使用PreFlightRequest替換處理器;若是是普通請求,添加一個攔截器CorsInterceptor。

PreFlightRequest是CorsProcessor對於HttpRequestHandler的一個適配器。這樣HandlerAdapter直接使用HttpRequestHandlerAdapter處理。

CorsInterceptor 是CorsProcessor對於HnalderInterceptorAdapter的適配器。

// AbstractHandlerMapping 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; } private class PreFlightHandler implements HttpRequestHandler { private final CorsConfiguration config; public PreFlightHandler(CorsConfiguration config) { this.config = config; } @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { corsProcessor.processRequest(this.config, request, response); } } private class CorsInterceptor extends HandlerInterceptorAdapter { private final CorsConfiguration config; public CorsInterceptor(CorsConfiguration config) { this.config = config; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return corsProcessor.processRequest(this.config, request, response); } } // CorsUtils public static boolean isPreFlightRequest(HttpServletRequest request) { return (isCorsRequest(request) && request.getMethod().equals(HttpMethod.OPTIONS.name()) && request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null); } 

能夠去github查看: https://github.com/haplone/spring_doc/blob/master/mvc/cors.md

參考: https://spring.io/blog/2015/06/08/cors-support-in-spring-framework

相關文章
相關標籤/搜索