Spring @CrossOrigin 通配符 解决跨域问题

@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);

注意问题:

  1. 如果您正在使用 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()...

​    }

}
  1. 全局 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("/**");

​    }

}

  1. 基于过滤器的 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

https://www.jianshu.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

* * *

posted @ 2019-08-13 20:49  托马斯布莱克  阅读(3348)  评论(0编辑  收藏  举报