阅读 376
Spring Cloud GateWay 解决跨域问题并兼容IE & 重复Header处理方法

Spring Cloud GateWay 解决跨域问题并兼容IE & 重复Header处理方法

Spring Cloud GateWay 解决跨域问题并兼容IE & 重复 Request Headers处理方法

一、Spring Cloud GateWay解决跨域问题并兼容IE

@Configuration
public class GlobalCorsConfig {
    private static final String MAX_AGE = "86400L";

    @Bean
    public WebFilter corsFilter() {

        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (CorsUtils.isCorsRequest(request)) {
                HttpHeaders requestHeaders = request.getHeaders();
                ServerHttpResponse response = ctx.getResponse();
                HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
                HttpHeaders headers = response.getHeaders();

                //允许所有域名进行跨域调用
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
                if (requestMethod != null) {//适配IE
                    //放行全部原始头信息
                    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders().toString().replace("[", "").replace("]", ""));
                    //允许所有请求方法跨域调用
                    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
                }
                //允许跨域发送cookie
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                //获取除[Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma]其他全部字段
                headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
                //请求有效期
                headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);

                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }
            return chain.filter(ctx);
        };
    }

}
复制代码

二、Gateway处理重复Request Headers的方法

2.1 跨域完成后,出现重复header,报出以下错误:

origin xxxx has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'xxxx, xxxx', but only one is allowed.The 'Access-Control-Allow-Origin' header contains multiple values 'xxxx,xxxx', but only one is allowed.
复制代码

2.2 处理方法:

方法一:

由于我用的Spring Cloud是较高的版本Hoxton.SR9,这个版本中有 DedupeResponseHeaderGatewayFilterFactory,贴出源码。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class DedupeResponseHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<DedupeResponseHeaderGatewayFilterFactory.Config> {
    private static final String STRATEGY_KEY = "strategy";

    public DedupeResponseHeaderGatewayFilterFactory() {
        super(DedupeResponseHeaderGatewayFilterFactory.Config.class);
    }

    public List<String> shortcutFieldOrder() {
        return Arrays.asList("name", "strategy");
    }

    public GatewayFilter apply(DedupeResponseHeaderGatewayFilterFactory.Config config) {
        return new GatewayFilter() {
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                    DedupeResponseHeaderGatewayFilterFactory.this.dedupe(exchange.getResponse().getHeaders(), config);
                }));
            }

            public String toString() {
                return GatewayToStringStyler.filterToStringCreator(DedupeResponseHeaderGatewayFilterFactory.this).append(config.getName(), config.getStrategy()).toString();
            }
        };
    }

    void dedupe(HttpHeaders headers, DedupeResponseHeaderGatewayFilterFactory.Config config) {
        String names = config.getName();
        DedupeResponseHeaderGatewayFilterFactory.Strategy strategy = config.getStrategy();
        if (headers != null && names != null && strategy != null) {
            String[] var5 = names.split(" ");
            int var6 = var5.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                String name = var5[var7];
                this.dedupe(headers, name.trim(), strategy);
            }

        }
    }

    private void dedupe(HttpHeaders headers, String name, DedupeResponseHeaderGatewayFilterFactory.Strategy strategy) {
        List<String> values = headers.get(name);
        if (values != null && values.size() > 1) {
            //过滤过程
            switch(strategy) {
            case RETAIN_FIRST:
                headers.set(name, (String)values.get(0));
                break;
            case RETAIN_LAST:
                headers.set(name, (String)values.get(values.size() - 1));
                break;
            case RETAIN_UNIQUE:
                headers.put(name, (List)values.stream().distinct().collect(Collectors.toList()));
            }

        }
    }

    public static class Config extends NameConfig {
        private DedupeResponseHeaderGatewayFilterFactory.Strategy strategy;

        public Config() {
            //默认给定过滤规则=RETAIN_FIRST
            this.strategy = DedupeResponseHeaderGatewayFilterFactory.Strategy.RETAIN_FIRST;
        }

        public DedupeResponseHeaderGatewayFilterFactory.Strategy getStrategy() {
            return this.strategy;
        }

        public DedupeResponseHeaderGatewayFilterFactory.Config setStrategy(DedupeResponseHeaderGatewayFilterFactory.Strategy strategy) {
            this.strategy = strategy;
            return this;
        }
    }

    public static enum Strategy {
        //过滤规则
        //[RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST]
        RETAIN_FIRST,
        RETAIN_LAST,
        RETAIN_UNIQUE;

        private Strategy() {
        }
    }
}

