spring cloud gateway 解决跨域的问题

1. 背景

随着前端三大框架的横空出世,前后端分离已经成为最流行的编程方式,在这种开发方式下衍生出了一系列的问题,比如说:跨域等,今天我们就来讨论一下跨域解决问题。

2.跨域参数的原因

当一个请求url的协议、域名、端口三者之间任意一个与当前页面那么这个请求就是跨域请求。

3.跨域解决方案

跨域解决方案最出名的有两个jsonp,同源策略

jsonp:是利用script标签绕过同源策略,获得一个类似这样的数据,jsonpcallback是页面存在的回调方法,参数就是想得到的json。
同源策略:就是告诉浏览器某些请求的url是和自己在同一台服务器上。

4. gateway 实现跨域

由于jsonp有很多局限性和缺点,我们一般通过设置同源策略来实现跨域,那么spring cloud gateway 如何实现同源策略设置?

4.1 配置 CorsWebFilter

gateway 是采用 spring webflux 作为 web框架,替换了之前的servelet, 关于webflux的简介请看外行人都能看得懂的WebFlux,错过了血亏! , webflux提供了一个
CorsWebFilter 来设置同源策略。


    @Bean
    public CorsWebFilter corsWebFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();

        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setMaxAge(600L);

        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsWebFilter(source);
    }

但是我们配置了这个还远远达不到我们的要求,因为它会发生如下错误:
spring cloud gateway 解决跨域的问题
spring cloud gateway 解决跨域的问题
打开详情说明我们会惊讶的发现在response header里面出现了多个Access-Control-Allow-Credentials等相关信息。
找到了原因之后我们就需要写一个去重的过滤器。

4.2 写 CorsResponseHeaderFilter类

创建 CorsResponseHeaderFilter 需要继承 GlobalFilter 类, 关于 GlobalFilter 的详解请查看Spring Cloud Gateway(十一):全局过滤器GlobalFilter, CorsResponseHeaderFilter的代码如下:


import com.flow.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
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.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.ArrayList;

@Slf4j
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
    @Override
    public int getOrder() {
        // 指定此过滤器位于NettyWriteResponseFilter之后
        // 即待处理完响应体后接着处理响应头
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.defer(() -> {
            exchange.getResponse().beforeCommit(() -> {
                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));
                            }});
                        });


                log.info("处理后的数据:{}", JsonUtils.objectToString(exchange.getResponse().getHeaders().entrySet()));
                return chain.filter(exchange);
            });

            return chain.filter(exchange);
        }));
    }

}

注意: webflux的response类是 ReactorServerHttpResponse,这个类是有为提交和已提交的状态,我们如果需要修改response的内容需要在未提交的状态下修改,所以我们必须要调用beforeCommit 函数来修改。

4.3 配置 CorsResponseHeaderFilter 类

    @Bean
    public CorsResponseHeaderFilter corsResponseHeaderFilter() {
        return new CorsResponseHeaderFilter();
    }

这样就大功告成了, 来看看运行效果吧
spring cloud gateway 解决跨域的问题

上一篇:进击前端<二> 使用Vue.js开发后台管理平台登录页面


下一篇:WebForm通过webservice接口远程读写工控机视频车辆检测器配置文件的方法