首页 > 动态中心 > 技术文章  >  正文

如何优雅的实现 Spring Boot 接口参数加密解密?

2023-06-15 00:00:00

文章摘要:加密解密本身并不是难事,问题是在何时去处理?因为有小伙伴刚好问到这个问题,松哥就抽空撸一篇文章和大家聊聊这个话题。定义一个过滤器,将请求和响应分别拦截下来进行处理也是一个办法,这种方式虽然粗暴,但是灵活,因为可以拿到一手的请求参数和响应数据。不过 SpringMVC 中给我们提供了 ResponseBodyAdvice 和 RequestBodyAdvice,利用这两个工具可以对请求和响应进行预处

加密解密本身并不是难事,问题是在何时去处理?因为有小伙伴刚好问到这个问题,松哥就抽空撸一篇文章和大家聊聊这个话题。定义一个过滤器,将请求和响应分别拦截下来进行处理也是一个办法,这种方式虽然粗暴,但是灵活,因为可以拿到一手的请求参数和响应数据。不过 SpringMVC 中给我们提供了 ResponseBodyAdvice 和 RequestBodyAdvice,利用这两个工具可以对请求和响应进行预处理,非常方便。

所以今天这篇文章有两个目的:

  • 分享参数/响应加解密的思路。
  • 分享 ResponseBodyAdvice 和 RequestBodyAdvice 的用法。

好了,那么接下来就不废话了,我们一起来看下。

1.开发加解密 starter

为了让我们开发的这个工具更加通用,也为了复习一下自定义 Spring Boot Starter,这里我们就将这个工具做成一个 stater,以后在 Spring Boot 项目中直接引用就可以。

首先我们创建一个 Spring Boot 项目,引入 spring-boot-starter-web 依赖:

  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-web</artifactId>
  4.     <scope>provided</scope>
  5.     <version>2.4.3</version>
  6. </dependency>

因为我们这个工具是为 Web 项目开发的,以后必然使用在 Web 环境中,所以这里添加依赖时 scope 设置为 provided。

依赖添加完成后,我们先来定义一个加密工具类备用,加密这块有多种方案可以选择,对称加密、非对称加密,其中对称加密又可以使用 AES、DES、3DES 等不同算法,这里我们使用 Java 自带的 Cipher 来实现对称加密,使用 AES 算法:

  1. public class AESUtils {
  2.     private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";
  3.     // 获取 cipher
  4.     private static Cipher getCipher(byte[] key, int model) throws Exception {
  5.         SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
  6.         Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
  7.         cipher.init(model, secretKeySpec);
  8.         return cipher;
  9.     }
  10.     // AES加密
  11.     public static String encrypt(byte[] data, byte[] key) throws Exception {
  12.         Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE);
  13.         return Base64.getEncoder().encodeToString(cipher.doFinal(data));
  14.     }
  15.     // AES解密
  16.     public static byte[] decrypt(byte[] data, byte[] key) throws Exception {
  17.         Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);
  18.         return cipher.doFinal(Base64.getDecoder().decode(data));
  19.     }
  20. }

这个工具类比较简单,不需要多解释。需要说明的是,加密后的数据可能不具备可读性,因此我们一般需要对加密后的数据再使用 Base64 算法进行编码,获取可读字符串。换言之,上面的 AES 加密方法的返回值是一个 Base64 编码之后的字符串,AES 解密方法的参数也是一个 Base64 编码之后的字符串,先对该字符串进行解码,然后再解密。

接下来我们封装一个响应工具类备用,这个大家如果经常看松哥视频已经很了解了:

  1. public class RespBean {
  2.     private Integer status;
  3.     private String msg;
  4.     private Object obj;
  5.     public static RespBean build() {
  6.         return new RespBean();
  7.     }
  8.     public static RespBean ok(String msg) {
  9.         return new RespBean(200, msg, null);
  10.     }
  11.     public static RespBean ok(String msg, Object obj) {
  12.         return new RespBean(200, msg, obj);
  13.     }
  14.     public static RespBean error(String msg) {
  15.         return new RespBean(500, msg, null);
  16.     }
  17.     public static RespBean error(String msg, Object obj) {
  18.         return new RespBean(500, msg, obj);
  19.     }
  20.     private RespBean() {
  21.     }
  22.     private RespBean(Integer status, String msg, Object obj) {
  23.         this.status = status;
  24.         this.msg = msg;
  25.         this.obj = obj;
  26.     }
  27.     public Integer getStatus() {
  28.         return status;
  29.     }
  30.     public RespBean setStatus(Integer status) {
  31.         this.status = status;
  32.         return this;
  33.     }
  34.     public String getMsg() {
  35.         return msg;
  36.     }
  37.     public RespBean setMsg(String msg) {
  38.         this.msg = msg;
  39.         return this;
  40.     }
  41.     public Object getObj() {
  42.         return obj;
  43.     }
  44.     public RespBean setObj(Object obj) {
  45.         this.obj = obj;
  46.         return this;
  47.     }
  48. }

接下来我们定义两个注解 @Decrypt 和 @Encrypt:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target({ElementType.METHOD,ElementType.PARAMETER})
  3. public @interface Decrypt {
  4. }
  5. @Retention(RetentionPolicy.RUNTIME)
  6. @Target(ElementType.METHOD)
  7. public @interface Encrypt {
  8. }

这两个注解就是两个标记,在以后使用的过程中,哪个接口方法添加了 @Encrypt 注解就对哪个接口的数据加密返回,哪个接口/参数添加了 @Decrypt 注解就对哪个接口/参数进行解密。这个定义也比较简单,没啥好说的,需要注意的是 @Decrypt比 @Encrypt 多了一个使用场景就是 @Decrypt 可以用在参数上。

考虑到用户可能会自己配置加密的 key,因此我们再来定义一个 EncryptProperties 类来读取用户配置的 key:

  1. @ConfigurationProperties(prefix = "spring.encrypt")
  2. public class EncryptProperties {
  3.     private final static String DEFAULT_KEY = "www.itboyhub.com";
  4.     private String key = DEFAULT_KEY;
  5.     public String getKey() {
  6.         return key;
  7.     }
  8.     public void setKey(String key) {
  9.         this.key = key;
  10.     }
  11. }

这里我设置了默认的 key 是 www.itboyhub.com,key 是 16 位字符串,松哥这个网站地址刚好满足。以后如果用户想自己配置 key,只需要在 application.properties 中配置 spring.encrypt.key=xxx 即可。

所有准备工作做完了,接下来就该正式加解密了。

因为松哥这篇文章一个很重要的目的是想和大家分享 ResponseBodyAdvice 和 RequestBodyAdvice 的用法,RequestBodyAdvice 在做解密的时候倒是没啥问题,而 ResponseBodyAdvice 在做加密的时候则会有一些局限,不过影响不大,还是我前面说的,如果想非常灵活的掌控一切,那还是自定义过滤器吧。这里我就先用这两个工具来实现了。

另外还有一点需要注意,ResponseBodyAdvice 在你使用了 @ResponseBody 注解的时候才会生效,RequestBodyAdvice 在你使用了 @RequestBody 注解的时候才会生效,换言之,前后端都是 JSON 交互的时候,这两个才有用。不过一般来说接口加解密的场景也都是前后端分离的时候才可能有的事。

先来看接口加密:

  1. @EnableConfigurationProperties(EncryptProperties.class)
  2. @ControllerAdvice
  3. public class EncryptResponse implements ResponseBodyAdvice<RespBean> {
  4.     private ObjectMapper om = new ObjectMapper();
  5.     @Autowired
  6.     EncryptProperties encryptProperties;
  7.     @Override
  8.     public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
  9.         return returnType.hasMethodAnnotation(Encrypt.class);
  10.     }
  11.     @Override
  12.     public RespBean beforeBodyWrite(RespBean body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
  13.         byte[] keyBytes = encryptProperties.getKey().getBytes();
  14.         try {
  15.             if (body.getMsg()!=null) {
  16.                 body.setMsg(AESUtils.encrypt(body.getMsg().getBytes(),keyBytes));
  17.             }
  18.             if (body.getObj() != null) {
  19.                 body.setObj(AESUtils.encrypt(om.writeValueAsBytes(body.getObj()), keyBytes));
  20.             }
  21.         } catch (Exception e) {
  22.             e.printStackTrace();
  23.         }
  24.         return body;
  25.     }
  26. }

我们自定义 EncryptResponse 类实现 ResponseBodyAdvice接口,泛型表示接口的返回类型,这里一共要实现两个方法:

  1. supports:这个方法用来判断什么样的接口需要加密,参数 returnType 表示返回类型,我们这里的判断逻辑就是方法是否含有 @Encrypt 注解,如果有,表示该接口需要加密处理,如果没有,表示该接口不需要加密处理。
  2. beforeBodyWrite:这个方法会在数据响应之前执行,也就是我们先对响应数据进行二次处理,处理完成后,才会转成 json 返回。我们这里的处理方式很简单,RespBean 中的 status 是状态码就不用加密了,另外两个字段重新加密后重新设置值即可。
  3. 另外需要注意,自定义的 ResponseBodyAdvice 需要用 @ControllerAdvice 注解来标记。

再来看接口解密:

  1. @EnableConfigurationProperties(EncryptProperties.class)
  2. @ControllerAdvice
  3. public class DecryptRequest extends RequestBodyAdviceAdapter {
  4.     @Autowired
  5.     EncryptProperties encryptProperties;
  6.     @Override
  7.     public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
  8.         return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class);
  9.     }
  10.     @Override
  11.     public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
  12.         byte[] body = new byte[inputMessage.getBody().available()];
  13.         inputMessage.getBody().read(body);
  14.         try {
  15.             byte[] decrypt = AESUtils.decrypt(body, encryptProperties.getKey().getBytes());
  16.             final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt);
  17.             return new HttpInputMessage() {
  18.                 @Override
  19.                 public InputStream getBody() throws IOException {
  20.                     return bais;
  21.                 }
  22.                 @Override
  23.                 public HttpHeaders getHeaders() {
  24.                     return inputMessage.getHeaders();
  25.                 }
  26.             };
  27.         } catch (Exception e) {
  28.             e.printStackTrace();
  29.         }
  30.         return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
  31.     }
  32. }
  1. 首先大家注意,DecryptRequest 类我们没有直接实现 RequestBodyAdvice 接口,而是继承自 RequestBodyAdviceAdapter 类,该类是 RequestBodyAdvice 接口的子类,并且实现了接口中的一些方法,这样当我们继承自 RequestBodyAdviceAdapter 时,就只需要根据自己实际需求实现某几个方法即可。
  2. supports:该方法用来判断哪些接口需要处理接口解密,我们这里的判断逻辑是方法上或者参数上含有 @Decrypt 注解的接口,处理解密问题。
  3. beforeBodyRead:这个方法会在参数转换成具体的对象之前执行,我们先从流中加载到数据,然后对数据进行解密,解密完成后再重新构造 HttpInputMessage 对象返回。

接下来,我们再来定义一个自动化配置类,如下:

  1. @Configuration
  2. @ComponentScan("org.javaboy.encrypt.starter")
  3. public class EncryptAutoConfiguration {
  4. }

这个也没啥好说的,比较简单。

最后,resources 目录下定义 META-INF,然后再定义 spring.factories 文件,内容如下:

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.javaboy.encrypt.starter.autoconfig.EncryptAutoConfiguration

这样当项目启动时,就会自动加载该配置类。

至此,我们的 starter 就开发完成啦。

2.打包发布

我们可以将项目安装到本地仓库,也可以发布到线上供他人使用。

2.1 安装到本地仓库

安装到本地仓库比较简单,直接 mvn install,或者在 IDEA 中,点击右边的 Maven,然后双击 install,如下:

2.2 发布到线上

发不到线上我们可以使用 JitPack 来做。

首先我们在 GitHub 上创建一个仓库,将我们的代码上传上去,这个过程应该不用我多说吧。

上传成功后,点击右边的 Create a new release 按钮,发布一个正式版,如下:

 

发布成功后,打开 jitpack,输入仓库的完整路径,点击 lookup 按钮,查找到之后,再点击 Get it 按钮完成构建,如下:

构建成功后,JitPack 上会给出项目引用方式:

注意引用时将 tag 改成你具体的版本号。

至此,我们的工具就已经成功发布了!小伙伴们可以通过如下方式引用这个 starter:

  1. <dependencies>
  2.     <dependency>
  3.         <groupId>com.github.lenve</groupId>
  4.         <artifactId>encrypt-spring-boot-starter</artifactId>
  5.         <version>0.0.3</version>
  6.     </dependency>
  7. </dependencies>
  8. <repositories>
  9.     <repository>
  10.         <id>jitpack.io</id>
  11.         <url>https://jitpack.io</url>
  12.     </repository>
  13. </repositories>

3.应用

我们创建一个普通的 Spring Boot 项目,引入 web 依赖,再引入我们刚刚的 starter 依赖,如下:

  1. <dependencies>
  2.     <dependency>
  3.         <groupId>org.springframework.boot</groupId>
  4.         <artifactId>spring-boot-starter-web</artifactId>
  5.     </dependency>
  6.     <dependency>
  7.         <groupId>com.github.lenve</groupId>
  8.         <artifactId>encrypt-spring-boot-starter</artifactId>
  9.         <version>0.0.3</version>
  10.     </dependency>
  11.     <dependency>
  12.         <groupId>org.springframework.boot</groupId>
  13.         <artifactId>spring-boot-starter-test</artifactId>
  14.         <scope>test</scope>
  15.     </dependency>
  16. </dependencies>
  17. <repositories>
  18.     <repository>
  19.         <id>jitpack.io</id>
  20.         <url>https://jitpack.io</url>
  21.     </repository>
  22. </repositories>

然后再创建一个实体类备用:

  1. public class User {
  2.     private Long id;
  3.     private String username;
  4.     //省略 getter/setter
  5. }

创建两个测试接口:

  1. @RestController
  2. public class HelloController {
  3.     @GetMapping("/user")
  4.     @Encrypt
  5.     public RespBean getUser() {
  6.         User user = new User();
  7.         user.setId((long) 99);
  8.         user.setUsername("javaboy");
  9.         return RespBean.ok("ok", user);
  10.     }
  11.     @PostMapping("/user")
  12.     public RespBean addUser(@RequestBody @Decrypt User user) {
  13.         System.out.println("user = " + user);
  14.         return RespBean.ok("ok", user);
  15.     }
  16. }

第一个接口使用了 @Encrypt 注解,所以会对该接口的数据进行加密(如果不使用该注解就不加密),第二个接口使用了 @Decrypt 所以会对上传的参数进行解密,注意 @Decrypt 注解既可以放在方法上也可以放在参数上。

接下来启动项目进行测试。

首先测试 get 请求接口:

可以看到,返回的数据已经加密。

再来测试 post 请求:

可以看到,参数中的加密数据已经被还原了。

如果用户想要修改加密密钥,可以在 application.properties 中添加如下配置:

spring.encrypt.key=1234567890123456

加密数据到了前端,前端也有一些 js 工具来处理加密数据,这个松哥后面有空再和大家说说 js 的加解密。

4.小结

好啦,今天这篇文章主要是想和大家聊聊 ResponseBodyAdvice 和 RequestBodyAdvice 的用法,一些加密思路,当然 ResponseBodyAdvice 和 RequestBodyAdvice 还有很多其他的使用场景,小伙伴们可以自行探索~本文使用了对称加密中的 AES 算法,大家也可以尝试改成非对称加密。

本文转载自微信公众号「江南一点雨」,转载本文请联系江南一点雨公众号。

上一篇 : 什么是透明加密软件?透明加密技术原理和优势有哪些
  • 相关推荐
  • 2024办公必备!2024年必看的8款文件加密神器,超好用

    随着数字化办公的普及,文件加密已成为保护企业敏感信息和个人隐私的重要手段。2024年,我们为您精选了8款超好用的文件加密神器,它们不仅功能强大,而且操作简便,是办公人士的必备之选。无论是个人用户还是企业团队,都能在这些软件中找到适合自己的加密解决方案,让您的数据安全得到全方位的保护。1、洞察眼MIT系统系统采用先进的加...

    2024-10-21 08:58:14
  • 2024年超好用的企业防泄密软件分享|超实用企业防泄密软件TOP8

    随着数字化时代的到来,企业数据安全面临前所未有的挑战。2024年,我们精心挑选了超实用企业防泄密软件TOP8,旨在为企业提供全方位、高效的数据保护解决方案。这些软件不仅功能强大,而且操作简便,能够有效防止敏感数据泄露,保障企业信息安全。让我们一同探索这些防泄密神器,共同构建企业数据安全的坚固防线。1、洞察眼MIT系统系...

    2024-10-21 08:44:21
  • 如何远程控制员工的电脑?四款神器助你轻松实现!

    在快节奏的工作环境中,远程控制员工电脑已成为提升团队协作效率、确保信息安全的重要手段。然而,面对琳琅满目的远程控制软件,如何找到既高效又安全的解决方案?本文将为您推荐四款备受好评的远程控制神器,它们不仅功能强大,而且操作简便,助您轻松实现远程管理,让团队工作更加协同高效。1、洞察眼MIT系统远程控制功能:借助该系统,管...

    2024-10-19 14:57:35
  • 如何防止文档外发泄密?文档防泄密秘籍:六款热门加密软件推荐

    在数字化时代,文档的安全外发成为企业保护核心资产的重要一环。为了防止敏感信息被泄露,选择一款合适的文档加密软件至关重要。本文精心挑选了六款热门的文档加密软件,它们各具特色,旨在为您的文档安全保驾护航。无论您是大型企业还是个人用户,都能在这份秘籍中找到适合自己的加密工具,确保文档在传输和存储过程中的安全性。一、洞察眼MI...

    2024-10-19 14:22:10
  • 如何通过聊天行为审计管理员工?员工聊天行为审计:五大实用方法

    在当今信息化办公的时代,员工聊天行为审计已成为企业管理中不可或缺的一环。为了保障信息安全、提升工作效率,我们精心总结了五大实用方法,旨在帮助企业有效管理员工的聊天行为。通过这些方法,企业可以实时监控聊天内容、识别敏感信息、追溯历史记录,从而确保员工行为合规、维护企业利益。让我们一同探索,打造更加安全、高效的办公环境。一...

    2024-10-19 10:44:22
  • 屏幕监控软件有哪些?精选8款软件,实时监控你的电脑屏幕!

    在数字化办公日益普及的今天,屏幕监控软件已成为企业确保信息安全、提升管理效率的重要工具。它们能够实时监控电脑屏幕,有效防止数据泄露,规范员工行为。以下八款屏幕监控软件,凭借其强大的功能和广泛的应用场景,成为了市场上的佼佼者。1、洞察眼MIT系统系统能够实时监控局域网内计算机的桌面画面和操作行为。这意味着管理员可以实时查...

    2024-10-19 10:05:10
  • 什么是透明加密?分享几款大家知道的文档加密软件

    透明加密是一种先进的数据加密技术。它允许用户在不影响文件正常使用的前提下,自动对数据进行加密和解密。这种加密方式对用户而言是“透明”的,即用户无需感知加密和解密的过程,就能无缝地使用加密后的文件。透明加密技术通常与操作系统或应用程序紧密结合,确保数据在存储和传输过程中的安全性,同时保持用户操作习惯的连续性。1、洞察眼M...

    2024-10-19 08:40:38
  • 实时监控电脑屏幕如何做到?实时监控电脑屏幕五种攻略

    在数字化办公日益普及的今天,实时监控电脑屏幕成为了确保工作效率与信息安全的重要手段。无论是企业管理者对员工工作状态的把控,还是家长对孩子学习动态的监督,实时监控都发挥着不可或缺的作用。以下五种实时监控电脑屏幕的攻略,将为您揭示高效、安全的监控之道,助您轻松实现屏幕内容的实时掌控。一、双屏连接监控原理:利用HDMI分屏器...

    2024-10-18 11:10:47
  • 如何限制与管控员工上网行为?六款上网行为监控软件来袭!

    在当今数字化时代,员工上网行为的管理已成为企业不可忽视的重要课题。为了保障工作效率与信息安全,六款强大的上网行为监控软件应运而生。它们如同智慧之眼,洞悉着网络世界的每一个角落,帮助企业精准限制与管控员工的上网行为,确保网络资源得以合理利用,同时守护企业的数字资产安全。让我们一同探索这些软件的奥秘,共创高效、安全的网络环...

    2024-10-18 10:06:48
  • 监控电脑屏幕的软件叫什么?八款电脑屏幕监控神器,赶紧收藏!

    在数字化办公日益普及的今天,电脑屏幕监控软件成为了企业管理的重要工具。它们如同守护神一般,默默注视着每一个电脑屏幕,确保信息安全,提升工作效率。以下是八款精心挑选的电脑屏幕监控神器,它们各具特色,功能强大。无论你是需要监控员工行为,还是保护数据安全,这些软件都能为你提供强有力的支持。赶紧收藏,让它们成为你企业管理的好帮...

    2024-10-18 09:06:10

大家都在搜的词:

微信扫一扫联系售前工程师