SpringCloud Gateway实现请求解密和响应加密

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

前言

本文环境使用比较新的 Java 17 和 SpringBoot 3.1.5,对应到Spring的版本是 6.0.13
使用到的三方插件有:

  • lombok
  • gson
  • hutool

本文注重实现请求的解密和响应的加密,加解密使用的是 Hutool 中的工具类,加解密算法目前提供了AES的方式,其余方式也可兼容扩展。
完整代码仓库:https://gitee.com/fengsoshuai/springcloud-gateway-feng-demo

借用网关中的过滤器GlobalFilter来实现这一功能。
本文只粘贴一些重点文件内容。

正文

一、项目简介

SpringCloud Gateway实现请求解密和响应加密,web框架学习,Java特性,spring cloud,gateway,spring boot,请求解密,响应加密
在聚合项目中,有两个核心模块,feng-server提供了 rest 接口,供网关使用。
feng-gateway 是核心实现的网关项目,实现了自定义过滤器,以及增加了一些基本配置功能。本文重心是网关项目。

二、核心代码

2.1 自定义过滤器

package org.feng.fenggateway.filters;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.feng.fenggateway.config.SecureProperties;
import org.feng.fenggateway.dto.ResponseDto;
import org.feng.fenggateway.secure.SecureComponent;
import org.feng.fenggateway.secure.SecureComponentFactory;
import org.feng.fenggateway.util.GsonUtil;
import org.reactivestreams.Publisher;
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.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
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.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.Set;

/**
 * 自定义密文过滤器
 *
 * @author feng
 */
@Slf4j
@Component
public class CustomCipherTextFilter implements GlobalFilter, Ordered {

    @Resource
    private SecureProperties secureProperties;

    private SecureComponent secureComponent;

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

        // 获取请求体
        ServerHttpRequest request = exchange.getRequest();
        // 获取响应体
        ServerHttpResponse response = exchange.getResponse();
        // 请求头
        HttpHeaders headers = request.getHeaders();
        // 请求方法
        HttpMethod method = request.getMethod();

        // 满足条件,进行过滤
        if (isNeedFilterMethod(method) && isNeedFilterContentType(headers.getContentType())) {
            return DataBufferUtils.join(request.getBody())
                    .flatMap(dataBuffer -> {
                        try {
                            // 获取请求参数
                            String originalRequestBody = getOriginalRequestBody(dataBuffer);

                            // 解密请求参数
                            String decryptRequestBody = decryptRequest(originalRequestBody);

                            // 装饰新的请求体
                            ServerHttpRequestDecorator requestDecorator = serverHttpRequestDecorator(request, decryptRequestBody);

                            // 装饰新的响应体
                            ServerHttpResponseDecorator responseDecorator = serverHttpResponseDecorator(response);

                            // 使用新的请求和响应转发
                            ServerWebExchange serverWebExchange = exchange.mutate().request(requestDecorator).response(responseDecorator).build();

                            // 放行拦截
                            return chain.filter(serverWebExchange);
                        } catch (Exception e) {
                            log.error("密文过滤器加解密错误", e);
                            return Mono.empty();
                        } finally {
                            DataBufferUtils.release(dataBuffer);
                        }
                    });
        }
        return chain.filter(exchange);
    }

    private String decryptRequest(String originalRequestBody) {
        if (!secureProperties.enableDecryptRequestParam()) {
            log.info("请求参数解密,跳过");
            return originalRequestBody;
        }

        log.info("请求参数解密,原文:{}", originalRequestBody);
        String decrypted = getSecureComponent().decrypt(originalRequestBody);
        log.info("请求参数解密,明文:{}", decrypted);
        return decrypted;
    }

    private String encryptResponse(String originalResponseBody) {
        if (!secureProperties.enableEncryptResponseParam()) {
            log.info("响应结果加密,跳过");
            return originalResponseBody;
        }

        ResponseDto responseDto = GsonUtil.fromJson(originalResponseBody, ResponseDto.class);
        // 只对data字段进行加密处理
        Object data = responseDto.getData();
        if (Objects.nonNull(data)) {
            responseDto.setData(getSecureComponent().encrypt(data.toString()));
        }
        log.info("响应结果加密,原文:{}", originalResponseBody);
        String result = GsonUtil.toJson(responseDto);
        log.info("响应结果加密,密文:{}", result);
        return result;
    }

    /**
     * 获取原始的请求参数
     *
     * @param dataBuffer 数据缓冲
     * @return 原始的请求参数
     */
    private String getOriginalRequestBody(DataBuffer dataBuffer) {
        byte[] bytes = new byte[dataBuffer.readableByteCount()];
        dataBuffer.read(bytes);
        return new String(bytes, StandardCharsets.UTF_8);
    }


    private boolean isNeedFilterMethod(HttpMethod method) {
        return NEED_FILTER_METHOD_SET.contains(method);
    }

    private boolean isNeedFilterContentType(MediaType mediaType) {
        return NEED_FILTER_MEDIA_TYPE_SET.contains(mediaType) || "json".equals(mediaType.getSubtype());
    }

    private ServerHttpRequestDecorator serverHttpRequestDecorator(ServerHttpRequest originalRequest, String decryptRequestBody) {
        return new ServerHttpRequestDecorator(originalRequest) {
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                byte[] bytes = decryptRequestBody.getBytes(StandardCharsets.UTF_8);
                return Flux.just(new DefaultDataBufferFactory().wrap(bytes));
            }
        };
    }


    private ServerHttpResponseDecorator serverHttpResponseDecorator(ServerHttpResponse originalResponse) {
        DataBufferFactory dataBufferFactory = originalResponse.bufferFactory();
        return new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux<? extends DataBuffer> fluxBody) {
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] byteArray = new byte[join.readableByteCount()];
                        join.read(byteArray);
                        DataBufferUtils.release(join);

                        String originalResponseBody = new String(byteArray, StandardCharsets.UTF_8);
                        //加密
                        byte[] encryptedByteArray = encryptResponse(originalResponseBody).getBytes(StandardCharsets.UTF_8);
                        originalResponse.getHeaders().setContentLength(encryptedByteArray.length);
                        originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
                        return dataBufferFactory.wrap(encryptedByteArray);
                    }));
                }
                return super.writeWith(body);
            }

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

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.putAll(originalResponse.getHeaders());
                return headers;
            }
        };
    }


    private static final Set<HttpMethod> NEED_FILTER_METHOD_SET = Set.of(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT);
    private static final Set<MediaType> NEED_FILTER_MEDIA_TYPE_SET = Set.of(MediaType.APPLICATION_JSON);

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

    public SecureComponent getSecureComponent() {
        if (Objects.isNull(secureComponent)) {
            secureComponent = SecureComponentFactory.get(secureProperties.getAlgorithm());
        }
        return secureComponent;
    }
}

2.2 网关配置

server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: feng-server1 # 路由id,自定义,只要唯一即可
          uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/server/list/server1/** # 这个是按照路径匹配,只要以/user/开头就符合要求
        - id: feng-server2
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/server/list/server2/**

# 自定义配置
feng:
  gateway:
    secure:
      request-switch:
        enable: false
      response-switch:
        enable: true
      algorithm: aes

2.3 自定义配置类

package org.feng.fenggateway.config;

import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.feng.fenggateway.secure.SecureComponentFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.Objects;

/**
 * 加解密属性配置
 *
 * @author feng
 */
@Slf4j
@Data
@ConfigurationProperties(prefix = SecureProperties.SECURE_PROPERTIES_PREFIX)
public class SecureProperties {

    public static final String SECURE_PROPERTIES_PREFIX = "feng.gateway.secure";

    /**
     * 算法
     */
    private SymmetricAlgorithm algorithm;

    /**
     * 请求开关
     */
    private SecureSwitch requestSwitch;

    /**
     * 响应开关
     */
    private SecureSwitch responseSwitch;

    public void checkSupportedAlgorithm() {
        log.info("校验是否支持算法:{}", algorithm);
        if (Objects.isNull(algorithm)) {
            return;
        }
        boolean supportedAlgorithm = SecureComponentFactory.isSupportedAlgorithm(algorithm);
        if (!supportedAlgorithm) {
            throw new UnsupportedOperationException("不支持的算法");
        }
        log.info("校验是否支持算法:校验通过");
    }

    /**
     * 是否启用解密请求参数
     *
     * @return 默认为否,其他情况看配置
     */
    public boolean enableDecryptRequestParam() {
        if (Objects.isNull(requestSwitch)) {
            return false;
        }
        return requestSwitch.getEnable();
    }

    /**
     * 是否启用加密响应参数
     *
     * @return 默认为否,其他情况看配置
     */
    public boolean enableEncryptResponseParam() {
        if (Objects.isNull(responseSwitch)) {
            return false;
        }
        return responseSwitch.getEnable();
    }
}

2.4 加密组件接口

这个可以用来扩展支持其他加密算法,目前实现类只有AES。

package org.feng.fenggateway.secure;

import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import jakarta.annotation.PostConstruct;

/**
 * 加解密组件
 *
 * @author feng
 */
public interface SecureComponent {

    /**
     * 加密
     *
     * @param originalText 原文
     * @return 密文
     */
    String encrypt(String originalText);

    /**
     * 解密
     *
     * @param encryptedText 密文
     * @return 解密后的明文
     */
    String decrypt(String encryptedText);

    /**
     * 获取加解密算法类型
     *
     * @return 加解密算法类型
     */
    SymmetricAlgorithm getAlgorithmType();


    @PostConstruct
    default void registerToFactory() {
        SecureComponentFactory.registerBean(this);
    }
}

2.5 加密组件实现,AES算法

package org.feng.fenggateway.secure;

import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;

/**
 * AES加解密组件
 *
 * @author feng
 */
@Component
public class SecureAESComponent implements SecureComponent {

    /**
     * 生成密钥,16、24、32位都行
     */
    private final static byte[] SECURE_KEY = "r4oz0f3kfk5tgyui".getBytes(StandardCharsets.UTF_8);

    /**
     * 偏移量,必须16位
     */
    private final static String IV = "r21g95kdsd423gy6";

    private final static AES AES_INSTANCE = new AES(Mode.CTS, Padding.PKCS5Padding, SECURE_KEY, IV.getBytes(StandardCharsets.UTF_8));

    @Override
    public String encrypt(String originalText) {
        return AES_INSTANCE.encryptHex(originalText);
    }

    @Override
    public String decrypt(String encryptedText) {
        return AES_INSTANCE.decryptStr(encryptedText);
    }

    @Override
    public SymmetricAlgorithm getAlgorithmType() {
        return SymmetricAlgorithm.AES;
    }
}

2.6 启动类,校验支持的算法配置

package org.feng.fenggateway;

import jakarta.annotation.Resource;
import org.feng.fenggateway.config.SecureProperties;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@ConfigurationPropertiesScan
@SpringBootApplication
public class FengGatewayApplication implements CommandLineRunner {

    @Resource
    private SecureProperties secureProperties;

    public static void main(String[] args) {
        SpringApplication.run(FengGatewayApplication.class, args);
    }

    @Override
    public void run(String... args) {
        secureProperties.checkSupportedAlgorithm();
    }
}

三、请求报文示例

POST http://localhost:10010/server/list/server2/user?authorization=feng
Content-Type: application/json;charset=UTF-8

{
  "username": "fbb"
}

四、测试结果

4.1 网关项目启动时

校验结果正常:
SpringCloud Gateway实现请求解密和响应加密,web框架学习,Java特性,spring cloud,gateway,spring boot,请求解密,响应加密

4.2 发生请求时

可以看到data字段已经加密响应了。
SpringCloud Gateway实现请求解密和响应加密,web框架学习,Java特性,spring cloud,gateway,spring boot,请求解密,响应加密

