diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/crypto/RespBodyEncryptResponseAdvice.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/crypto/RespBodyEncryptResponseAdvice.java index d3dbf0d..ce34f85 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/crypto/RespBodyEncryptResponseAdvice.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/crypto/RespBodyEncryptResponseAdvice.java @@ -19,7 +19,7 @@ import javax.annotation.PostConstruct; import java.util.List; /** - * 对白名单接口的 CommonResult.data 做响应加密(需客户端 Header 声明)。 + * 对白名单接口的 CommonResult.data 强制做响应加密(不依赖客户端是否声明 X-Resp-Enc)。 */ @ControllerAdvice @Order(Ordered.HIGHEST_PRECEDENCE) @@ -41,7 +41,7 @@ public class RespBodyEncryptResponseAdvice implements ResponseBodyAdvice return; } support = new RespBodyEncryptSupport(properties.getPrivateKeyBase64(), objectMapper); - log.info("[resp-encrypt] 响应加密已启用,白名单={}", RespBodyEncryptSupport.PATH_SUFFIXES); + log.info("[resp-encrypt] 响应加密已启用(强制),白名单={}", RespBodyEncryptSupport.PATH_SUFFIXES); } @Override @@ -68,13 +68,6 @@ public class RespBodyEncryptResponseAdvice implements ResponseBodyAdvice return body; } - List encHeaders = request.getHeaders().get(RespBodyEncryptSupport.HEADER_RESP_ENC); - String enc = (encHeaders != null && !encHeaders.isEmpty()) ? encHeaders.get(0) : null; - if (!RespBodyEncryptSupport.wantEncrypt(enc)) { - // 旧客户端未声明加密:仍返回明文,便于过渡 - return body; - } - List ekHeaders = request.getHeaders().get(RespBodyEncryptSupport.HEADER_RESP_EK); String ek = (ekHeaders != null && !ekHeaders.isEmpty()) ? ekHeaders.get(0) : null; try { diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/crypto/RespBodyEncryptSupport.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/crypto/RespBodyEncryptSupport.java index 6820989..f060656 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/crypto/RespBodyEncryptSupport.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/crypto/RespBodyEncryptSupport.java @@ -21,7 +21,8 @@ import java.util.Map; import java.util.Set; /** - * 响应体加密:客户端用 RSA 公钥加密随机 AES Key(Header),服务端 AES-GCM 加密 data。 + * 响应体加密:客户端可用 RSA 公钥加密随机 AES Key(请求 Header),服务端 AES-GCM 加密 data。 + * 若客户端未带 Key,服务端生成 AES Key 并写入响应信封字段 {@code ek}(RSA 私钥加密,客户端公钥解密)。 */ @Slf4j public class RespBodyEncryptSupport { @@ -32,7 +33,8 @@ public class RespBodyEncryptSupport { public static final Set PATH_SUFFIXES = new HashSet<>(Arrays.asList( "/member/recent/quick-page", "/member/recent/getById", - "/member/recent/look-wechat" + "/member/recent/look-wechat", + "/member/user/page" )); private static final String RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding"; @@ -68,18 +70,14 @@ public class RespBodyEncryptSupport { return false; } - public static boolean wantEncrypt(String encHeader) { - return "1".equals(encHeader) || "true".equalsIgnoreCase(encHeader); - } - /** - * 用 Header 中的 RSA 密文解出 AES Key,再加密 data 为信封 Map。 + * 白名单接口强制加密 data。优先使用请求头中的 AES Key;否则服务端生成 Key 并写入信封 {@code ek}。 */ - public Map encryptData(Object data, String ekBase64) throws Exception { - if (StrUtil.isBlank(ekBase64)) { - throw new IllegalArgumentException("缺少 X-Resp-Ek"); - } - byte[] aesKey = decryptAesKey(ekBase64); + public Map encryptData(Object data, String requestEkBase64) throws Exception { + final boolean serverGeneratedKey = StrUtil.isBlank(requestEkBase64); + final byte[] aesKey = serverGeneratedKey + ? generateAesKeyBytes() + : decryptAesKey(requestEkBase64); byte[] plain = objectMapper.writeValueAsBytes(data == null ? new HashMap<>() : data); byte[] iv = new byte[IV_LEN]; @@ -95,9 +93,18 @@ public class RespBodyEncryptSupport { envelope.put("alg", "RSA-PKCS1-AES-256-GCM"); envelope.put("iv", Base64.encode(iv)); envelope.put("ct", Base64.encode(ct)); + if (serverGeneratedKey) { + envelope.put("ek", encryptAesKeyForClient(aesKey)); + } return envelope; } + private String encryptAesKeyForClient(byte[] aesKey) throws Exception { + Cipher rsa = Cipher.getInstance(RSA_TRANSFORMATION); + rsa.init(Cipher.ENCRYPT_MODE, privateKey); + return Base64.encode(rsa.doFinal(aesKey)); + } + private byte[] decryptAesKey(String ekBase64) throws Exception { Cipher rsa = Cipher.getInstance(RSA_TRANSFORMATION); rsa.init(Cipher.DECRYPT_MODE, privateKey); diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index db3a41d..3d3d25c 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -237,7 +237,7 @@ yudao: base-package: cn.iocoder.yudao member: resp-encrypt: - # 默认关;local/prod 配置私钥后开启。客户端带 X-Resp-Enc/X-Resp-Ek 时加密 3 个敏感接口 data + # local/prod 配置私钥后开启。白名单接口 data 强制加密(客户端可带 X-Resp-Ek,也可由响应信封 ek 字段解密) enabled: true private-key-base64: ${YUDAO_RESP_ENCRYPT_PRIVATE_KEY:} web: