spring cloud WebFlux gateway 解决跨域问题

2 篇文章 0 订阅
1 篇文章 0 订阅

跨域问题对于后端来说已经不是什么新鲜的事,传统gataway跨域网上有很多解决办法和实现方案比如
1.在yml中添加  

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allow-credentials: true
            allowedOrigins: "*"
            allowedMethods: "*"
            allowedHeaders: "*"

2.在代码中添加

3.nginx 配置 添加请求头解决跨域

我遇到的问题:

nginx解决跨域是可以的,但是使用方法1和2 对WebFlux 都不工作,webflux使用的是先经过WebFilter而不是GlobalFilter,我们知道跨域解决就是response header 添加Access-Control-Allow-Origin等一些控制参数 所以参考方法2
第一版WebFlux实现方法
添加 CorsFilter (可用下文中的CorsWebFilterConfiguration 代替

package web.filter.cors;

import com.baozun.bs.moncler.common.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Optional;

@Slf4j
@Component
class CorsFilter implements WebFilter, Ordered {


    @Override
    public int getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        log.info("CorsFilter filter ");
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders reqHeaders = request.getHeaders();
        URI uri = request.getURI();
        log.info("CorsFilter filter request.getMethod():{},uri:{}", request.getMethod(), uri);

        if (request.getMethod() == HttpMethod.OPTIONS) {
            do {
                if (!reqHeaders.containsKey(Constants.ORIGIN)) {
                    log.info("CorsFilter filter ORIGIN");
                    break;
                }

                String origin = reqHeaders.getFirst(Constants.ORIGIN).toString();

                URI originUri = URI.create(origin);
				
                if (uri.getScheme().equals(originUri.getScheme()) && uri.getPort() == originUri.getPort() && uri.getHost().equals(originUri.getHost()))
                    break;


                HttpHeaders respHeaders = exchange.getResponse().getHeaders();
                respHeaders.setContentLength(0);
                respHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
                exchange.getResponse().setStatusCode(HttpStatus.NO_CONTENT);

                respHeaders.set(Constants.Access_Control_Allow_Origin, "*");
                respHeaders.set(Constants.Access_Control_Allow_Credentials, "true");
                respHeaders.set(Constants.Access_Control_Allow_Methods, "GET, POST, PUT, DELETE, OPTIONS");

                if (reqHeaders.containsKey(Constants.Access_Control_Request_Headers)) {
                    String accessControlRequestHeaders = reqHeaders.getFirst(Constants.Access_Control_Request_Headers);
                    respHeaders.set(Constants.Access_Control_Allow_Headers, accessControlRequestHeaders);
                }
              
                return exchange.getResponse().setComplete();
            } while (true);

            return chain.filter(exchange);

        } else {
            if (CollectionUtils.isEmpty(exchange.getResponse().getHeaders().get(Constants.TraceId))) {
                Optional.ofNullable(MDC.get(Constants.TraceId)).ifPresent((t) -> {
                    exchange.getResponse().getHeaders().add(Constants.TraceId, t);
                });
            }
            exchange.getResponse().getHeaders().add(Constants.Access_Control_Allow_Origin, "*");
            exchange.getResponse().getHeaders().set(Constants.Access_Control_Allow_Credentials, "true");
            exchange.getResponse().getHeaders().set(Constants.Access_Control_Allow_Methods, "GET, POST, PUT, DELETE, OPTIONS");
        }

        return chain.filter(exchange);
    }


}

由于Netty的bug 请求头会重复,所以需要自己写方法去掉重复的返回头 自定义Ordered 在NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER 之后执行

package com.baozun.bs.moncler.web.filter.cors;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Optional;
import java.util.Set;

@Slf4j
@Component("corsResponseHeaderFilter")
public class CorsRemoveHeaderFilter implements GlobalFilter, Ordered {


    @Override
    public int getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 21;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Optional.ofNullable(MDC.get(Constants.TraceId)).ifPresent((t) -> {
            exchange.getResponse().getHeaders().add(Constants.TraceId, t);
        });

        return chain.filter(exchange).then(Mono.defer(() -> {
            //移除重复的返回头
            HttpHeaders rpsHeaders = exchange.getResponse().getHeaders();
            Set<String> headers =  Constants.headers();
            for (String header : headers) {
                if(rpsHeaders.containsKey(header)) {
                    String value = rpsHeaders.getFirst(header);
                    exchange.getResponse().getHeaders().remove(header);
                    if (Constants.Access_Control_Allow_Methods.equals(header)) {
                        value = Constants.ACCESS_CONTROL_ALLOW_METHODS_VALUE;
                    }
                    exchange.getResponse().getHeaders().set(header, value);
                }
            }
            return chain.filter(exchange);
        }));
    }
}
package web.filter.cors;

import java.util.HashSet;
import java.util.Set;

public class Constants {

	public static final String ORIGIN = "Origin";
	public static final String Access_Control_Allow_Origin = "Access-Control-Allow-Origin";
	public static final String Access_Control_Allow_Credentials = "Access-Control-Allow-Credentials";
	public static final String Access_Control_Allow_Methods = "Access-Control-Allow-Methods";
	public static final String Access_Control_Request_Headers = "Access-Control-Request-Headers";
	public static final String Access_Control_Allow_Headers = "Access-Control-Allow-Headers";
	public static final String TraceId = "X-B3-TraceId";
	public static final String X_FRAME_OPTIONS = "X-Frame-Options";
	public static final String X_XSS_Protection = "X-XSS-Protection";
	public static final String VARY = "Vary";
	public static final String ACCESS_CONTROL_ALLOW_METHODS_VALUE ="GET,POST,PUT,DELETE,OPTIONS";


	public static Set<String> headers = new HashSet<>();

	static {
		headers.add(Access_Control_Allow_Origin);
		headers.add(Access_Control_Allow_Credentials);
		headers.add(Access_Control_Allow_Methods);
		headers.add(Access_Control_Request_Headers);
		headers.add(Access_Control_Allow_Headers);
		headers.add(VARY);
		headers.add(TraceId);
		headers.add(X_FRAME_OPTIONS);
		headers.add(X_XSS_Protection);
	}

	public static Set<String> headers(){
		return headers;
	}




}

 以上方式完美解决了webflux getaway 跨域问题。但是美中不足由于时间紧代码有些乱。

闲下来之后查了webflux官网发现有其他好的解决方法 重写CorsWebFilter
用 CorsWebFilterConfiguration 代替CorsFilter

package web.filter.cors;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

@Component
@NoArgsConstructor
@Setter
@Getter
public class CorsWebFilterConfiguration {
    @Bean
    CorsWebFilter corsFilter() {

        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        //允许全部域名
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.setAllowedMethods(Arrays.asList(Constants.ACCESS_CONTROL_ALLOW_METHODS_VALUE.split(",")));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }

}

总结:
遇见问题可用先查看对应技术的官方文档这样可以避免一些时间的浪费,实际的使用过程中还是会遇到一些问题,比如 没有去掉多余的返回头 测试环境是好的,但是发布到 uat 又不行了,后来继续排查发现是NettyWriteResponseFilter的bug。以上是一些遇到问题的分享希望可以帮助到需要的朋友

本人公众号 会不定期推送一些文章 ,有兴趣可以关注

 

 

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kevin.dingdeyang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值