这是一篇由跨域问题引起的方案

1,985 阅读3分钟

背景

这篇文章的起因源于一次解决业务中心页面一次跨域请求。因为现在非常提倡业务中台,即复用服务的能力,所以在一个统一的业务中心的域名下请求不同域名中台项目的资源就非常普遍。所以这必然会引起跨域问题。

跨域

跨域实际上源于浏览器对 javascript 的一种安全限制(也被称之为同源策略)。 默认情况下, 我们只能访问同一协议、同一域名、同一端口下的资源。

实际场景

业务中心前台有两个功能,功能 A 需要请求用户中心和价格中心的后台 API 来完成;功能 B 则需要请求用户中心,政策中心、组织中心、客户中心后台的 API 来完成。

以功能 A 请求用户中心为例,则在 bussiness.center.com 发起到 user.com 域下的请求,则会出现跨域问题。

单个服务跨域的解决方案

通过 Filter 解决

以 Spring Cloud Gateway 为例,我们需要在 网关中添加跨域 filter 实现逻辑。当然也可在任何一个 Spring 5.0+ 的服务中,采用以下方式处理跨域。

  • Interface WebFilter

package org.springframework.web.server;

// 需要 spring 5.0+
public interface WebFilter {

	Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);

}

接口描述:Web请求的拦截式链式处理合同,可用于实现跨领域,与应用程序无关的需求,例如安全性,超时等。

根据 WebFilter 便有两种实现方式。

**自定义 Filter 实现 WebFilter **

@Component
public class CorsFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
        //TODO 在 response 请求头中添加允许跨域的配置,
        // 如 Access-Control-Allow-Origin
        return webFilterChain.filter(serverWebExchange);
    }
}

推荐使用 Spring 自实现的跨域过滤器 CorsWebFilter 处理跨域

通过配置 CorsConfigurationSource 的方式处理跨域

@Bean
CorsWebFilter corsWebFilter() {
    CorsConfiguration corsConfig = new CorsConfiguration();
    corsConfig.setAllowedOrigins(Arrays.asList("http://allowed-origin.com"));
    corsConfig.setMaxAge(8000L);
    corsConfig.addAllowedMethod("PUT");
    corsConfig.addAllowedHeader("Baeldung-Allowed");

    UrlBasedCorsConfigurationSource source =
            new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfig);

    return new CorsWebFilter(source);
}

  • InterFace GlobalFilter

使用 SprinCloud Gateway GlobalFilter 解决跨域的思路与 自实现 WebFiler 的思路基本一致。需要注意的地方是,关注网关各种 Filter 的执行顺序,个人推荐将配置跨域的 GlobalFilter 最先执行。

@Component
public class GlobalCorsFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {        				//TODO 在 response 请求头中添加允许跨域的配置,
        // 如 Access-Control-Allow-Origin   
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
在 Controller 上使用 @CrossOrigin 来处理跨域

可以通过注解的方式对特殊 Controller 的 API 处理跨域。

@CrossOrigin(value = { "http://allowed-origin.com" },
        allowedHeaders = { "Baeldung-Allowed" },
        maxAge = 900
)
@RestController
public class CorsOnClassController {

    @PutMapping("/cors-enabled-endpoint")
    public Mono<String> corsEnabledEndpoint() {
        // ...
    }

    @CrossOrigin({ "http://another-allowed-origin.com" })
    @PutMapping("/endpoint-with-extra-origin-allowed")
    public Mono<String> corsEnabledWithExtraAllowedOrigin() {
        // ...
    }

    // ...
}
在全局配置上启用 CORS

官方还推荐了一种通过覆盖 WebFluxConfigurer 实现的 addCorsMappings() 方法来定义全局CORS配置。需要 @EnableWebFlux 引入 Spring WebFlux 配置。

@Configuration
@EnableWebFlux
public class CorsGlobalConfiguration implements WebFluxConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry) {
        corsRegistry.addMapping("/**")
                .allowedOrigins("http://allowed-origin.com")
                .allowedMethods("PUT")
                .maxAge(3600);
    }
}

业务中心的跨域解决方案

无论是通过 Filter 的方式,添加 HttpReponse Header 中添加允许跨域请求头,还是通过重写 WebFluxConfigurer 来配置跨域。对于单个服务都是不错方案。

但对于业务中心的前台来说,配置如此多了中台项目的域名及对应的 API ,这是非常不可取的同时也非常令人抓狂。

所以建立中间层就显得尤为重要和关键。我们使用 Nginx 作为反向代理服务器解决跨域问题。

通过配置 nginx.conf, 设置 upstream 和 server 就可以很方便的解决跨域问题,此外还可以实现负载均衡,健康检查等。

upstream user {
    zone upstream_dynamic 64k;

    server backend1.example.com      weight=5;
    server backend2.example.com:8080 fail_timeout=5s slow_start=30s;
    server 192.0.2.1                 max_fails=3;
    server backend3.example.com      resolve;
    server backend4.example.com      service=http resolve;

    server backup1.example.com:8080  backup;
    server backup2.example.com:8080  backup;
}

upstream policy {
    zone upstream_dynamic 64k;

    server backend1.example.com      weight=5;
    server backend2.example.com:8080 fail_timeout=5s slow_start=30s;
    server 192.0.2.1                 max_fails=3;
    server backend3.example.com      resolve;
    server backend4.example.com      service=http resolve;

    server backup1.example.com:8080  backup;
    server backup2.example.com:8080  backup;
}

server {
    listen 80;
    server_name bussiness.center.com;
  
    location /api/user {
        proxy_pass http://user;
        health_check;
    }
  
    location /api/policy {
        proxy_pass http://policy;
        health_check;
    }
}

参考文章

www.baeldung.com/spring-webf…