spring gateway 实现接口数据加密传输

在一些对安全要求很高的系统,需要对数据进行加密传输,我们可以采用在网关加密解密数据传输提高系统安全性,加密解密算法使用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请求

服务器日志

展开阅读全文

页面更新:2024-03-01

标签:数据   公私   工具包   乱码   网关   数据传输   中文   体内   接口   权限   系统

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top