参考 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 和秘钥。
请求头不加参数,去访问就报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 插件这边的源码还是比较好理解的。