Spring MVC的跨域资源共享(CORS)的使用方法及实现机制
Spring MVC的CORS配置方式
参考:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#cors
方式一:针对Controller或者RequestMapping的注解方式
可以使用@CorssOrigin
进行配置,可以针对RequestMapping:
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@RequestMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
也可以针对Controller:
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@RequestMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
双层控制:
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("http://domain2.com")
@RequestMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
方式二:javaConfig全局配置
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
}
方式三:xml全局配置
<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>
Spring MVC CORS的实现机制
实现机制
在DispatcherServlet
的doDispatch
方法中,代码会调用getHandler
方法,这个方法迭代handlerMappings
列表并分别调用列表中各个HandlerMapping的HandlerMapping.getHandler
方法。
在AbstractHandlerMapping#getHandler#getHandler
方法中可以看到,它使用了CorsUtils#isCorsRequest
判断当前request是否为跨域请求,如果是的话,会调用自己的getCorsHandlerExecutionChain
方法:
/**
* Look up a handler for the given request, falling back to the default
* handler if no specific one is found.
* @param request current HTTP request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// ...省略...
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;
}
而在getCorsHandlerExecutionChain
中可以看到,它首先判断当前请求是否为Http Pre-Flight请求,如果不是,则向executionChain中增加拦截器CorsInterceptor
:
/**
* Update the HandlerExecutionChain for CORS-related handling.
* <p>For pre-flight requests, the default implementation replaces the selected
* handler with a simple HttpRequestHandler that invokes the configured
* {@link #setCorsProcessor}.
* <p>For actual requests, the default implementation inserts a
* HandlerInterceptor that makes CORS-related checks and adds CORS headers.
* @param request the current request
* @param chain the handler chain
* @param config the applicable CORS configuration, possibly {@code null}
* @since 4.2
*/
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;
}
我们可以在同文件中(AbstractHandlerMapping
)看到CorsInterceptor
为private的class。它的唯一任务就是调用corsProcessor.processRequest
来为request注入跨域头:
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);
}
}
关于DefaultCorsProcessor
的源代码就不贴了,请自行阅读。
坑
特别注意!
这个CorsInterceptor
拦截器是动态追加在整个拦截器列表的最后的!
这个CorsInterceptor
拦截器是动态追加在整个拦截器列表的最后的!
这个CorsInterceptor
拦截器是动态追加在整个拦截器列表的最后的!
重要的事情说3遍。所以如果你在拦截器里面抛出Exception的话,就别希望它来帮你加跨域的头了!!!
- 完 -