请求和响应结果:
SpringCloud Gateway实现请求解密和响应加密,web框架学习,Java特性,spring cloud,gateway,spring boot,请求解密,响应加密文章来源地址https://www.toymoban.com/news/detail-739354.html

到了这里,关于SpringCloud Gateway实现请求解密和响应加密的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 记一次线上bug排查-----SpringCloud Gateway组件 请求头accept-encoding导致响应结果乱码

           基于公司的业务需求,在SpringCloud Gateway组件的基础上,写了一个转发服务,测试开发阶段运行正常,并实现初步使用。但三个月后,PostMan请求接口,返回异常,经排查,从日志中获取到转发响应的结果为乱码:        跟踪日志: 转发到目标接口,响应结果已乱码

    2024年02月04日
    浏览(52)
  • SpringCloud-Gateway实现RSA加解密

    Gateway网关作为流量的入口,有的接口可能需要对请求内容加密,返回结果加密,保证数据安全性。 一、RSA介绍         RSA主要使用大整数分解这个数学难题进行设计,巧妙地利用了数论的概念。给了RSA公钥,首先想到的攻击就是分解模数,给了的因子攻击者可以计算得到

    2024年02月16日
    浏览(52)
  • spring boot如何实现对应用系统进行请求加密、响应加密处理

    参考文档:https://blog.csdn.net/zhuocailing3390/article/details/125054315 通过实现 RequestBodyAdvice 接口,对前端请求的参数进行解密并且重新让真实结构的数据进入到Controller中; 通过实现 ResponseBodyAdvice 接口,将响应的参数进行加密,返回到前端; 扩展: 可以通过自定义注解,实现对指

    2024年02月07日
    浏览(40)
  • 【SpringCloud Gateway】SpringCloud各微服务之间用户登录信息共享的实现思路——gateway网关token校验以及向微服务发送请求携带token

            最近在学习SpringCloud项目时,想到了一些问题,各个微服务分别部署在不同的服务上,由naocs作为注册中心实现负载均衡,彼此之间通过Feign相互调用通信,信息同步并不像单体项目那样方便,传统单体项目的登录验证方式似乎在SpringCloud中不能满足项目的需求。那么

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

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

    2023年04月24日
    浏览(44)
  • springcloud gateway中打印请求参数,请求路径和返回数据

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

    2024年02月15日
    浏览(40)
  • Gateway全局异常处理及请求响应监控

    我们在上一篇文章基于压测进行Feign调优完成的服务间调用的性能调优,此时我们也关注到一个问题,如果我们统一从网关调用服务,但是网关因为某些原因报错或者没有找到服务怎么办呢? 如下所示,笔者通过网关调用 account 服务,但是 account 服务还没起来。此时请求还没

    2024年02月04日
    浏览(48)
  • SpringCloud微服务实战——搭建企业级开发框架:微服务安全加固—自定义Gateway拦截器实现防止SQL注入/XSS攻击

     SQL注入是常见的系统安全问题之一,用户通过特定方式向系统发送SQL脚本,可直接自定义操作系统数据库,如果系统没有对SQL注入进行拦截,那么用户甚至可以直接对数据库进行增删改查等操作。   XSS全称为Cross Site Script跨站点脚本攻击,和SQL注入类似,都是通过特定方

    2024年02月03日
    浏览(65)
  • 前端请求参数加密、.NET 后端解密

    本文详细介绍了前端请求参数加密、.NET 后端解密,文章较长,请各位看官耐心看完。 目录 一、前端使用“CryptoJS”,前端AES加密,.NET后端AES解密 1.1、加密解密效果图 1.2、CryptoJS介绍 1.3、准备工作:安装“CryptoJS” 1.3.1、使用npm进行安装 1.3.2、Visual Studio中安装 1.3.2.1、选择

    2024年02月08日
    浏览(74)
  • springcloud的gateway之GlobalFilter获取请求信息及requestBody

    《本文参考地址》 RequestGlobalFilter.java ResponseGlobalFilter.java RequestAndResponseGlobalFilter.java

    2024年02月16日
    浏览(32)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包