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

这篇具有很好参考价值的文章主要介绍了SpringCloud Gateway 整合RSA对请求参数解密、响应结果加密【SpringCloud系列10】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

gateway 参数加解密,spring cloud,gateway,java

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));
            }
        };
    }
}

复制代码

测试获取订单详情数据 - 未加密的数据 

gateway 参数加解密,spring cloud,gateway,java

测试获取加密的订单数据 

gateway 参数加解密,spring cloud,gateway,java

 对应的订单服务中的控制器

@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://www.toymoban.com/news/detail-736685.html

到了这里,关于SpringCloud Gateway 整合RSA对请求参数解密、响应结果加密【SpringCloud系列10】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringCloud Gateway获取请求响应body大小

    本文获取请求、响应body大小方法的前提 : 网关只做转发逻辑,不修改请求、相应的body内容。 SpringCloud Gateway内部的机制类似下图,HttpServer(也就是NettyServer)接收外部的请求,在Gateway内部请求将会通过HttpClient(Netty实现的客户端)发送给后端应用。 本文的body获取方式,基于

    2024年02月14日
    浏览(48)
  • SpringCloud GateWay通过过滤器GatewayFilter修改请求或响应内容

    Spring Cloud Gateway在有些场景中需要获取request body内容进行参数校验或参数修改,我们通过在GatewayFilter中获取请求内容来获取和修改请求体,下面我们就基于ServerWebExchange来实现: ServerWebExchange命名为服务网络交换器,存放着重要的请求-响应属性、请求实例和响应实例等等,有

    2024年02月16日
    浏览(54)
  • springcloud gateway中打印请求参数,请求路径和返回数据

    在平时前后端联调过程中,需要查询日志看到前端请求的接口,上送的参数,返回数据这样有利于我们定位问题;话不多说直接上代码。 在gateway模块中,新建一个filter的包,然后创建改类,即可在控制台和日志文件里面打印出请求参数,只写了常用的 post 和 get 请求的方式;

    2024年02月15日
    浏览(40)
  • springcloud gateway 配置predicates 多个path;路由请求头新增参数

    一、 在一个微服务节点的predicates下配置多个path: - Path=/test/batis/test,/test/testJmeter,具体配置如下: 二、 在请求头中新增header参数,yml文件配置如下: 三、自定义filter中新增请求头参数:

    2024年02月16日
    浏览(43)
  • SpringCloud Gateway 网关请求中body、query、header参数的获取和修改

          最近在开发中要改造一个普通SpringBoot接口服务为SpringCloud Gateway网关服务,并且需要在网关做验签,由于我们这个服务需要对外几个第三方平台提供接口,每家请求的传参形式都不同,有将签名信息放请求头、也有将签名信息放query参数、还有直接放body中的,请求头

    2024年01月23日
    浏览(74)
  • springboot 日志记录接口的请求参数和响应结果的两种方式-拦截器和切面(具体代码)

    springboot 日志记录接口的请求参数和响应结果的两种方式-拦截器和切面(具体代码) 前言:在生产中如果出现问题,我们想要查看日志,某个时间段用户调用接口的请求参数和响应的返回结果,通过日志来推测下用户当时做了什么操作。日志记录接口的请求参数和响应结果有利

    2024年02月02日
    浏览(64)
  • Springboot接口返回参数以及入参RSA加密解密

    网上有好多通过aop切面以及自定义的RSA工具类进行加密解密的方法,期中的过程繁琐也不好用,博主研究了一天从网上到了超好用的基于Springboot框架实现的接口RSA加密解密方式,通过 rsa-encrypt-body-spring-boot 实现了对Spring Boot接口返回值、参数值通过注解的方式自动加解密。

    2024年02月13日
    浏览(48)
  • python requests.get发送Http请求响应结果乱码、Postman请求结果正常

    最近在写爬虫程序,自己复制网页http请求的url、头部,使用python requests和postman分别请求,结果使用postman发送http get请求,可以得到正常的json数据,但是使用python的requests发送则接受到乱码,response.text的内容是: response.content的内容是: 十分费解,于是网上搜索了相关内容,

    2024年01月24日
    浏览(58)
  • SpringCloud Alibaba Sentinel整合GateWay

    在微服务系统中,网关提供了微服务系统的统一入口,所以我们在做限流的时候,肯定是要在网关层面做一个流量的控制,Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。 网关: Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模块,此模块中包含网关限流的

    2024年02月16日
    浏览(47)
  • SpringCloud Gateway 3.x 响应头添加 Skywalking TraceId

    在微服务架构中,一次请求可能会被多个服务处理,而每个服务又会产生相应的日志,且每个服务也会有多个实例。在这种情况下,如果系统发生异常,没有 Trace ID,那么在进行日志分析和追踪时就会非常困难,因为我们无法将所有相关的日志信息串联起来。 如果将 Trace I

    2023年04月24日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包