暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Sping-cloud-gateway加密验签可以这么玩

鲁班同学 2021-11-10
2036

 

 图片解释:

        图中api可以作为公司内部的集中入口统一做验签加解密,当然也可以公司的统一出口(假设api后面对接的是各家银行)针对对接不同的公司进行单独集中处理加解密


        这也就是网关要干的事,统一对外暴露出入口,对无关业务(验签,加解密,容错,限流)的操作进行集中开发处理(不建议在网关搞业务,不建议操作数据库)


本节要实现的目标:


        用最少的代码、配置来对接一个新的外部系统(需要验签,加解密)


现状:

        为保证数据通信安全,我们在关系到外网通信时一般要将数据进行加密,对通话进行验签,今偶然间看了一下公司的网关(对外网关,对接了很多银行),主要实现是每接一个银行都是单独写了一个类,里边封装了所有接口调用包含加解密验签类的工作,将银行方出入参封装成对象暴露给公司内部系统调用


优缺点:

        优点:实现传统,开发人员易懂,新接银行复制修改即可,能够针对不同资方分别开发,互不影响(有些银行给提供jar包里边封装了调用,大部分还是自己http调用)


        不足:网关的作用不应该关心业务代码(封装了业务字段,如果哪天要改,就得改两个系统),代码冗余度高


针对现状的思考:


        1、大部分银行都是提供接口地址直接调用,能不能通过配置实现转发,只针对不同银行做加解密验签,其它都不做


        2、针对特殊银行(给jar包调用的)特殊封装实现动态调用

针对第二种:可统一封装controller,字段包含银行标识,接口标识,通过配置映射调用,每次新增只需新增映射即可,本次不实现,只讲第一种(可包含大多数实现)


进入正题:实现思路:


        1、公司内部请求业务数据到网关

        2、网关统一拦截将业务数据按要求进行加密转发,数据流入内网,流出外网

        3、外网调用配置白名单,调用后进行拦截验签解密转发

代码实现思路:

        1、请求转发可通过gateway配置文件配置

        2、自定义过滤器解析requestBody将body数据进行加密

        3、解析ResponseBody将数据解密转发请求


代码实现:


先看一个配置图

这里主要看id是user-server的配置(gateway端口8001)


1、拦截url中带有user-info的请求转发到http://localhost:8003

        例如:请求http://localhost:8001/user-info/user会转发到http://localhost:8003/user-info/user

2、自定义ModifyRequestBody过滤器,解析body进行加密

3、自定义ModifyResponseBody过滤器,解析response进行验签解密


刚开始写的时候网上搜了一个解析body的代码,总体实现起来略显恶心,后来看了一下源码,原来源码里很多功能人家都实现好了,在这里提醒一下,开发某个功能时请一定要看看人家有没有帮你实现好,不要在闭门造车了


 简单看一下基本都有,我们这里主要用了上面两个,注意这里配置的时候我们省略了后缀GatewayFilterFactory


源码(文末)可以简单看看

public class ModifyRequestBodyGatewayFilterFactory extends
    AbstractGatewayFilterFactory<ModifyRequestBodyGatewayFilterFactory.Config> {
..........省略
@Override
@SuppressWarnings("unchecked")
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
       ......省略===================================================
Mono<?> modifiedBody = serverRequest.bodyToMono(inClass)
// .log("modify_request_mono", Level.INFO)
            .flatMap(o -> config.rewriteFunction.apply(exchange, o));
==================================================================================
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
config.getOutClass());
。。。。。。。省略
return bodyInserter.insert(outputMessage, new BodyInserterContext())
// .log("modify_request", Level.INFO)
.then(Mono.defer(() -> {
ServerHttpRequest decorator = decorate(exchange, headers,
outputMessage);
return chain
.filter(exchange.mutate().request(decorator).build());
}));
      }
};
。。。。。省略
  }
  public static class Config {
    private Class inClass;
    private Class outClass;
    private String contentType;
    private RewriteFunction rewriteFunction;
    .........省略........
  }
}

意思就是解析bady然后重新生成了一个,主要看下面注释那一行(config.rewriteFunction.apply)也就是这个重写函数,我们做的就是取重写这个函数即可:

