spring MVC cors跨域實現源碼解析


spring MVC cors跨域實現源碼解析

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

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

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

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

使用案例

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

在web.xml中配置CorsFilter

<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中配置

// 簡單配置,未配置的均使用默認值,就是全面放開
<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 具體封裝跨域配置信息的pojo

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

  • 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的調用棧

通過代碼可以看到這邊解析mvc:cors中的定義信息。

跨域信息的配置可以以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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM