如何在Spring Boot应用中基于Azure Cosmos DB实现Always Encrypted?
Spring Boot + Cosmos DB 实现类 Always Encrypted 字段级加密方案
核心说明
Always Encrypted 原是 SQL Server 的专属特性,但如果要在 Cosmos DB 中实现客户端侧敏感字段加密(即应用层加密后存入数据库,读取时解密),完全可以通过 JDBC/SDK 结合 Azure Key Vault 实现类似效果,无需依赖 Windows/Docker 环境的 SQL Server。
具体实现步骤
1. 依赖配置(Maven 示例,Gradle 可对应转换)
在 pom.xml 中添加必要依赖:
<dependencies> <!-- Azure Key Vault 密钥管理 --> <dependency> <groupId>com.azure</groupId> <artifactId>azure-security-keyvault-keys</artifactId> <version>4.8.0</version> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-identity</artifactId> <version>1.12.0</version> </dependency> <!-- Cosmos DB Java SDK --> <dependency> <groupId>com.azure</groupId> <artifactId>azure-cosmos</artifactId> <version>4.56.0</version> </dependency> <!-- 加密工具依赖 --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.77</version> </dependency> </dependencies>
2. Azure Key Vault 密钥准备
- 在 Azure 门户创建 Key Vault,生成RSA 密钥(作为密钥加密密钥 KEK,用于加密数据密钥 DEK)
- 给 Spring Boot 应用分配 Key Vault 的访问权限(托管身份或服务主体均可),确保应用具备
get、encrypt、decrypt密钥的权限
3. 客户端加密工具类实现
封装加密解密逻辑,负责从 Key Vault 获取密钥、管理数据密钥、处理字段加密:
import com.azure.security.keyvault.keys.KeyClient; import com.azure.security.keyvault.keys.cryptography.CryptographyClient; import com.azure.security.keyvault.keys.cryptography.models.DecryptResult; import com.azure.security.keyvault.keys.cryptography.models.EncryptResult; import com.azure.security.keyvault.keys.models.KeyVaultKey; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class EncryptionUtils { private final CryptographyClient cryptoClient; public EncryptionUtils(KeyClient keyClient, String keyName) { KeyVaultKey key = keyClient.getKey(keyName); this.cryptoClient = new CryptographyClient(key, keyClient.getHttpPipeline()); } // 生成并加密数据密钥(DEK) public String generateAndEncryptDEK() throws Exception { SecretKey dek = Cipher.getInstance("AES").getKeyGenerator().generateKey(); byte[] dekBytes = dek.getEncoded(); EncryptResult encryptResult = cryptoClient.encrypt("RSA-OAEP", dekBytes); return Base64.getEncoder().encodeToString(encryptResult.getCiphertext()); } // 解密数据密钥 public SecretKey decryptDEK(String encryptedDEK) throws Exception { byte[] encryptedBytes = Base64.getDecoder().decode(encryptedDEK); DecryptResult decryptResult = cryptoClient.decrypt("RSA-OAEP", encryptedBytes); return new SecretKeySpec(decryptResult.getPlaintext(), "AES"); } // 加密敏感字段(GCM模式,带IV) public String encryptField(String plainText, SecretKey dek) throws Exception { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, dek); byte[] encrypted = cipher.doFinal(plainText.getBytes()); byte[] iv = cipher.getIV(); byte[] combined = new byte[iv.length + encrypted.length]; System.arraycopy(iv, 0, combined, 0, iv.length); System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length); return Base64.getEncoder().encodeToString(combined); } // 解密敏感字段 public String decryptField(String encryptedText, SecretKey dek) throws Exception { byte[] combined = Base64.getDecoder().decode(encryptedText); byte[] iv = new byte[12]; byte[] encrypted = new byte[combined.length - iv.length]; System.arraycopy(combined, 0, iv, 0, iv.length); System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, dek, new javax.crypto.spec.GCMParameterSpec(128, iv)); byte[] plainBytes = cipher.doFinal(encrypted); return new String(plainBytes); } }
4. Spring Boot 与 Cosmos DB 集成
- 配置文件
application.properties添加连接信息:
azure.cosmos.uri=你的CosmosDB URI azure.cosmos.key=你的CosmosDB主键 azure.cosmos.database=目标数据库名 azure.keyvault.uri=你的Key Vault URI azure.keyvault.key-name=之前创建的RSA密钥名
- 实体类定义(存储加密后的字段和DEK):
import com.azure.spring.data.cosmos.core.mapping.Container; import com.azure.spring.data.cosmos.core.mapping.PartitionKey; @Container(containerName = "users") public class User { private String id; @PartitionKey private String username; private String encryptedEmail; // 加密后的邮箱 private String encryptedDEK; // 加密后的DEK // getter、setter省略 }
- Service 层业务逻辑处理:
import org.springframework.stereotype.Service; @Service public class UserService { private final UserRepository userRepository; private final EncryptionUtils encryptionUtils; public UserService(UserRepository userRepository, EncryptionUtils encryptionUtils) { this.userRepository = userRepository; this.encryptionUtils = encryptionUtils; } public User createUser(String username, String email) throws Exception { User user = new User(); user.setUsername(username); // 生成并加密DEK,存入文档 String encryptedDEK = encryptionUtils.generateAndEncryptDEK(); user.setEncryptedDEK(encryptedDEK); // 解密DEK后加密敏感字段 SecretKey dek = encryptionUtils.decryptDEK(encryptedDEK); user.setEncryptedEmail(encryptionUtils.encryptField(email, dek)); return userRepository.save(user); } public String getUserEmail(String userId) throws Exception { User user = userRepository.findById(userId).orElseThrow(); SecretKey dek = encryptionUtils.decryptDEK(user.getEncryptedDEK()); return encryptionUtils.decryptField(user.getEncryptedEmail(), dek); } }
关键注意事项
- 此方案是客户端字段级加密,和 Always Encrypted 逻辑一致:敏感数据在应用层加密,传输、存储全程密文,只有授权应用能解密
- 每个文档存储独立的加密DEK,用Key Vault的KEK加密,避免单密钥泄露影响全部数据
- 可通过 Spring AOP 封装加密解密逻辑,减少业务代码重复
内容的提问来源于stack exchange,提问作者Andy Escobar
相关产品推荐
相关产品推荐