@Component
public class GongShangEncryptFunction implements RewriteFunction<BaseRequest,String> {


public static final String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANe2bmG2t11hweL4rd48JsBdeyZfRy8phAUqhs8sBi8lHFn8L3+VDkZH35BRPZPzDJ30a2lehBZjL+FXZCWIub16baGQgFjT2Jiod3xCa0uIvFDrpS28NAMb+gcw1VAVFJL1FVHtNKk2GYLQjZUR+oX884mKbtG4e49P9lMH7Z1vAgMBAAECgYALJEAVSfO0ngz+pSuN0/uIagunUrqBhBpujeDCqJp1KuyI9U6av18qYCH6+Uc98grPycUWfyxBX8QkVng0vBgjyfzeboPUbh0MFx5DA5lJ2yMv+oPLXIEjTcA4sVUxoRXLrSSETTGIuigFuNctc4vBx755hxGn1VnBhuEYsvu6MQJBAO51Br/xw3m5trlJN0ko4P7nlziFWhB0wopCGv6tLeQYvkcw98jz+EplARNcTP/0aQrggHM5UPP7cW7j6kp+AQkCQQDnlQwAG2gOJ30cJxMlQH23NE9ju3poUzHUiJ8qOXSHZOVScYP8VWtKfFMWSiQXriIgQ34LsyDLo/k6MJ3TG+C3AkBGfwR61I+0uenCR1n34AT8dx0m0Y251br5wudWKX6qs4H1bA2lNDNQUyIJRj1hYjF3zL1M00ISj2COpwTJ9wx5AkB9Q1CnaiuhpFh29ufTOYwGocPjhVATyBRnCrNVSpiud7PXIVGsFqQfORpULyxQpr8MxpUSTQULQZmYkR19SFIHAkEA4WW+WwuP7PGbwAyjrlcJo6e24zQ0N189zCfqULWnKGTYBm5LhMyJGEmVzjsPb148PD4Z7yGdhrWQtPlfWbCfTQ==";
public static final String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXtm5htrddYcHi+K3ePCbAXXsmX0cvKYQFKobPLAYvJRxZ/C9/lQ5GR9+QUT2T8wyd9GtpXoQWYy/hV2QliLm9em2hkIBY09iYqHd8QmtLiLxQ66UtvDQDG/oHMNVQFRSS9RVR7TSpNhmC0I2VEfqF/POJim7RuHuPT/ZTB+2dbwIDAQAB";
@Override
public Publisher<String> apply(ServerWebExchange exchange, BaseRequest baseRequest) {
System.out.println("==========="+ JSON.toJSONString(baseRequest));
if (exchange.getRequest().getMethodValue().equals(HttpMethod.GET.name())){
return Mono.just(JSON.toJSONString(baseRequest));
}
try {
String key = SignUtil.getRandom();
String content = JSON.toJSONString(baseRequest);
String reqData = SignUtil.encrypt4Base64(content, key);
String randomKey = SignUtil.encryptByPublicKey4Pkcs5(key.getBytes(StandardCharsets.UTF_8), publicKey);
XdRequest xdRequest = new XdRequest();
xdRequest.setChannelId("100000");
xdRequest.setTime(String.valueOf(System.currentTimeMillis()));
xdRequest.setVersion("1.0");
xdRequest.setData(reqData);
xdRequest.setRandomKey(randomKey);
String signData = SignUtil.sign(JSON.toJSONString(xdRequest, SerializerFeature.MapSortField)
.getBytes(StandardCharsets.UTF_8), privateKey);
xdRequest.setSign(signData);
return Mono.just(JSON.toJSONString(xdRequest));
} catch (Exception e) {
e.printStackTrace();
}
return Mono.empty();
}
}

实现Config里面的RewriteFunction接口,泛型第一个参数是入参(body)类型,二个是出参类型,可根据实际情况自定义,这里拿到body去做一些加密,然后返回一个新的body即可


然后回到刚开始的yml配置图

 

rewriteFunction,outClass,inClass是源码里Config里的三个字段,对应我们开发的funcfion,出入参类型,这样及结束了requestBody解析加密的开发,即每接一个银行只需要配置相应的过滤器及函数即可


responseBody也是一样的,后面需要用到什么自带的或者模仿写一个都可以这么来了


文中源代码:https://gitee.com/carpentor/spring-cloud-example.git



公众号主要记录各种源码、面试题、微服务技术栈,帮忙关注一波,非常感谢




文章转载自鲁班同学,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论