失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Soul网关源码阅读19-解析sign插件

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

时间:2020-03-29 17:53:35

相关推荐

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

sign插件是 soul网关自带的,用来对请求进行签名认证的插件,下面来解析一下sign插件。

一、环境搭建
soul-admin

开启 sign 插件:系统管理 --> 插件管理

soul-bootstrap

soul-bootstrap/pom.xml

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

开启服务

soul-admin

soul-bootstrap

soul-examples-http

二、配置选择器和选择器规则
devide 插件

我这里通过devide插件代理http请求,所以开启的devide插件,并且soul-examples-http服务开启后,数据会自动同步

无需手动添加,如果需要自定义可以自由修改

sign 插件

选择器和选择器规则需要手动配置

配置选择器规则

配置认证信息

在soul-admin –> 认证管理,点击新增,新增一条 AK/SK

这里配置的资源路径很重要,一定要配置的和访问的路径一致,不然无法匹配到

例如:

访问的路径为:/http/order/findById

资源路径配置:/http/order/findById

查看签名信息

三、测试
生成签名:

public class Test {public static void main(String[] args) {// 时间戳Long time = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();System.out.println("时间戳:" + time);Map<String, String> map = Maps.newHashMapWithExpectedSize(2);//值应该为毫秒数的字符串形式map.put("timestamp", time.toString()); map.put("path", "/http/order/findById");map.put("version", "1.0.0");List<String> storedKeys = Arrays.stream(map.keySet().toArray(new String[]{})).sorted(Comparator.naturalOrder()).collect(Collectors.toList());final String sign = storedKeys.stream().map(key -> String.join("", key, map.get(key))).collect(Collectors.joining()).trim().concat("676E2AF3FEB740F6B6A52AC26FE190D7");// 打印sign值System.out.println("sign值:" + sign);String signMd5 = DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase();System.out.println("签名:" + signMd5);}}

时间戳:1612511976633sign值:path/http/order/findByIdtimestamp1612511976633version1.0.0676E2AF3FEB740F6B6A52AC26FE190D7签名:3FF4C53459039A6CEDB84113531ECBF2

发起请求

设置请求header

appkey: D33891FF040C415A97F15C84DAB1734C

sign: E1BA0EC113D0534981825670960FAFEB

timestamp: 1612512422109

version: 1.0.0

curl -X GET \'http://127.0.0.1:9195/http/order/findById?id=5' \-H 'appkey: D33891FF040C415A97F15C84DAB1734C' \-H 'cache-control: no-cache' \-H 'postman-token: 496221e3-df89-6c05-1c3c-eb447700c141' \-H 'sign: E1BA0EC113D0534981825670960FAFEB' \-H 'timestamp: 1612512422109' \-H 'version: 1.0.0'

返回结果:

{"id": "5","name": "hello world findById"}

timestamp 超过5分钟:

{"code": 401,"message": "The signature timestamp has exceeded 5 minutes!","data": null}

网关日志:

-02-05 16:08:16.200 INFO 50312 --- [-work-threads-4] o.d.soul.plugin.base.AbstractSoulPlugin : sign selector success match , selector name :sign-test-02-05 16:08:16.200 INFO 50312 --- [-work-threads-4] o.d.soul.plugin.base.AbstractSoulPlugin : sign rule success match , rule name :sign-rule-02-05 16:08:16.200 INFO 50312 --- [-work-threads-4] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http-02-05 16:08:16.201 INFO 50312 --- [-work-threads-4] o.d.soul.plugin.base.AbstractSoulPlugin : divide rule success match , rule name :/http/order/findById-02-05 16:08:16.201 INFO 50312 --- [-work-threads-4] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://10.7.254.31:8188/order/findById?id=5, retryTimes is 0

四、解析sign源码

1、soul-plugin-sign

org.dromara.soul.plugin.sign.SignPlugindoExecute 方法执行sign插件处理signService.signVerify(exchange) 对请求信息进行签名验证

public class SignPlugin extends AbstractSoulPlugin {.....@Overrideprotected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {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);}}

org.dromara.soul.plugin.sign.service.DefaultSignServiceverify 方法会验证请求header中的timestamp,判断是否超过5分钟sign 方法进行签名验证

@Slf4jpublic class DefaultSignService implements SignService {@Value("${soul.sign.delay:5}")private int delay;@Overridepublic Pair<Boolean, String> signVerify(final ServerWebExchange exchange) {PluginData signData = BaseDataCache.getInstance().obtainPluginData(PluginEnum.SIGN.getName());if (signData != null && signData.getEnabled()) {final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);assert soulContext != null;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);// 比较时间,判断是否超过5分钟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()));// 判断请求path 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);}// 根据请求信息生成签名signString 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, "");}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, "");}// 取出请求信息header信息,path,用于生成签名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;}}

如果觉得《Soul网关源码阅读19-解析sign插件》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。