• Soul 学习笔记---使用 sign 插件(十八)


    参考 soul 官方文档,sign插件

    1.启动 soul-admin, 开启 sign 插件,添加 sign 插件的选择器和规则,这里和 divide 插件的一致

    2.soul-bootstrap 引入依赖,启动 soul-bootstrap,启动 soul-examples-http

    <dependency>
        <groupId>org.dromara</groupId>
        <artifactId>soul-spring-boot-starter-plugin-sign</artifactId>
        <version>${project.version}</version>
    </dependency>
    

    3.soul-admin 认证管理菜单添加一条数据

    添加完后,自动帮我们生成好了 AppKey 和秘钥。

    image-20210201224033351

    请求头不加参数,去访问就报401了

    请求头加上这些参数就能正常访问了。

    这个 sign 值的生成,我是使用了 soul 网关的 SignUtilsTest 类,这里的 signKey 是秘钥。

    不过这个有效期只有5分钟,超过5分钟,就报错了。

    我们 debug 看下 soul 是怎么使用 sign 插件的。直接看 DefaultSignService 的 signVerify 方法

            PluginData signData = BaseDataCache.getInstance().obtainPluginData(PluginEnum.SIGN.getName());
            //sign 插件开启才走执行
            if (signData != null && signData.getEnabled()) {
                final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
                assert soulContext != null;
                //从 exchange 获取header数据验证
                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);
            //这里就和当前时间比较,超时的话,就返回 false
            if (between > delay) {
                return Pair.of(Boolean.FALSE, String.format(SoulResultEnum.SING_TIME_IS_TIMEOUT.getMsg(), delay));
            }
            return sign(soulContext, exchange);
        }
    	//签名验证
        private Pair<Boolean, String> sign(final SoulContext soulContext, final ServerWebExchange exchange) {
            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);
            }
            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);
            }
       		//请求路径和 认证管理模块配的路径是否匹配
            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);
            }
            //根据header传的参数生成sign,验证生成的sign是否和head 的sign一致
            String sigKey = SignUtils.generateSign(appAuthData.getAppSecret(), buildParamsMap(soulContext));
            boolean result = Objects.equals(sigKey, soulContext.getSign());
            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, "");
                }
                //这里如果我们配置的认证名称和contextPath一致的话,会把appParam放到请求头里,这里还不知道放的目的何在。
                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, "");
        }
    

    总体来看,sign 插件这边的源码还是比较好理解的。

  • 相关阅读:
    VScode中Python的交互式命令环境使用笔记
    jmeter beanshell判断响应的json串,参数的值是否正确;
    类和类的继承 实现关系;
    类与类依赖关系,实例;
    类与类包含关系,实例;
    java中有package的编译执行;java编译乱码;
    java 类与类之间的关系,方法重写与方法重载的区别
    beanshell sampler构造响应数据;
    Scanner类及其中方法的使用;
    java 构造方法 代码块 this
  • 原文地址:https://www.cnblogs.com/fightingting/p/14468773.html
Copyright © 2020-2023  润新知