SpringCloud实战 Gateway网关整合SpringSecurity 自定义过滤器执行多次原因及解决方案

前言

最近为了学习微服务项目的开发,我自己开发了一个分布式博客项目。

在项目的API网关中,我添加一个过滤器并将其配置到了SpringSecurity的过滤器链中,该过滤器尝试从请求头中获取并解析token,封装用户认证凭证。

最近在测试时,我偶然发现该过滤器在每次请求时都会调用两次,后来经过排查及查资料,发现是因为该过滤器被封装到了过滤器链中两次。下面分享下这个问题及解决方案,希望可以帮到有需要的人,谢谢。

bug复现

如下图,我在过滤器中添加一些打印之后,发现每个请求都走了两次该过滤器。垃圾代码大佬轻喷

不难看出 如果能够拿到token,会通过openfeign调用用户服务。这个问题还是比较严重的,因为这会给用户服务带来压力,毕竟第二次调用是完全没有必要的。

分析

因为gateway默认采用webflux,而非webmvc。所以该过滤器实现了WebFilter接口,该过滤器之所以被执行两次,是因为它被封装到了webFilter链中的两个位置。

webflux原生过滤器链(webFilters)

在SpringBoot关于webflux的自动配置类HttpHandlerAutoConfiguration中,关于HttpHandle实例的创建,是先通过WebHttpHandlerBuilder的applicationContext方法创建了一个WebHttpHandlerBuilder实例,然后再调用了其build方法进行创建。

在applicationContext方法中,就涉及到了WebFilter的收集。

可以看出这里是从容器中获取了所有WebFilter类型的bean,并将其收集到webflux的过滤器链中。

因为项目中可能以后会存在多个过滤器,所以自定义的过滤器最好还是交由容器管理,也就是添加@Component注解。那这个地方就不可避免的会被收集到webflux的过滤器链中。

SpringSecurity过滤器链(SecurityWebFilterChain)

SpringSecurity的功能实现主要是基于一系列的过滤器,在SpringSecurity的配置类中,需要向容器提供一个SecurityWebFilterChain实例,即SpringSecurity的过滤器链。构建这个对象时,一般需要进行一些自定义的过滤器的新增或替换。

这里不再赘述,如有需要,可参考本人SpringSecurity相关文章。

解决方案

webmvc

相关的解决方案 其实在webmvc中已经有了,就是这个过滤器
org.springframework.web.filter.OncePerRequestFilter

在webmvc中,如果想要保证一个过滤器只执行一次,可以继承这个过滤器,它的实现原理,其实就是为当前过滤器设置了一个执行标记。

如果没有这个标记,则会执行doFilterInternal方法,执行子类过滤器的逻辑。并且在执行结束后,还将这个标记移除了。这里注意,移除时,整个过滤器链已经执行完了,所以没有影响。

webflux

我就参考OncePerRequestFilter写了一个过滤器,思路也是通过设置标记来过滤,代码如下:

java 复制代码
/**
 * 参考 webmvc 中的 org.springframework.web.filter.OncePerRequestFilter
 * 在 ServerWebExchange 中设置已执行标记,防止过滤器被执行多次
 */
public abstract class AbstractGatewayOncePerRequestFilter implements WebFilter {

    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";

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

        //拼接过滤器执行过的标记名称
        String alreadyFilteredAttributeName = getClass().getName() + ALREADY_FILTERED_SUFFIX;

        //判断当前过滤器是否已执行过
        if (exchange.getAttribute(alreadyFilteredAttributeName) == null) {

            //当前过滤器未执行过 设置标记并执行
            exchange.getAttributes().put(alreadyFilteredAttributeName, true);

            return doFilter(exchange, chain)
                    .doOnSuccess(v -> exchange.getAttributes().remove(alreadyFilteredAttributeName));
        }

        //当前过滤器已执行过 跳过
        return chain.filter(exchange);
    }

    abstract Mono<Void> doFilter(ServerWebExchange exchange, WebFilterChain chain);
}

原本的AuthenticationTokenFilter继承AbstractGatewayOncePerRequestFilter,重写doFilter方法,具体逻辑写在这里即可。

java 复制代码
/**
 * token过滤器:解析请求头中的token 并放到上下文中 方便后面对用户登录状态进行判断
 */
@Component
public class AuthenticationTokenFilter extends AbstractGatewayOncePerRequestFilter {

    @Autowired
    private UserService userService;

    @Override
    public Mono<Void> doFilter(ServerWebExchange exchange, WebFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Token");

        if (StrUtil.isNotBlank(token)) {
            String username = AuthUtil.getLoginUsername(token);
            Result<AuthUserDto> result = userService.findByUsername(username);
            if ("0".equals(result.getCode())) {
                AuthUserDto dto = result.getData();
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(dto.getUsername(), dto.getPassword(), AuthorityUtils.NO_AUTHORITIES);
                return chain.filter(exchange).subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication));
            }
        }
        return chain.filter(exchange);
    }
}

源码

如有需要 可直接参考下面项目代码:

如有帮助 欢迎star~ 谢谢

相关推荐
Demons_kirit3 分钟前
Dubbo+Zookeeper
分布式·zookeeper·dubbo
码农liuxin2 小时前
Dubbo 与 Eureka 深度对比:服务通信与发现的核心差异与选型指南
分布式·后端·dubbo
道法自然,人法天3 小时前
微服务的认识与拆分
微服务·云原生·架构
好记性+烂笔头3 小时前
Hadoop八股
大数据·hadoop·分布式
Python数据分析与机器学习3 小时前
《基于Hadoop的出租车需求预测系统设计与实现》开题报告
大数据·hadoop·分布式·python·算法·数据挖掘·数据分析
StableAndCalm3 小时前
什么是hadoop
大数据·hadoop·分布式
麻芝汤圆3 小时前
在虚拟机上安装 Hadoop 全攻略
大数据·linux·服务器·hadoop·windows·分布式
lqlj22333 小时前
第一个Hadoop程序
大数据·hadoop·分布式
蝴蝶不愿意5 小时前
微服务拆分-拆分购物车服务
笔记·学习·微服务·云原生·架构
Bohemian6 小时前
服务稳定性建设之限流机制 学习笔记
后端·微服务·面试