soul网关系列(十六):sign、waf插件源码解读

一、概述

  • sign插件
    • sign插件是 soul网关自带的,用来对请求进行签名认证的插件。
    • 采用Ak/SK鉴权技术方案。
    • 采用鉴权插件,责任链的模式的模式来完成。
    • 当鉴权插件开启,并配置所有接口鉴权时候生效。
  • waf插件
    • waf插件,是网关的用来对流量实现防火墙功能的核心实现。

二、sign插件

3.1 相关配置

  1. soul网关引入了soul-spring-boot-starter-plugin-sign

  2. 在soul-admin中开启sign-plugin插件

  3. Admin web端-System Manage-Authentication里添加一个AK/SK 记录
    在这里插入图片描述
    在这里插入图片描述

  4. 然后在sign插件里配置selector和rule
    在这里插入图片描述
    在这里插入图片描述

  5. 像之前一样的请求会报如下错误
    在这里插入图片描述

  6. sign生成方式

需要请求的path、header中的timestamp、以及sk。

生成方式是按照path{path}timestamp{timestamp}version{1.0.0}{sk}的方式形成字符串。如果path为/sofa/findAll,header中的timestamp是1612371127000,sk是8103F45D19464A59A0F339CBC1C4641C,
那么sign=MD5(“path/sofa/findAlltimestamp1612371127000version1.0.08103F45D19464A59A0F339CBC1C4641C”).toUpperCase()=72F2B69CBB8477B10D56EDFA666396B7。请求header中应该有如下4个信息才能过验证:
在这里插入图片描述

3.2 源码解读

/**
 * The type Default sign service.
 *
 * @author xiaoyu
 */
@Slf4j
public class DefaultSignService implements SignService {
    
    //可以自定义sign过期时间,单位为分钟
    @Value("${soul.sign.delay:5}")
    
    private int delay;
    
    @Override
    public Pair<Boolean, String> signVerify(final ServerWebExchange exchange) {
        PluginData signData = BaseDataCache.getInstance().obtainPluginData(PluginEnum.SIGN.getName());
        //需要配置 sign 插件且 sign 插件是激活状态
        if (signData != null && signData.getEnabled()) {
            final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
            assert soulContext != null;
            //执行verify方法校验签名
            return verify(soulContext, exchange);
        }
        return Pair.of(Boolean.TRUE, "");
    }
    
    private Pair<Boolean, String> verify(final SoulContext soulContext, final ServerWebExchange exchange) {
    	//任意为空,则失败
        if (StringUtils.isBlank(soulContext.getAppKey())
                || StringUtils.isBlank(soulContext.getSign())
                || StringUtils.isBlank(soulContext.getTimestamp())) {
            log.error("sign parameters are incomplete,{}", soulContext);
            return Pair.of(Boolean.FALSE, Constants.SIGN_PARAMS_ERROR);
        }
        //获取当前的时间戳
        final LocalDateTime start = DateUtils.formatLocalDateTimeFromTimestampBySystemTimezone(Long.parseLong(soulContext.getTimestamp()));
        final LocalDateTime now = LocalDateTime.now();
        final long between = DateUtils.acquireMinutesBetween(start, now);
        //跟delay做比对,timestap不能比现在早过5分钟
        if (between > delay) {
            return Pair.of(Boolean.FALSE, String.format(SoulResultEnum.SING_TIME_IS_TIMEOUT.getMsg(), delay));
        }
        return sign(soulContext, exchange);
    }
    
