登录 soul-admin,开启 waf 插件。

官网上的这两句很重要,waf 插件源码的实现也是根据这两句话来的。

  • 当 module 设置为 black 模式的时候,只有匹配的流量才会执行拒绝策略,不匹配的,直接会跳过。
  • 当 module 设置为 mixed 模式的时候,所有的流量都会通过 waf插件,针对不同的匹配流量,用户可以设置是 拒绝,还是通过。

一开始我选的 black 模式,配置如下。

请求时,就是这样的。

接下来看下源码是怎么实现的。

waf 插件是前置插件,也就是在请求真正 url 之前进行的,在插件链上第三个执行的。执行 waf 插件时,AbstractSoulPlugin 会匹配选择器和规则,匹配成功后,在 WafPlugin 会执行 doExecute 方法。
但 waf 插件比较特殊一点是,其他插件规则匹配不成功,一般就是走下一个插件了,AbstractSoulPlugin 的 handleSelectorIsNull,handleRuleIsNull 的代码都是这么写的

	//执行下一个插件
	protected Mono<Void> handleSelectorIsNull(final String pluginName, final ServerWebExchange exchange, final SoulPluginChain chain) {
        return chain.execute(exchange);
    }

    protected Mono<Void> handleRuleIsNull(final String pluginName, final ServerWebExchange exchange, final SoulPluginChain chain) {
        return chain.execute(exchange);
    }

但 wafPlugin 重写了 handleSelectorIsNull 和 handleRuleIsNull 方法,即使匹配不到选择器和规则,只要你 waf 插件开启了,也会走到 wafPlugin 的 doExecute 方法。

    @Override
    protected Mono<Void> handleSelectorIsNull(final String pluginName, final ServerWebExchange exchange, final SoulPluginChain chain) {
        //执行 waf 插件的 doExecute
        return doExecute(exchange, chain, null, null);
    }

    @Override
    protected Mono<Void> handleRuleIsNull(final String pluginName, final ServerWebExchange exchange, final SoulPluginChain chain) {
        return doExecute(exchange, chain, null, null);
    }

waf 插件的 doExecute 方法

    protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
        //获取配置的是 black 还是 mixed,这个值是启动 soul-bootstrap 进行数据同步时,调用 WafPluginDataHandler 的 handlerPlugin 方法放进去的
        WafConfig wafConfig = Singleton.INST.get(WafConfig.class);
      	//匹配不到选择器和规则,就走这个逻辑
        if (Objects.isNull(selector) && Objects.isNull(rule)) {
            //这里就和一开始讲的官网上的说明对上了,black模式下,不匹配的直接跳过,走下一个插件
            if (WafModelEnum.BLACK.getName().equals(wafConfig.getModel())) {
                return chain.execute(exchange);
            }
            //如果是 mixed,匹配不到就是 forbidden
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            Object error = SoulResultWrap.error(HttpStatus.FORBIDDEN.value(), Constants.REJECT_MSG, null);
            return WebFluxResultUtils.result(exchange, error);
        }
        String handle = rule.getHandle();
        WafHandle wafHandle = GsonUtils.getInstance().fromJson(handle, WafHandle.class);
        if (Objects.isNull(wafHandle) || StringUtils.isBlank(wafHandle.getPermission())) {
            log.error("waf handler can not configuration:{}", handle);
            return chain.execute(exchange);
        }
        //我们在规则配置的是 reject 时,这里就按照我们配置的错误码直接返回 response
        if (WafEnum.REJECT.getName().equals(wafHandle.getPermission())) {
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            Object error = SoulResultWrap.error(Integer.parseInt(wafHandle.getStatusCode()), Constants.REJECT_MSG, null);
            return WebFluxResultUtils.result(exchange, error);
        }
        //规则配置的是 allow 的话,直接走下一个插件了,可见 allow 时,配置的状态码也没啥用。
        return chain.execute(exchange);
    }

利用网关的这个 waf 插件,我们就可以对那些非法的请求设置拦截策略。