Spring Cloud GateWay addresses cross-domain issues and is compatible with IE & Repeat Request Headers processing

Spring Cloud GateWay solves cross-domain problems and is compatible with 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();

                // Allow all domain names to make cross-domain calls
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
                if(requestMethod ! =null) {//适配IE
                    // Release all original headers
                    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders().toString().replace("["."").replace("]".""));
                    // Allow all request methods to be invoked across domains
                    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
                }
                // Allow cross-domain cookie sending
                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, "*");
                // Request validity period
                headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);

                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    returnMono.empty(); }}returnchain.filter(ctx); }; }}Copy the code

Gateway handles duplicate Request Headers

2.1 Duplicate Header occurs after cross-domain completion, and the following error is reported:

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.
Copy the code

2.2 Treatment methods:

Method one:

Because I am using Spring Cloud is higher version Hoxton SR9, with DedupeResponseHeaderGatewayFilterFactory in this version, posted on the source code.

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() {
                returnGatewayToStringStyler.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) {
            // Filter process
            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() {
            // Default given filter rule =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 {
        // Filter rules
        //[RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST]
        RETAIN_FIRST,
        RETAIN_LAST,
        RETAIN_UNIQUE;

        private Strategy(){}}}Copy the code

By the source code can be seen in the DedupeResponseHeader filtering rules for RETAIN_FIRST | RETAIN_UNIQUE | RETAIN_LAST. And upon initial DedupeResponseHeaderGatewayFilterFactory has given the default filtering rules for RETAIN_FIRST.

Tain_first = Filter for first value RETAIN_LAST= filter for last value RETAIN_UNIQUE= Filter for unique valueCopy the code

DedupeResponseHeader You can configure any of the three rules. Filter rules and filter parameters are separated by commas (,).

spring.cloud.gateway.default-filters[0]=DedupeResponseHeader=A B C D,[RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST]
Copy the code

Or in the configuration, also can omit filtering rules, DedupeResponseHeaderGatewayFilterFactory automatically given RETAIN_FIRST as the default filtering rules.

spring.cloud.gateway.default-filters[0]=DedupeResponseHeader=A B C D
Copy the code

The YML configuration is as follows:

spring:
  application:
    name: gateway-server2
  cloud: gateway: Discovery: locator: # Indicates whether to combine this parameter with the service discovery component and forward the packet to a specific service instance using the serviceId.enabled: trueWhether to enable the routing rule lower- based on service discoverycase-service-id: true# # if transfer service name lowercase gateway cross-domain Header after repeated filter, filter rules [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_FIRSTCopy the code

Method 2:

Inheriting GlobalFilter directly, Ordered override filter() filters the headers attribute in exchange.getresponse ().getheaders ()

@Component
public class CorsResponseHeaderFilter implements GlobalFilter.Ordered {

    @Override
    public int getOrder() {
        // Remove duplicate CORS headers after NettyWriteResponseFilter processes the response body
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
    }

    @Override
    @SuppressWarnings("serial")
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        returnchain.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));
                    }}));
            returnchain.filter(exchange); })); }}Copy the code

GateWay global filter adds parameters to the Request header

As nginx requests from the project to the gateway, the obtained authentication data needs to be stored in the httpRequest request body for use by downstream logical services.

Without further ado, let’s get right to the code.

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

        // Request body content to be overwritten
        ServerHttpRequest httpRequest = requestHeadersRePut(request, jsonMap);
        ServerWebExchange newExchage = exchange.mutate().request(httpRequest).build();
        return chain.filter(newExchage);
    }

    /** * auth saves 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; }}Copy the code