    /**
     * verify sign .
     *
     * @param soulContext {@linkplain SoulContext}
     * @return result : True is pass, False is not pass.
     */
    private Pair<Boolean, String> sign(final SoulContext soulContext, final ServerWebExchange exchange) {
        final AppAuthData appAuthData = SignAuthDataCache.getInstance().obtainAuthData(soulContext.getAppKey());
        //appKey是"认证管理"页面分发出去的,否者验签失败
        if (Objects.isNull(appAuthData) || !appAuthData.getEnabled()) {
            log.error("sign APP_kEY does not exist or has been disabled,{}", soulContext.getAppKey());
            return Pair.of(Boolean.FALSE, Constants.SIGN_APP_KEY_IS_NOT_EXIST);
        }
        List<AuthPathData> pathDataList = appAuthData.getPathDataList();
        if (CollectionUtils.isEmpty(pathDataList)) {
            log.error("You have not configured the sign path:{}", soulContext.getAppKey());
            return Pair.of(Boolean.FALSE, Constants.SIGN_PATH_NOT_EXIST);
        }
   		//查看path是否在配置列表中
        boolean match = pathDataList.stream().filter(AuthPathData::getEnabled)
                .anyMatch(e -> PathMatchUtils.match(e.getPath(), soulContext.getPath()));
        if (!match) {
            log.error("You have not configured the sign path:{},{}", soulContext.getAppKey(), soulContext.getRealUrl());
            return Pair.of(Boolean.FALSE, Constants.SIGN_PATH_NOT_EXIST);
        }
        // 根据之前介绍的sign生成逻辑再生成一遍签名sign
        String sigKey = SignUtils.generateSign(appAuthData.getAppSecret(), buildParamsMap(soulContext));
        boolean result = Objects.equals(sigKey, soulContext.getSign());
        //校验sign是否合法
        if (!result) {
        	/ 不同验签失败
            log.error("the SignUtils generated signature value is:{},the accepted value is:{}", sigKey, soulContext.getSign());
            return Pair.of(Boolean.FALSE, Constants.SIGN_VALUE_IS_ERROR);
        } else {
            List<AuthParamData> paramDataList = appAuthData.getParamDataList();
            if (CollectionUtils.isEmpty(paramDataList)) {
                return Pair.of(Boolean.TRUE, "");
            }
            paramDataList.stream().filter(p ->
                    ("/" + p.getAppName()).equals(soulContext.getContextPath()))
                    .map(AuthParamData::getAppParam)
                    .filter(StringUtils::isNoneBlank).findFirst()
                    .ifPresent(param -> exchange.getRequest().mutate().headers(httpHeaders -> httpHeaders.set(Constants.APP_PARAM, param)).build()
            );
        }
        return Pair.of(Boolean.TRUE, "");
    }
    
    private Map<String, String> buildParamsMap(final SoulContext dto) {
        Map<String, String> map = Maps.newHashMapWithExpectedSize(3);
        map.put(Constants.TIMESTAMP, dto.getTimestamp());
        map.put(Constants.PATH, dto.getPath());
        map.put(Constants.VERSION, "1.0.0");
        return map;
    }
}

  • 整体逻辑比较简单
  • 用时间看请求是否在效期内5分钟内
  • 用约定的签名方式同样生成出1个签名,如果和请求带的签名相同则表明验签通过。这套逻辑在sk\ak不丢失的情况下保证了请求这是合法有效的。

四、waf插件

4.1 配置

  1. 在网关的 pom.xml 文件中添加 waf 的支持。
 <!-- soul waf plugin starter-->
 <dependency>
     <groupId>org.dromara</groupId>
     <artifactId>soul-spring-boot-starter-plugin-waf</artifactId>
     <version>${project.version}</version>
 </dependency>
 <!-- soul waf plugin end-->
  1. 在 soul-admin –> 插件管理-> waf 设置为开启。
  2. 插件编辑里面新增配置模式。
{"model":"black"}  
  • 默认为黑名单模式,设置值为 mixed 则为混合模式.
  • 当 model 设置为 black 模式的时候,只有匹配的流量才会执行拒绝策略,不匹配的,直接会跳过。
  • 当 model 设置为 mixed 模式的时候,所有的流量都会通过 waf插件,针对不同的匹配流量,用户可以设置是拒绝,还是通过。
    在这里插入图片描述
  1. 配置selectorList
    在这里插入图片描述
  2. 配置ruleList
    在这里插入图片描述
  3. postman测试
    在这里插入图片描述
    可以看到waf插件已生效

4.2 源码解读

    protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
        WafConfig wafConfig = Singleton.INST.get(WafConfig.class);
        // 选择器和rule都为空时处理逻辑
        if (Objects.isNull(selector) && Objects.isNull(rule)) {
            // 如果是黑名单模式,则直接到下一个插件
            if (WafModelEnum.BLACK.getName().equals(wafConfig.getModel())) {
                return chain.execute(exchange);
            }
            // 如果是混合模式,则流量都会通过waf插件,默认都拒绝 403
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            Object error = SoulResultWrap.error(403, 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);
        }
        // 如果是拒绝策略,则获取设置的拒绝状态码,提示message直接返回
        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);
        }
        return chain.execute(exchange);
    }

