Skip to main content

如何使用Dotenvx保护Spring Boot REST API的字段级加密?

· 5 min read
Libing Chen
Java程序员,兼全栈、Rust和AI开发

dotenvx-spring-boot为Spring Boot应用提供了完美的配置项加密和解密支持, 保护你的配置项不被泄露,而且Dotenvx JetBrains Plugin也提供了非常好的IDE使用体验, 既保证了你的配置项安全,又保证了你的使用体验,不会降低你的工作效率。

使用Spring Boot开发REST API时,有时会涉及到一些安全级别比较高的场景,如提供用户敏感信息的REST API,会包括手机号码、身份证号码、银行卡号等, 当然这些REST API只会开放给特定的应用,而且也有注入JWT Token验证、IP来源限制等安全措施,但是安全同学还是会要求你对这些敏感信息进行字段级加密, 这样只有掌握密钥的应用,才能解密这些敏感信息,这样即便是REST API被攻击,攻击者也无法获取到敏感信息。

那么如何才能做到字段级加密呢?让我们看一下典型的做法,这里我们以抖音平台应用对接为例,进行一个说明。

从官方的文档中,我们可以看到,处于安全性的要求,出于保护购买应用的卖家信息,抖音平台要求应用开发者对卖家的一些信息进行加密处理,其中最核心的就卖家作为购买人的手机号。

商家在应用市场查询您的应用名称,选择订购即可;商家在"服务市场"下单并完成支付,开发者会在“回调地址”中接收到加密之后的订购消息。

所以当有卖家购买你开发的应用后,抖音平台会通过你在平台上配置的回调地址,发送一个POST请求,POST请求的body内容如下:

{
"msg_type":1,
"msg":"t6+nBhBrFBwqfFw6Mqn0exP03go+2d7iZLQK+lTL8Ak+SOvZyGUKmpnQJIWpmBxX08=="
}

其中msg是被加密的,这个算法也不麻烦,就是AES 256,抖音平台和你会共同约定一个appSecret的对称密钥,用于该msg的加密和解密。 同样的方式,微信、支付宝等平台好像也都采用,主要是满足安全的要求。如果第三方服务商的token信息泄露后,我们还能够通过appSecret保护用户的敏感信息。

作为普通的开发者,我们能否结合Dotenvx更优雅地来实现这个需求呢?答案是肯定的。

{
"msg_type":1,
"dotenv.public.key": "xxxxxx",
"msg": {
"shop_id": 7868,
"phone": "encrypted:U2FsdGVkX1+Iu8m7vG6k0g==",
"service_start_time": 1599727507,
"service_end_time": 1599728507
}
}

对比上述的加密类型的msg作为base64字符串,我们还是使用原生的JSON格式,但是将敏感信息字段,如phone字段,使用Dotenvx进行加密。

这样做API的同学体验马上就不一样啦,之前HTTP Client + JSON就搞定,被你这个AES 256 base64字符串一折腾,我还要调整代码,先json解析出来, 然后AES 256解密,然后再进行json解析,代码复杂度上升,关键是和一些框架对接也麻烦,如Spring的HTTP Interface等,要做很多的定制。

通过局部字段加密,整体的结构还是对的,直接JSON解析即可,代码复杂度没有上升,关键是这些敏感信息,我只有需要的时候才会解密,存入到数据库中, 当然也是加密的,我根本不会做任何调整。

回到字段加密的实现上,这个也不麻烦,有之前的双方保存AES 256密钥,调整为平台方保存应用对应的公钥,当需要进行字段加密时,使用应用的公钥进行加密即可, 应用开发者在收到JSON数据后,使用自己的私钥进行解密即可。这点上,其实平台方其实也减轻了很多的压力,要知道保存公钥和保存AES 256的对称密钥,安全级别是完全不一样的。

最后回到Spring Boot上,我如何在Spring Boot中集成Dotenvx来实现这个需求呢?其实也不麻烦,你只需要添加dotenvx-spring-boot-starter依赖,如下:


<dependency>
<groupId>org.mvnsearch</groupId>
<artifactId>dotenvx-spring-boot-starter</artifactId>
<version>0.1.3</version>
</dependency>

然后我们创建一个Jackson Module,包括DotenvxGlobalJsonSerializerDotenvxGlobalJsonDeserializer,如下:


@Configuration
public class DotenvxJacksonConfig {
@Bean
public SimpleModule dotenvxJacksonModule(@Value("${dotenvx.public.key}") String publicKey, @Value("${dotenvx.private.key}") String privateKey) {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(new DotenvxGlobalJsonSerializer(publicKey));
simpleModule.addDeserializer(String.class, new DotenvxGlobalJsonDeserializer(privateKey));
return simpleModule;
}
}

最后让我们看一下REST Controller的样例, 我们只需将私密的数据添加一个private:前缀即可,如下:

    @GetMapping("/private/info")
public Map<String, String> privateInfo() {
Map<String, String> map = new HashMap<>();
map.put("email", "private:[email protected]");
return map;
}

这样REST API就会输出字段级别的JSON数据,如下:

{
"email": "encrypted:BFpnkHlxxx"
}

借助这种非对称加密的手段,我们就可以轻松地实现字段级别的加密,保护用户的敏感信息,同时也保证了开发的效率(结构体透明)和体验,关键是安全级别也提升了(非对称 vs AES 256堆成)。 当然这里也不是完全否认加密整个结构体的方案,这种实现方式也有它的优势,如多语言的支持,AES 256的实现几乎所有语言都有,使用Dotenvx非对称的方案,可能需要引入一些第三方库。

最后说明一下:如果你要实现字段级别加密,个人觉得还是非对称方式还是非常合适的,如果你的后端是Spring Boot的话,那么JSON数据中的加密字段的自动加解密, 对Jackson的扩展性来说,简直就是小菜,新的Dotenvx JetBrains Plugin插件也增加了对JSON的支持,可以快速实现json字段的加密和解密,体验非常好。