spring MVC cors跨域实现源码解析

网友投稿 288 2023-06-14


spring MVC cors跨域实现源码解析

名词解释:跨域资源共享(Cross-Origin Resource Sharing)

简单说就是只要协议、IP、http方法任意一个不同就是跨域。

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

跨域具体的定义请移步mozilla查看

使用案例

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

在web.xml中配置CorsFilter

cors

org.springframework.web.filter.CorsFilter

cors

/*

在xml中配置

// 简单配置,未配置的均使用默认值,就是全面放开

// 这是一个全量配置

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" />

allowed-origins="http://domain1.com" />

使用注解

@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

wItyz

实现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 allowedOrigins;

// 允许的http方法

private List allowedMethods;

// 允许的请求头

private List allowedHeaders;

// 返回的响应头

private List exposedHeaders;

// 是否允许携带cookies

private Boolean allowCredentials;

// 预请求的存活有效期

private Long maxAge;

combine是将跨域信息进行合并

3个check方法分别是核对request中的信息是否包含在允许范围内

配置初始化

在系统启动时通过CorsBeanDefinitionParser解析配置文件;

加载RequestMappingHandlerMapping时,通过InitializingBean的afterProperties的钩子调用initCorsConfiguration初始化注解信息;

配置文件初始化

在CorsBeanDefinitionParser类的parse方法中打一个断点。

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 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_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 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

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" />

allowed-origins="http://domain1.com" />

使用注解

@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

wItyz

实现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 allowedOrigins;

// 允许的http方法

private List allowedMethods;

// 允许的请求头

private List allowedHeaders;

// 返回的响应头

private List exposedHeaders;

// 是否允许携带cookies

private Boolean allowCredentials;

// 预请求的存活有效期

private Long maxAge;

combine是将跨域信息进行合并

3个check方法分别是核对request中的信息是否包含在允许范围内

配置初始化

在系统启动时通过CorsBeanDefinitionParser解析配置文件;

加载RequestMappingHandlerMapping时,通过InitializingBean的afterProperties的钩子调用initCorsConfiguration初始化注解信息;

配置文件初始化

在CorsBeanDefinitionParser类的parse方法中打一个断点。

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 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_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 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

allowed-origins="http://domain1.com" />

使用注解

@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

wItyz

实现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 allowedOrigins;

// 允许的http方法

private List allowedMethods;

// 允许的请求头

private List allowedHeaders;

// 返回的响应头

private List exposedHeaders;

// 是否允许携带cookies

private Boolean allowCredentials;

// 预请求的存活有效期

private Long maxAge;

combine是将跨域信息进行合并

3个check方法分别是核对request中的信息是否包含在允许范围内

配置初始化

在系统启动时通过CorsBeanDefinitionParser解析配置文件;

加载RequestMappingHandlerMapping时,通过InitializingBean的afterProperties的钩子调用initCorsConfiguration初始化注解信息;

配置文件初始化

在CorsBeanDefinitionParser类的parse方法中打一个断点。

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 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_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 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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java 得到集合中所有子集
下一篇:在Spring Boot中如何使用log4j记录日志
相关文章

 发表评论

暂时没有评论,来抢沙发吧~