五、小结

  • sign插件

    • pom引入sign插件
    • 在soul-admin中开启sign-plugin插件
    • admin端-System Manage-Authentication里添加一个AK/SK 记录
    • sign插件里配置selector和rule
    • 按规则填充headers参数,请求网关
  • waf插件

    • pom引入waf插件
    • 在soul-admin中开启waf-plugin插件,配置防火墙模式
    • 配置selector和rule
    • 请求验证
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WPF应用程序框架(WAF)v2.5.0.7源码 源码描述: WPF应用程序框架(WAF)是一个轻量级的框架,可以帮助您创建结构良好的WPF应用程序。 它支持你在申请一个分层的架构和模型-视图-ViewModel(又名MVVM, M-V-VM, PresentationModel)模式。 特点 WPF应用程序框架(WAF) ViewModel的:包含类型,帮助你实现的Model-View-ViewModel模式。 DataModel的:基类的应用,支持你的DataModel-View - ViewModel模式。 DelegateCommand:DelegateCommand允许你来处理视图比其他类别的WPF命令。 INotifyPropertyChanged的:基类实现INotifyPropertyChanged接口。实施检查中的属性名称的DEBUG模式。 WeakEvent:第一类支持的WPF WeakEvent模式,它可以帮助你避免内存泄漏。 验证:DataErrorInfoSupport类带来的IDataErrorInfo接口与DataAnnotations的验证框架。 ConverterCollection:这个集合是能够保持同步模型的ObservableCollection DataModels。 服务:显示一条消息或打开/保存文件对话框,向用户提供服务。 最近的文件:RecentFileList类提供了最近的文件列表,可以装载和存储在应用程序设置的逻辑。 单元测试扩展 例如:如果一个action结果在一个特殊的exception,可以用ExpectedException方法来测试。 PropertyChanged:提供了一个辅助方法来测试如果一个属性改变事件是当一个特定的行动提出被执行。 CanExecuteChangedEvent:一个helper方法来测试一个CanExecute改变事件是当一个特定的行动提出被执行。 v2507更新信息 图例: [b]打破变化; [O]标记为过时成员 WAF的:添加CollectionHelper.GetNextElementOrDefault方法。 InfoMan:支持创建一个新的电子邮件,并保存在发送框中。 InfoMan:新的电子邮件:选择从地址簿中的电子邮件地址。 InfoMan:显示在导航窗格中的项目数。 InfoMan:支持删除的电子邮件。 InfoMan:加入在Common.Presentation搜索盒的控制和使用,在EmailCli??ent通讯录模块。 InfoMan:设计数据添加到通讯录意见。
根据您提供的引用内容,sudo: .waf/:找不到命令 的错误信息表示在使用sudo命令时,系统无法找到名为.waf/的命令。这可能是因为您尝试以root权限执行名为.waf/的命令,但该命令不存在或未正确安装。 要解决这个问题,您可以执行以下步骤: 1. 确认您要执行的命令的完整路径。在终端中输入`which .waf/`,它将显示命令的路径。如果输出为空或显示找不到命令的错误,那么可能是您没有正确安装或配置该命令。 2. 如果您确定命令已正确安装,但仍然无法找到,请检查是否将命令所在的目录添加到sudo的环境变量中。您可以编辑sudo的配置文件,通常位于`/etc/sudoers`,并确保您的命令所在目录已添加到sudo的secure_path选项中。 3. 如果您是在虚拟机中运行命令,并且在虚拟机中安装了不同的操作系统或环境,请确保您在正确的虚拟机中执行命令,并且已经正确安装和配置了所需的软件。 请注意,根据提供的引用内容,您的问题中未提到.waf/的具体用途和目的。如果您需要更具体的帮助,请提供更多上下文和详细信息。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [为什么linux中sudo执行会“找不到命令”](https://blog.csdn.net/weixin_33964987/article/details/116694368)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [sudo python 未找到命令,sudo后不切换python环境](https://blog.csdn.net/qq_40570751/article/details/118144301)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值