springgateway实现接口数据加密传输
在一些对安全要求很高的系统,需要对数据进行加密传输,我们可以采用在网关加密解密数据传输提高系统安全性,加密解密算法使用rsa(相对性能影响较小),java代码如下import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.RSA; import com.google.common.base.Joiner; import io.netty.buffer.ByteBufAllocator; import lombok.extern.slf4j.Slf4j; import org.apache.commons.compress.utils.Lists; import org.apache.commons.lang3.StringUtils; import org.reactivestreams.Publisher; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.NettyDataBufferFactory; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.net.URI; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR; /** * @ClassName AppFilter * @Description TODO * @Author ljq * @Date 2022/12/13 16:16 * @Version 1.0 */ @Slf4j @RefreshScope @Component public class AppFilter implements GlobalFilter, Ordered { private static Joiner joiner = Joiner.on(""); @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { //如果存在appId 走应用认证接口 ServerHttpRequest request = exchange.getRequest(); String appId = request.getHeaders().getFirst("appId"); if(StringUtils.isNotBlank(appId)){ if (exchange.getRequest().getMethod().equals(HttpMethod.POST)) { String bodyStr = getBodyStr(exchange.getRequest().getBody()); log.info("加密原始数据"+bodyStr); //重新封装请求 因为请求体内容已被消费,需要重新写入 URI uri = request.getURI(); request = request.mutate().uri(uri).build(); //这块需要掉用微服务进行应用权限校验 并返回应用公私钥 这里使用hutu工具包 对数据进行rsa加解密 start String privateKey = "30820155020100300d06092a864886f70d01010105000482013f3082013b020100024100b9c7d70130df32ff4f41f79390fc085439e917a632b2707c0d3d4c8da0cedc22e55d66ddbe59287b16aa63ff3a98d23e3938da0ceabac4167204e09a3a0cd88f020301000102401f5ae821ce52cd73a3b7d98631612832b6f76d4362a9152d0abafed1a4836549c042700fc26b750a460c999c9a0107aec4cdfecde897606dcc56bb653aee1a89022100f773cf5f947e4eb442b8a2ba42929a557e2b6c48869347296fe6e5a5c7e12513022100c032ac9e7c2ca7ca817bdd8626fe25f427188c10bc42206fd3605764f767ba15022100f0653ded29219bec5b756c016f736523f132d63b8f21bd5c702deca4258e80a9022043e3625fd4c2bd3de580c81db3b63fd7bedb87d5fd796a15b5d728e78c104285022100879796fca38aadf54a0dd90959d1eef452fffe504d73dbaff887f5089e36cd19"; String publicKey = "305c300d06092a864886f70d0101010500034b003048024100b9c7d70130df32ff4f41f79390fc085439e917a632b2707c0d3d4c8da0cedc22e55d66ddbe59287b16aa63ff3a98d23e3938da0ceabac4167204e09a3a0cd88f0203010001"; RSA rsa = SecureUtil.rsa(privateKey,publicKey); bodyStr = rsa.decryptStr(bodyStr, KeyType.PrivateKey); log.info("解密数据:"+bodyStr); //这块需要掉用微服务进行应用权限校验 并返回应用公私钥 这里使用hutu工具包 对数据进行rsa加解密 end //将解密数据封装向下传递 DataBuffer bodyDataBuffer = stringBuffer(bodyStr); Flux bodyFlux = Flux.just(bodyDataBuffer); request = new ServerHttpRequestDecorator(request) { @Override public Flux getBody() { return bodyFlux; } }; //可以选择对返回体也进行加密 ResBodyEncryptDecorator responseDecorator = new ResBodyEncryptDecorator(exchange.getResponse(),exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR),rsa); ServerHttpRequest newRequest = request.mutate().build(); return chain.filter(exchange.mutate().request(newRequest).response(responseDecorator).build()); }else{ //可以将get请求也设置特定的参数读取加解密 可以照着post请求做 return AppIdInvalid(exchange); } }else{ return AppIdInvalid(exchange); } } public static class ResBodyEncryptDecorator extends ServerHttpResponseDecorator{ private DataBufferFactory bufferFactory; private String contentType; private RSA rsa; public ResBodyEncryptDecorator(ServerHttpResponse delegate,String contentType,RSA rsa) { super(delegate); this.contentType = contentType; this.bufferFactory = delegate.bufferFactory(); this.rsa = rsa; } @Override public Mono writeWith(Publisher<? extends DataBuffer> body) { log.info("得到响应体"); if (getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) { // 获取响应 ContentType // 记录 JSON 格式数据的响应体 if (!StringUtils.isEmpty(contentType) && contentType.contains(MediaType.APPLICATION_JSON_VALUE)) { Flux<? extends DataBuffer> fluxBody = Flux.from(body); // 解决返回体分段传输 return super.writeWith(fluxBody.buffer().map(dataBuffers -> { List list = Lists.newArrayList(); dataBuffers.forEach(dataBuffer -> { byte[] content = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(content); DataBufferUtils.release(dataBuffer); list.add(new String(content, Charset.forName("UTF-8"))); }); String responseData = joiner.join(list); log.info("得到响应体内容:{}", responseData.replaceAll(" ","").replaceAll(" ","")); responseData = rsa.encryptBase64(responseData, KeyType.PublicKey); return bufferFactory.wrap(responseData.getBytes()); })); } } return super.writeWith(body); } } /** * token 无效,消息返回 * @param exchange * @return */ private Mono AppIdInvalid(ServerWebExchange exchange) { ServerHttpResponse response = exchange.getResponse(); byte[] bits = "{"result":"-1","message":"应用不存在","data":null}".getBytes(StandardCharsets.UTF_8); DataBuffer buffer = response.bufferFactory().wrap(bits); response.setStatusCode(HttpStatus.UNAUTHORIZED); //指定编码,否则在浏览器中会中文乱码 response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8"); return response.writeWith(Mono.just(buffer)); } /** * 获取请求体内容 * @param body * @return */ private String getBodyStr(Flux body) { AtomicReference bodyRef = new AtomicReference<>(); body.subscribe(buffer -> { CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); DataBufferUtils.release(buffer); bodyRef.set(charBuffer.toString()); log.info(charBuffer.toString()); }); //获取request body return bodyRef.get(); } private DataBuffer stringBuffer(String value) { byte[] bytes = value.getBytes(StandardCharsets.UTF_8); NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length); buffer.write(bytes); return buffer; } public static void main(String[] args) { String privateKey = "30820155020100300d06092a864886f70d01010105000482013f3082013b020100024100b9c7d70130df32ff4f41f79390fc085439e917a632b2707c0d3d4c8da0cedc22e55d66ddbe59287b16aa63ff3a98d23e3938da0ceabac4167204e09a3a0cd88f020301000102401f5ae821ce52cd73a3b7d98631612832b6f76d4362a9152d0abafed1a4836549c042700fc26b750a460c999c9a0107aec4cdfecde897606dcc56bb653aee1a89022100f773cf5f947e4eb442b8a2ba42929a557e2b6c48869347296fe6e5a5c7e12513022100c032ac9e7c2ca7ca817bdd8626fe25f427188c10bc42206fd3605764f767ba15022100f0653ded29219bec5b756c016f736523f132d63b8f21bd5c702deca4258e80a9022043e3625fd4c2bd3de580c81db3b63fd7bedb87d5fd796a15b5d728e78c104285022100879796fca38aadf54a0dd90959d1eef452fffe504d73dbaff887f5089e36cd19"; String publicKey = "305c300d06092a864886f70d0101010500034b003048024100b9c7d70130df32ff4f41f79390fc085439e917a632b2707c0d3d4c8da0cedc22e55d66ddbe59287b16aa63ff3a98d23e3938da0ceabac4167204e09a3a0cd88f0203010001"; RSA rsa = SecureUtil.rsa(privateKey,publicKey); String data = "{ " + " "current":1, " + " "size":20, " + " "param":{ " + " "afterSale":"Y", " + " "commitTimeStart":"2011-11-11 00:00:00" " + " } " + "}"; String encrypt = rsa.encryptBase64(data, KeyType.PrivateKey); String decypt = rsa.decryptStr(encrypt, KeyType.PublicKey); System.out.println(encrypt); System.out.println(decypt); encrypt = rsa.encryptBase64(data, KeyType.PublicKey); decypt = rsa.decryptStr(encrypt, KeyType.PrivateKey); System.out.println(encrypt); System.out.println(decypt); } @Override public int getOrder() { return -2; } }
遇到的问题:
1 有可能无法获取到请求体里面的内容可以参考 https://zhuanlan.zhihu.com/p/471402045
效果:
postman请求
服务器日志
祝贺!灵丘红石塄乡上榜啦黄河新闻网大同讯(记者吴星)近日,文化和旅游部第四批全国乡村旅游重点村名单和第二批全国乡村旅游重点镇(乡)名单出炉,大同市灵丘县红石塄乡上榜。红石塄乡位于灵丘县东南部,西依太白巍山
巨变丁香湖2005年前丁香湖地区违法挖沙,使这里坑塘连片。丁香湖,曾经是沈阳的母亲河浑河的古河道。上世纪末,因违法挖沙行为泛滥,这里坑塘连片,破烂不堪,是当时沈阳的龙须沟。2002年,丁香湖
70后的女人依然美丽一晃都五十了,对我来说,年龄其实真的不是问题,很多人到了五十以后,就觉得打扮已经没有那么重要了。退休了,在家带孙子,就不再注重自己的外形。任由自己放纵,身材变了,面孔也苍老了,最后
世界多么美丽秋日生活打卡季人生在世,不会一直顺风顺水,总会坎坎坷坷,会有一些不如意,会有烦恼郁闷忧愁和不愉快。这个时候,听一曲轻快悦耳的音乐,看着和音乐一起出现的视频中的美景,心情会豁然开朗,
奋斗的青春最美丽奋斗的青春,是长风破浪会有时,直挂云帆济沧海的豪情壮志奋斗的青春,是千磨万击还坚劲,任尔东西南北风的坚韧顽强奋斗的青春,是仰天大笑出门去,我辈岂是蓬蒿人的自信难掩奋斗的青春,是雄关
张柏芝被周星驰捧红,让陈冠希毁掉青春,三胎父亲至今不敢露面文娱情故纵编辑娱情故纵前言如果提起张柏芝这位争议颇多的女星时,不知道网友们首先会想到什么?这么多年以来每当张柏芝出现的时候,关于她身上的标签和争议几乎从来都不少。从当年出道第一部戏
齐女宣姜,美丽也是一种罪恶陌上人如玉,公子世无双。每个人都渴望俊美的容颜,儒雅的外表。渴望翩翩公子,玉树临风,清扬婉兮,遗世独立。无论这样的容貌能否为人生带来益处,都是一道靓丽的风景,令人愉悦,赏心悦目。目
吾辈青年,生于青春吾愿吾亲爱之青年,生于青春死于青春,生于少年死于少年也。题记吾辈青年作为新时代青年,当担起历史责任,让青春在新时代改革开放的广阔天地中绽放,让人生在实现中国梦的奋斗追逐中展现出勇敢
约网友见面的情景永远刻进记忆里我辜负了一个美丽的女孩子上篇十二年前那时QQ盛行,经常去泡网吧。我从零六年开始接触电脑,只是看电影,听歌,不会聊天,因为不会打字,汉语拼音也不会。很多同年人都在沉迷游戏,昼夜乐在其中,废寢忘食,不去工作,
红尘心若美丽,你我皆可成诗很想为你写首小诗把心尖的喜悦给你可岁月湮灭了太多的情怀身外物都给你我更想遁入虚空感知世界又不干涉万物运行打坐闭关忘掉空间时间风花雪月与禅定相左世间世俗入乡随俗男欢女爱就得调情颇多生
3700万,再见篮网!杜兰特逐渐不耐烦,一手好牌彻底被你自己浪费12886,谁能想到,欧文被禁赛,西蒙斯高挂免战牌,小库里伤病反复,处于动荡混乱当中的篮网,可以在奇才的主场打出如此酣畅淋漓的比赛?全场比赛,杜兰特打得十分轻松,他在33分钟的出场