Soul源码解析(19)-Soul网关sign插件源码解读

一、目标

1、解读Soul网关sign插件签名算法;

2、解读sign插件主要业务罗逻处理代码;

二、内容

2.1 背景

上一节,我们一起学习了sign插件的使用,体验了sign插件的主要功能,具体的使用流程可以参考:

Soul源码解析(18)-Soul网关sign插件使用:https://blog.csdn.net/qq_38314459/article/details/113658890

2.2 SignPlugin插件分析

SignPlugin插件继承了AbstractSoulPlugin,这里核心的方法是doExecute()方法,就一起来看一下这个方法,这里主要是调用了SignService里面的signVerify()方法进行插件验证操作,并对返回的结果进行判断:

protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
    	// 调用了SignService里面的signVerify()方法进行插件验证操作
        Pair<Boolean, String> result = signService.signVerify(exchange);
        if (!result.getLeft()) {
            Object error = SoulResultWrap.error(SoulResultEnum.SIGN_IS_NOT_PASS.getCode(), result.getRight(), null);
            return WebFluxResultUtils.result(exchange, error);
        }
        return chain.execute(exchange);
    }

SignService接口定义了一个signVerify()方法,其实现类是DefaultSignService。接下来一起看一下这个方法:

public Pair<Boolean, String> signVerify(final ServerWebExchange exchange) {
    	// 从缓存中获取sign插件配置参数
        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, "");
    }

verify()方法分析,主要是对参数进行判断以及超时的操作,如果这些都校验通过,则调用sign()方法进行逻辑判断:

private Pair<Boolean, String> verify(final SoulContext soulContext, final ServerWebExchange exchange) {
    //appkey、sign、Timestamp任一参数为空,则抛出错误信息
        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做比对,如果间隔时间超出delay的值,则需要重新验证。delay的可以配置,默认5分钟
        if (between > delay) {
            return Pair.of(Boolean.FALSE, String.format(SoulResultEnum.SING_TIME_IS_TIMEOUT.getMsg(), delay));
        }
        return sign(soulContext, exchange);
    }

sign()方法分析

private Pair<Boolean, String> sign(final SoulContext soulContext, final ServerWebExchange exchange) {
    // 从缓存中获取appKey值
        final AppAuthData appAuthData = SignAuthDataCache.getInstance().obtainAuthData(soulContext.getAppKey());
    // 如果参数为空或者是未激活状态,则抛出错误
        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);
        }
    // 获取配置的path并进行校验
        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);
        }
    // 生成signKey
        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, "");
    }

2.3 sign签名算法

sign插件的主要处理逻辑就是这些了,这里再看一下生成signKey的方法:

第一步,先构建一个Map,

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;
    }

第二步,进行Key的自然排序,然后Key,Value值拼接最后再拼接分配给你的Sk

List<String> storedKeys = Arrays.stream(params.keySet()
                .toArray(new String[]{}))
                .sorted(Comparator.naturalOrder())
                .collect(Collectors.toList());
        final String sign = storedKeys.stream()
                .filter(key -> !Objects.equals(key, Constants.SIGN))
                .map(key -> String.join("", key, params.get(key)))
                .collect(Collectors.joining()).trim()
                .concat(signKey);

第三步,Md5加密后转成大写

DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase()

总体如下所示:

public static String generateSign(final String signKey, final Map<String, String> params) {
        List<String> storedKeys = Arrays.stream(params.keySet()
                .toArray(new String[]{}))
                .sorted(Comparator.naturalOrder())
                .collect(Collectors.toList());
        final String sign = storedKeys.stream()
                .filter(key -> !Objects.equals(key, Constants.SIGN))
                .map(key -> String.join("", key, params.get(key)))
                .collect(Collectors.joining()).trim()
                .concat(signKey);
        return DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase();
    }

三、总结

今天我们一起解读了Soul网关sign插件源码,并看了相关的签名算法,总体流程并不复杂,主要关注sign和verify方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值