Spring Cloud配置跨域访问的五种方案

在使用SpringCloud实现微服务时,经常会碰到前端页面访问多个二级域名的情况,跨域是首先要解决的问题。

解决这个问题,可以从两方面入手,一种方案是在微服务各自的业务模块中实现,即在SpringBoot层实现,另外一种方案就是在Gateway层实现。

首先讲一下在SpringBoot层实现的三种方案。

一,在Controller上添加@CrossOrigin注解

这种方式适合只有一两个rest接口需要跨域或者没有网关的情况下,这种处理方式就非常简单,适合在原来基代码基础上修改,影响比较小。

<p><code class="language-java hljs"><ol class="hljs-ln"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment">/* 注解方式 */</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-meta">@CrossOrigin</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-meta">@RestController</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HandlerScanController</span> </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-meta"><span class="hljs-meta">@CrossOrigin(allowCredentials = "true", allowedHeaders = "*", methods = {RequestMethod.GET,</span></span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="6"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-meta">                RequestMethod.POST, RequestMethod.DELETE, RequestMethod.OPTIONS,</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="7"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-meta">                RequestMethod.HEAD,</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="8"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-meta">                RequestMethod.PUT,</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="9"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-meta">                RequestMethod.PATCH},</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="10"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-meta">                origins = "*")</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="11"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-meta">@PostMapping("/confirm")</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="12"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        <span class="hljs-function"><span class="hljs-keyword">public</span> Response <span class="hljs-title">handler</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> Request json)</span> </span>{</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="13"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">            <span class="hljs-keyword">return</span> (<span class="hljs-keyword">null</span>);</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="14"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">        }</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="15"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    }</div></div></li></ol></code><div class="hljs-button {2}" data-title="复制" data-report-click="{&quot;spm&quot;:&quot;1001.2101.3001.4259&quot;}" onclick="hljs.copyCode(event)"></div></p> 

二,增加WebMvcConfigurer全局配置

如果有大量的rest接口的时候,显然第一种方案已经不适合了,工作量大,也容易出错,那就通过全局配置的方式,允许SpringBoot端所有的rest接口都支持跨域访问,这个时候就需要考虑安全性的问题。

代码如下:


 
  1. @Configuration
  2. public class MyConfiguration {
  3. @Bean
  4. public WebMvcConfigurer corsConfigurer() {
  5. return new WebMvcConfigurerAdapter() {
  6. @Override
  7. public void addCorsMappings(CorsRegistry registry) {
  8. registry.addMapping( "/**").allowCredentials( true).allowedMethods( "GET");
  9. }
  10. };
  11. }
  12. }

三,结合Filter使用

这种方案的使用场景跟第二种方案类似,只不过换成使用Filter的方式实现。
在spring boot的主类中,增加一个CorsFilter


 
  1. /**
  2. * attention:简单跨域就是GET,HEAD和POST请求,但是POST请求 的"Content-Type"只能是application/x-www-form-urlencoded, multipart/form-data 或 text/plain
  3. * <p>
  4. * 反之,就是非简单跨域,此跨域有一个预检机制,说直白点,就是会发两次请求,一次OPTIONS请求,一次真正的请求
  5. */
  6. @Bean
  7. public CorsFilter corsFilter() {
  8. final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  9. final CorsConfiguration config = new CorsConfiguration();
  10. config.setAllowCredentials( true); // 允许cookies跨域
  11. config.addAllowedOrigin( "*"); // #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
  12. config.addAllowedHeader( "*"); // #允许访问的头信息,*表示全部
  13. config.setMaxAge( 18000L); // 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
  14. config.addAllowedMethod( "OPTIONS"); // 允许提交请求的方法,*表示全部允许
  15. config.addAllowedMethod( "HEAD");
  16. config.addAllowedMethod( "GET"); // 允许Get的请求方法
  17. config.addAllowedMethod( "PUT");
  18. config.addAllowedMethod( "POST");
  19. config.addAllowedMethod( "DELETE");
  20. config.addAllowedMethod( "PATCH");
  21. source.registerCorsConfiguration( "/**", config);
  22. return new CorsFilter(source);
  23. }

以上这种方案如果微服务多的话,需要在每个服务的主类上都加上这么段代码,增加了维护量。

以上三种方案都是在SpringBoot的基础上实现的解决方案,在模块较多或者接口较多的情况下不易维护。

既然SpringCloud自带Gateway,下面就讲讲使用Gateway的跨域解决方案。

四,在Gateway端增加CorsFilter拦截器

这种方案跟方案三有些类似,只不过是放到了Gateway端,对于有多个微服务模块的情况下,就大大减少了SpringBoot模块端的代码量,让各个模块更集中精力做业务逻辑实现。这个方案只需要在Gateway里添加Filter代码类即可。


 
  1. public class CorsWebFilter implements WebFilter {
  2. private static final String ALL = "*";
  3. private static final String MAX_AGE = "18000L";
  4. @Override
  5. public Mono<Void> filter(ServerWebExchange ctx, WebFilterChain chain) {
  6. ServerHttpRequest request = ctx.getRequest();
  7. String path = request.getPath().value();
  8. ServerHttpResponse response = ctx.getResponse();
  9. if ( "/favicon.ico".equals(path)) {
  10. response.setStatusCode(HttpStatus.OK);
  11. return Mono.empty();
  12. }
  13. if (!CorsUtils.isCorsRequest(request)) {
  14. return chain.filter(ctx);
  15. }
  16. HttpHeaders requestHeaders = request.getHeaders();
  17. HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
  18. HttpHeaders headers = response.getHeaders();
  19. headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
  20. headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
  21. if (requestMethod != null) {
  22. headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
  23. }
  24. headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
  25. headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
  26. headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
  27. if (request.getMethod() == HttpMethod.OPTIONS) {
  28. response.setStatusCode(HttpStatus.OK);
  29. return Mono.empty();
  30. }
  31. return chain.filter(ctx);
  32. }
  33. }

五,修改Gateway配置文件

在仔细阅读过Gateway的文档你就会发现,原来CorsFilter早已经在Gateway里了,不需要自己写代码实现,而且更灵活,修改配置文件即可,结合配置中心使用,可以实现动态修改。

application.yml


 
  1. spring:
  2. cloud:
  3. gateway:
  4. globalcors:
  5. corsConfigurations:
  6. '[/**]':
  7. allowedOrigins: "http://domain.com"
  8. allowedMethods:
  9. - GET
  10. - POST
上一篇:通过Nacos动态刷新Spring Cloud Gateway的路由


下一篇:引入 Gateway 网关,这些坑一定要学会避开!!!