本文由 簡悅 SimpRead 轉碼, 原文地址 https://www.cnblogs.com/wangdaijun/p/11348463.html
@CrossOrigin 通配符 解決跨域問題
痛點:
對很多 api 接口需要 開放 H5 Ajax 跨域請求支持 由于環境多套域名不同, 而 CrossOrigin 原生只支持 * 或者具體域名的跨域支持 所以想讓 CrossOrigin 支持下通配 *.abc.com 支持所有 origin 為 abc.com 域 (包括各種子域名) 名來的 Ajax 請求支持跨域.
解決思路:
支持通配
@CrossOrigin(origins = {".abc.com"}) 通配 主域 + 任意子域 www.abc.com order.api.abc.com dev.order.abc.com 等
@CrossOrigin(origins = {".order.abc.com"}) 通配 order 子域 子域名 dev.order.abc.com test.order.abc.com uat.order.abc.com 等
Spring 默認支持 cors 拓展下 Spring 對跨域的處理類
解決方案:
獲取 RequestMappingHandlerMapping 設置自定義 MyCorsProcessor 代替 DefaultCorsProcessor
/**
* 給requestMappingHandlerMapping 對象注入自定義 MyCorsProcessor
* @author tomas
* @create 2019/8/12
**/
@Configuration
@EnableWebMvc
public class MyWebMvcConfig extends DelegatingWebMvcConfiguration {
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping = super.requestMappingHandlerMapping();
handlerMapping.setCorsProcessor(new MyCorsProcessor());
return handlerMapping;
}
}
/**
* MyCorsProcessor 描述
* 自定義 如果xxx.com域下的請求允許跨域
*
* @author tomas
* @create 2019/8/12
**/
public class MyCorsProcessor extends DefaultCorsProcessor {
/**
* Check the origin of the request against the configured allowed origins.
* @param requestOrigin the origin to check
* @return the origin to use for the response, or {@code null} which
* means the request origin is not allowed
*/
@Nullable
public String checkOrigin(CorsConfiguration config, @Nullable String requestOrigin) {
if (!StringUtils.hasText(requestOrigin)) {
return null;
}
if (ObjectUtils.isEmpty(config.getAllowedOrigins())) {
return null;
}
if (config.getAllowedOrigins().contains(CorsConfiguration.ALL)) {
if (config.getAllowCredentials() != Boolean.TRUE) {
return CorsConfiguration.ALL;
}
else {
return requestOrigin;
}
}
AntPathMatcher pathMatcher = new AntPathMatcher("|");
for (String allowedOrigin :config.getAllowedOrigins()) {
if (requestOrigin.equalsIgnoreCase(allowedOrigin)) {
return requestOrigin;
}
//推薦方式:正則 注意(CrossOrigin(origins = {"*.abc.com"}) ) 主域會匹配主域+子域 origins = {"*.pay.abc.com"} 子域名只會匹配子域
if(pathMatcher.isPattern(allowedOrigin)&&pathMatcher.match(allowedOrigin,requestOrigin)){
return requestOrigin;
}
//不推薦方式:寫死
if(allowedOrigin.contains("*.abc.com")&& requestOrigin.contains("abc.com")){
return requestOrigin;
}
}
return null;
}
}
原理分析:
Spring mvc cors
Spring MVC 的文檔這樣說:
Spring MVC 的 HandlerMapping 實現內置支持 CORS, 在成功映射一個請求到一個 handler 之后, HandlerMapping 會檢查 CORS 配置以采取下一步動作。
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-cors-processing
Spring MVC 會在找到 handler 后通過添加一個攔截器來檢查 CORS 配置。
下面來看一下 Spring MVC 中的 CORS 的實現。
DispatcherServlet 調用 AbstractHandlerMapping 中的 getHandler() 方法:
/**
* 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
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
對于 Ajax 請求 getCorsHandlerExecutionChain 自動加上一個 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, @Nullable 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 中 私有 class CorsInterceptor
private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {
@Nullable
private final CorsConfiguration config;
public CorsInterceptor(@Nullable CorsConfiguration config) {
this.config = config;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return corsProcessor.processRequest(this.config, request, response);
}
@Override
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.config;
}
}
CorsInterceptor 中 preHandle 方法 實際處理 processRequest 的是 AbstractHandlerMapping.this.corsProcessor
這個 corsProcessor =new DefaultCorsProcessor() 是一個默認的跨域處理類
我們的重點就是 重寫 DefaultCorsProcessor 的 checkOrigin 方法
@Override
@SuppressWarnings("resource")
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
HttpServletResponse response) throws IOException {
if (!CorsUtils.isCorsRequest(request)) {
return true;
}
......
return handleInternal(serverRequest, serverResponse, config, preFlightRequest);
}
/**
* Handle the given request.
*/
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config, boolean preFlightRequest) throws IOException {
String requestOrigin = request.getHeaders().getOrigin();
String allowOrigin = checkOrigin(config, requestOrigin);
HttpHeaders responseHeaders = response.getHeaders();
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;
}
..........
response.flush();
return true;
}
/**
* Check the origin and determine the origin for the response. The default
* implementation simply delegates to
* {@link org.springframework.web.cors.CorsConfiguration#checkOrigin(String)}.
*/
// 重寫此方法 支持通配符 或者支持正則表達式 寫法見開頭解決方案
@Nullable
protected String checkOrigin(CorsConfiguration config, @Nullable String requestOrigin) {
return config.checkOrigin(requestOrigin);
}
}
dispatcherServlet 中在真正 invoke handler 之前會先調用攔截器: 從而通過加的 cors 攔截器阻止請求。
doDispatch 方法:
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
注意問題:
- 如果您正在使用 Spring Security,請確保在 Spring 安全級別啟用 CORS,并允許它利用 Spring MVC 級別定義的配置。在 Spring 安全級別啟用 CORS:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
? @Override
? protected void configure(HttpSecurity http) throws Exception {
? http.cors().and()...
? }
}
- 全局 CORS 配置
除了細粒度、基于注釋的配置之外,您還可能需要定義一些全局 CORS 配置。這類似于使用篩選器,但可以聲明為 Spring MVC 并結合細粒度 @CrossOrigin 配置。默認情況下,所有 origins and GET, HEAD and POST methods 是允許的。
使整個應用程序的 CORS 簡化為:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurer {
? @Override
? public void addCorsMappings(CorsRegistry registry) {
? registry.addMapping("/**");
? }
}
- 基于過濾器的 CORS 支持
作為上述其他方法的替代,Spring 框架還提供了 CorsFilter。在這種情況下,不用使用@CrossOrigin或``WebMvcConfigurer#addCorsMappings(CorsRegistry)
,,例如,可以在 Spring Boot 應用程序中聲明如下的過濾器:
@Configuration
public class MyConfiguration {
? @Bean
? public FilterRegistrationBean corsFilter() {
? UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
? CorsConfiguration config = new CorsConfiguration();
? config.setAllowCredentials(true);
? config.addAllowedOrigin("http://domain1.com");
? config.addAllowedHeader("*");
? config.addAllowedMethod("*");
? source.registerCorsConfiguration("/**", config);
? FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
? bean.setOrder(0);
? return bean;
? }
}
感謝 @大神張林峰老師 @王昆老師 @中睿老師 給出的寶貴意見
1、官方文檔 https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
2、https://blog.csdn.net/weixin_33713503/article/details/88039675
http://www.lxweimin.com/p/d05303d34222
https://www.cnblogs.com/helloz/p/10961039.html
2、https://blog.csdn.net/taiyangnimeide/article/details/78305131
3、https://blog.csdn.net/snowin1994/article/details/53035433