likes
comments
collection
share

SpringCloud Gateway 整合RSA对请求参数解密、响应结果加密【SpringCloud系列10】

作者站长头像
站长
· 阅读数 18

SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。

本文章是系列文章中的一篇

本文章实现的是 网关中的 参数解密、响应数据体加密功能。

SpringCloud Gateway 整合RSA对请求参数解密、响应结果加密【SpringCloud系列10】

1 集成 commons-codec

commons-codec 是Apache开源组织提供的用于摘要运算、编码解码的包。常见的编码解码工具Base64、MD5、Hex、SHA1、DES等。

   <dependency>
       <groupId>commons-codec</groupId>
       <artifactId>commons-codec</artifactId>
       <version>1.15</version>
   </dependency>

本项目中集成RSA 非对称算法,RSAUtils 工具类

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class RSAUtils {

    public static final String PUBLIC_KEY = "public_key";

    public static final String PRIVATE_KEY = "private_key";


    public static Map<String, String> generateRasKey() {
        Map<String, String> rs = new HashMap<>();
        try {
            // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
            KeyPairGenerator keyPairGen = null;
            keyPairGen = KeyPairGenerator.getInstance("RSA");
            keyPairGen.initialize(1024, new SecureRandom());
            // 生成一个密钥对,保存在keyPair中
            KeyPair keyPair = keyPairGen.generateKeyPair();
            // 得到私钥 公钥
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
            // 得到私钥字符串
            String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
            // 将公钥和私钥保存到Map
            rs.put(PUBLIC_KEY, publicKeyString);
            rs.put(PRIVATE_KEY, privateKeyString);
        } catch (Exception e) {
            throw new RuntimeException("RsaUtils 生成公钥失败...");
        }
        return rs;
    }


    public static String encrypt(String str, String publicKey) {
        try {
            //base64编码的公钥
            byte[] decoded = Base64.decodeBase64(publicKey);
            RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
            //RSA加密
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            //当长度过长的时候,需要分割后加密 117个字节
            byte[] resultBytes = getMaxResultEncrypt(str, cipher);
            return Base64.encodeBase64String(resultBytes);
        } catch (Exception e) {
            log.error("加密失败:"+e.getMessage());
            throw new RuntimeException("RsaUtils 加密失败");
        }
    }

    private static byte[] getMaxResultEncrypt(String str,  Cipher cipher) throws IllegalBlockSizeException, BadPaddingException {
        byte[] inputArray = str.getBytes(StandardCharsets.UTF_8);
        int inputLength = inputArray.length;
        log.info("{}|加密字节数|inputLength:{}",str, inputLength);
        // 最大加密字节数,超出最大字节数需要分组加密
        int MAX_ENCRYPT_BLOCK = 117;
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while (inputLength - offSet > 0) {
            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }
    public static String decrypt(String str, String privateKey) {

        try {
            //64位解码加密后的字符串
            byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
            //base64编码的私钥
            byte[] decoded = Base64.decodeBase64(privateKey);
            RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
            //RSA解密
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            return new String(cipher.doFinal(inputByte));
        } catch (Exception e) {
            log.error("解密失败:"+e.getMessage());
            throw new RuntimeException("RsaUtils 解密失败.");
        }
    }
}

然后创建一个测试类,生成一组公钥与私钥: 随机生成的公钥为:

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcGdZDJOJKjcfx0zlMJxAzcZb6Hozm51L+MCyvUGsa1jaz4NVkvKsdaVny3PcGDM/DUp6tR4rtzTLDG9QX/yQI32+L4dA9xhQIvizdQxFSwj/7rJ2ecze2MHTqRCjzhQqKuWGuf/lXGlbhXY/Uf9Nn+ZJBVsdKrXPzBPpLuadn5QIDAQAB

随机生成的私钥为:

MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJwZ1kMk4kqNx/HTOUwnEDNxlvoejObnUv4wLK9QaxrWNrPg1WS8qx1pWfLc9wYMz8NSnq1Hiu3NMsMb1Bf/JAjfb4vh0D3GFAi+LN1DEVLCP/usnZ5zN7YwdOpEKPOFCoq5Ya5/+VcaVuFdj9R/02f5kkFWx0qtc/ME+ku5p2flAgMBAAECgYAUQP3zTvvViePhf/M1QEmdLdCAZNUKDgWkrtd9am/F1vmDXq68GAa+atxIOLIMej5oLMt4gYndz6bAeKyM7dvc3dGRZbVTR5lhYVj0nlPYwky90ZxruhRuEzIBY01yXj2HWoUq/7+dSmxKOASYDW+yKIUuE/4tZhoWZR0b24t42QJBAPb1NWe/zakFzHiTTbrffv9djLgeIuqar7B5pnZRnm/53otlsnLfDOkRLgCHnOHQp/xiHDpUtbfnxBKnx5skWnMCQQCh0QGKOCXdXzXyo1srX9Ya6LEd+gNgTpXBOn1Y3WdQ1p7kNZTcZJ61XodW4tgACv24NJUmWtEKwe/9PE8SteZHAkBy8xYlsaCf4SQYp7ARoMAzSy8Z8GUeQFwwz58NCdaulmbhCbgzQeF3htibxIPglEfs8RnkiNOAw69/Y3tEmnpDAkB6/rii7OarCzGgSlaD84Z0UaY+2Mg0LcdaZjDcmP1szpVbdPa/RqPzy/QnMKlp7vDHUQCFdMYr3RmjbHHWEPkFAkEA0e7TdHheSqyAnpy8TEXMJsmMHW/37RIVtY0OeQZz9TuXG6TtsjZIna0QviCFQtxg9Zz3oRfDIoM3IrasuDFrRA==

然后保存在一个类中

public class RSAConstant {
    //私钥
    public static final String PRIVATE_KEY = "";
    //公钥
    public static final String PUBLICK_KEY = "";
}

2 网关项目中创建 RequestEncryptFilter

RequestEncryptFilter 在过滤器中获取请求的参数,解密后再将参数设置回去

import com.alibaba.cloud.commons.lang.StringUtils;
import com.biglead.common.utils.RSAUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.reflect.Field;
import java.net.URI;


@Configuration
@Slf4j
public class RequestEncryptFilter implements GlobalFilter, Ordered {


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        log.info("============================RequestEncryptFilter start===================================");


        ServerHttpRequest request = exchange.getRequest();
        URI uri = request.getURI();
        //请求方法
        HttpMethod method = request.getMethod();
        MediaType mediaType = request.getHeaders().getContentType();

        String  sign = request.getHeaders().getFirst("encrypt");
        if(StringUtils.isEmpty(sign)){
            log.info("不需要解密 ");
            return chain.filter(exchange);
        }
        log.info("需要解密数据 ");

        if (method == HttpMethod.GET) {
            //1 修改请求参数,并获取请求参数
            try {
                updateRequestParam(exchange);
            } catch (Exception e) {
                return MonoUtils.invalidUrl(exchange);
            }
        }

        if (method != HttpMethod.POST) {
            log.info("非POST请求 不需要解密 ");
            return chain.filter(exchange);
        }
        //2 获取请求体,修改请求体
        ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());

        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
            //解密请求体
            String encrypt = RSAUtils.decrypt(body, RSAConstant.PRIVATE_KEY);
            return Mono.just(encrypt);
        });

        //3 创建BodyInserter修改请求体
        BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        //4 创建CachedBodyOutputMessage并且把请求param加入
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
            ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return outputMessage.getBody();
                }
            };
            return chain.filter(exchange.mutate().request(decorator).build());
        }));

    }

    /**
     * 修改前端传的参数
     */
    private void updateRequestParam(ServerWebExchange exchange) throws NoSuchFieldException, IllegalAccessException {
        ServerHttpRequest request = exchange.getRequest();
        //请求链接
        URI uri = request.getURI();
        //请求参数
        String query = uri.getQuery();
        //判断是否有加密的参数 这里的约定是 param
        if (StringUtils.isNotBlank(query) && query.contains("param")) {
            String[] split = query.split("=");
            String paramValue = split[1];
            //解密请求参数
            String param = RSAUtils.decrypt(paramValue, RSAConstant.PRIVATE_KEY);
            //使用反射强行拿出 URI 的 query
            Field targetQuery = uri.getClass().getDeclaredField("query");
            //授权
            targetQuery.setAccessible(true);
            //重新设置参数
            targetQuery.set(uri, param);
        }
    }


    @Override
    public int getOrder() {
        return -1;
    }
}

3 ResponseEncryptFilter 响应数据加密

ResponseEncryptFilter 主要是用来实现对响应数据体的加密 ,所以这里实现的思路是 :

  • 获取响应体数据
  • 获取加密标识 encrypt
  • 加密
import com.alibaba.fastjson.JSON;
import com.biglead.common.utils.RSAUtils;
import com.google.common.base.Charsets;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
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.DefaultDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Map;
import java.util.Objects;

@Configuration
@Slf4j
public class ResponseEncryptFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("============================ResponseEncryptFilter start===================================");

        ServerHttpRequest request = exchange.getRequest();
        URI uri = request.getURI();
        String url = uri.getPath();

        HttpStatus statusCode = exchange.getResponse().getStatusCode();
        if(Objects.equals(statusCode, HttpStatus.BAD_REQUEST) || Objects.equals(statusCode, HttpStatus.TOO_MANY_REQUESTS)){
            // 如果是特殊的请求,已处理响应内容,这里不再处理
            return chain.filter(exchange);
        }

        // 根据具体业务内容,修改响应体
        return modifyResponseBody(exchange, chain);
    }

    /**
     * 修改响应体
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> modifyResponseBody(ServerWebExchange exchange, GatewayFilterChain chain)  {
        ServerHttpResponse originalResponse = exchange.getResponse();
        originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator response = buildResponse(originalResponse, bufferFactory);
        return chain.filter(exchange.mutate().response(response).build());
    }


    @Override
    public int getOrder() {
        return -1;
    }
    private ServerHttpResponseDecorator buildResponse(ServerHttpResponse originalResponse, DataBufferFactory bufferFactory) {
        return new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        DataBufferUtils.release(join);
                        // 流转为字符串
                        String responseData = new String(content, Charsets.UTF_8);
                        System.out.println(responseData);

                        Map map = JSON.parseObject(responseData);
                        //处理返回的数据
                        Object encrypt = map.get("encrypt");
                        if(encrypt!=null){
                            log.info("加密响应数据 开始 :{}",responseData);
                            //加密数据
                            responseData = RSAUtils.encrypt(responseData,RSAConstant.PUBLICK_KEY);
                            log.info("加密响应数据 完成 :{}",responseData);
                        }

                        byte[] uppedContent = responseData.getBytes(Charsets.UTF_8);
                        originalResponse.getHeaders().setContentLength(uppedContent.length);
                        return bufferFactory.wrap(uppedContent);
                    }));
                } else {
                    log.error("获取响应体数据 :"+getStatusCode());
                }
                return super.writeWith(body);
            }

            @Override
            public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
                return writeWith(Flux.from(body).flatMapSequential(p -> p));
            }
        };
    }
}

测试获取订单详情数据 - 未加密的数据 SpringCloud Gateway 整合RSA对请求参数解密、响应结果加密【SpringCloud系列10】

测试获取加密的订单数据 SpringCloud Gateway 整合RSA对请求参数解密、响应结果加密【SpringCloud系列10】 对应的订单服务中的控制器

@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {

    @Resource
    private OrderService orderService;

     @Value("${server.port}")
     private String serverPort;

    /**
     * @param id 订单id
     * @return 用户
     */
    @GetMapping(value = "/{id}")
    public OrderInfo queryById(@PathVariable("id") Long id) {
        log.info("查询订单信息 port {}",serverPort);
        return orderService.queryById(id);
    }

    /**
     * 返回加密的请求体
     * @param id
     * @return
     */
    @GetMapping(value = "/encrypt/{id}")
    public Result queryEncryptById(@PathVariable("id") Long id) {
        log.info("查询订单信息 port {}",serverPort);
        OrderInfo orderInfo = orderService.queryById(id);
        return Result.okEncryptData(orderInfo);
    }
}

在 Result.okEncryptData 中封装了加密标识

public class Result extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;
    
    public static Result okEncryptData(Object data) {
        Result r = new Result();
        r.put("data", data);
        r.put("code", 200);
        r.put("message","操作成功");
        r.put("encrypt",true);
        return r;
    }
}

本项目源码 gitee.com/android.lon… 如果有兴趣可以关注一下公众号 biglead ,每周都会有 java、Flutter、小程序、js 、英语相关的内容分享

转载自:https://juejin.cn/post/7218768330034298941
评论
请登录