复制代码

由源码中可以看出DedupeResponseHeader的过滤规则为RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST。而且在DedupeResponseHeaderGatewayFilterFactory初始化时已经给定了默认的过滤规则为RETAIN_FIRST

RETAIN_FIRST=过滤取第一个值
RETAIN_LAST=过滤取最后一个值
RETAIN_UNIQUE=过滤取唯一的值
复制代码

DedupeResponseHeader可以配置三种规则中任一种规则,过滤规则和过滤参数以逗号,分割。

spring.cloud.gateway.default-filters[0]=DedupeResponseHeader=A B C D,[RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST]
复制代码

或在配置中,也可以省略过滤规则,DedupeResponseHeaderGatewayFilterFactory会自动给定RETAIN_FIRST为默认过滤规则。

spring.cloud.gateway.default-filters[0]=DedupeResponseHeader=A B C D
复制代码

yml配置如下:

spring:
  application:
    name: gateway-server2
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true                  # 是否开启基于服务发现的路由规则
          lower-case-service-id: true    # 是否将服务名称转小写
      #gateway跨域后 Header重复过滤,过滤规则[RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST]
      default-filters[0]: DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials Access-Control-Expose-Headers Access-Control-Allow-Methods Access-Control-Allow-Headers Content-Type Vary,RETAIN_FIRST
复制代码

方法二:

直接继承GlobalFilter, Ordered复写 filter() 过滤exchange.getResponse().getHeaders()中的headers属性

@Component
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {

    @Override
    public int getOrder() {
        // 指定位于 NettyWriteResponseFilter 处理完响应体后移除重复 CORS 响应头
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
    }

    @Override
    @SuppressWarnings("serial")
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.defer(() -> {
            exchange.getResponse().getHeaders().entrySet().stream()
                    .filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
                    .filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
                            || kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)))
                    .forEach(kv -> kv.setValue(new ArrayList<String>() {{
                        add(kv.getValue().get(0));
                    }}));
            return chain.filter(exchange);
        }));
    }
}
复制代码

三、GateWay全局过滤器向request header中添加参数

由于在项目中从nginx请求到gateway,需要将获取到的权鉴数据存到httpRequest请求体中,供下游逻辑服务使用。

废话不多说,直接上代码。

@Component
@Slf4j
public class AccessFilter implements GlobalFilter, Ordered {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestTemplate restTemplate;

    //authMap
    private JSONObject jsonMap;

    @Autowired
    private CustomConfiguration customConfiguration;

    private static boolean isContains(String container, String[] regx) {
        for (int i = 0; i < regx.length; i++) {
            if (container.indexOf(regx[i]) != -1) {
                return true;
            }
        }
        return false;
    }


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        String includes = "/public;/auth-authentication;/admin/auth-license/addCmsFiles";
        String[] excludeList = customConfiguration.getExcludeUrl().split(";");
        if (isContains(request.getURI().toString(), excludeList)) {
            return chain.filter(exchange);
        }

        //请求体内容重写
        ServerHttpRequest httpRequest = requestHeadersRePut(request, jsonMap);
        ServerWebExchange newExchage = exchange.mutate().request(httpRequest).build();
        return chain.filter(newExchage);
    }

    /**
     * auth参数存入
     * request header
     *
     * @param request
     * @param authMap
     * @return
     */
    private ServerHttpRequest requestHeadersRePut(ServerHttpRequest request, JSONObject authMap) {
        ServerHttpRequest httpRequest;
        Map<String, String> map = new HashMap<>();

        Consumer<HttpHeaders> httpHeaders = httpHeader -> {
            httpHeader.set("userId", authMap.getString("userId"));
            httpHeader.set("departmentId", authMap.getString("departmentId"));
            try {
                httpHeader.set("realName", URLEncoder.encode(authMap.getString("realName"), "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.info("{} ===>putRequestHeaders, exceptiopn: {}", this.getClass().getSimpleName(), e.toString());
                e.printStackTrace();
            }
            httpHeader.set("username", authMap.getString("username"));
            httpHeader.set("authorization", authMap.getString("authorization"));
        };
        httpRequest = request.mutate().headers(httpHeaders).build();
        return httpRequest;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
复制代码
文章分类
后端
文章标签