Skip to content

Commit 777f4e5

Browse files
authored
🎨 #3841 【微信支付】修复 RsaCryptoUtil 无法加密继承字段和嵌套对象的问题
1 parent 16d5cf9 commit 777f4e5

File tree

2 files changed

+200
-2
lines changed

2 files changed

+200
-2
lines changed

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
import java.security.NoSuchAlgorithmException;
1515
import java.security.PrivateKey;
1616
import java.security.cert.X509Certificate;
17+
import java.util.ArrayList;
1718
import java.util.Base64;
1819
import java.util.Collection;
20+
import java.util.Collections;
21+
import java.util.List;
1922

2023
/**
2124
* 微信支付敏感信息加密
@@ -36,10 +39,26 @@ public static void encryptFields(Object encryptObject, X509Certificate certifica
3639
}
3740
}
3841

42+
/**
43+
* 递归获取类的所有字段,包括父类中的字段
44+
*
45+
* @param clazz 要获取字段的类
46+
* @return 所有字段的列表
47+
*/
48+
private static List<Field> getAllFields(Class<?> clazz) {
49+
List<Field> fields = new ArrayList<>();
50+
while (clazz != null && clazz != Object.class) {
51+
Field[] declaredFields = clazz.getDeclaredFields();
52+
Collections.addAll(fields, declaredFields);
53+
clazz = clazz.getSuperclass();
54+
}
55+
return fields;
56+
}
57+
3958
private static void encryptField(Object encryptObject, X509Certificate certificate) throws IllegalAccessException, IllegalBlockSizeException {
4059
Class<?> infoClass = encryptObject.getClass();
41-
Field[] infoFieldArray = infoClass.getDeclaredFields();
42-
for (Field field : infoFieldArray) {
60+
List<Field> infoFieldList = getAllFields(infoClass);
61+
for (Field field : infoFieldList) {
4362
if (field.isAnnotationPresent(SpecEncrypt.class)) {
4463
//字段使用了@SpecEncrypt进行标识
4564
if (field.getType().getTypeName().equals(JAVA_LANG_STRING)) {
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package com.github.binarywang.wxpay.v3.util;
2+
3+
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingReceiverV3Request;
4+
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingV3Request;
5+
import com.github.binarywang.wxpay.exception.WxPayException;
6+
import com.github.binarywang.wxpay.v3.SpecEncrypt;
7+
import com.google.gson.annotations.SerializedName;
8+
import lombok.Data;
9+
import org.testng.annotations.Test;
10+
11+
import java.lang.reflect.Field;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
import static org.testng.Assert.*;
16+
17+
/**
18+
* RsaCryptoUtil 测试类
19+
*/
20+
public class RsaCryptoUtilTest {
21+
22+
/**
23+
* 测试反射能否找到嵌套类中的 @SpecEncrypt 注解字段
24+
*/
25+
@Test
26+
public void testFindAnnotatedFieldsInNestedClass() {
27+
// 创建 Receiver 对象
28+
ProfitSharingV3Request.Receiver receiver = new ProfitSharingV3Request.Receiver();
29+
receiver.setName("测试姓名");
30+
31+
// 使用反射查找带有 @SpecEncrypt 注解的字段
32+
Class<?> receiverClass = receiver.getClass();
33+
Field[] fields = receiverClass.getDeclaredFields();
34+
35+
boolean foundNameField = false;
36+
boolean nameFieldHasAnnotation = false;
37+
38+
for (Field field : fields) {
39+
if (field.getName().equals("name")) {
40+
foundNameField = true;
41+
if (field.isAnnotationPresent(SpecEncrypt.class)) {
42+
nameFieldHasAnnotation = true;
43+
}
44+
}
45+
}
46+
47+
// 验证能够找到 name 字段并且它有 @SpecEncrypt 注解
48+
assertTrue(foundNameField, "应该能找到 name 字段");
49+
assertTrue(nameFieldHasAnnotation, "name 字段应该有 @SpecEncrypt 注解");
50+
}
51+
52+
/**
53+
* 测试嵌套对象中的字段加密
54+
* 验证 List<Receiver> 中每个 Receiver 对象的 name 字段是否能被正确找到和处理
55+
*/
56+
@Test
57+
public void testEncryptFieldsWithNestedObjects() {
58+
// 创建测试对象
59+
ProfitSharingV3Request request = ProfitSharingV3Request.newBuilder()
60+
.appid("test-appid")
61+
.subMchId("test-submchid")
62+
.transactionId("test-transaction")
63+
.outOrderNo("test-order-no")
64+
.unfreezeUnsplit(true)
65+
.build();
66+
67+
List<ProfitSharingV3Request.Receiver> receivers = new ArrayList<>();
68+
ProfitSharingV3Request.Receiver receiver = new ProfitSharingV3Request.Receiver();
69+
receiver.setName("张三"); // 设置需要加密的字段
70+
receiver.setAccount("test-account");
71+
receiver.setType("PERSONAL_OPENID");
72+
receiver.setAmount(100);
73+
receiver.setRelationType("STORE");
74+
receiver.setDescription("测试分账");
75+
76+
receivers.add(receiver);
77+
request.setReceivers(receivers);
78+
79+
// 验证 receivers 字段有 @SpecEncrypt 注解
80+
try {
81+
Field receiversField = ProfitSharingV3Request.class.getDeclaredField("receivers");
82+
boolean hasAnnotation = receiversField.isAnnotationPresent(SpecEncrypt.class);
83+
assertTrue(hasAnnotation, "receivers 字段应该有 @SpecEncrypt 注解");
84+
} catch (NoSuchFieldException e) {
85+
fail("应该能找到 receivers 字段");
86+
}
87+
88+
// 验证name字段不为null
89+
assertNotNull(receiver.getName());
90+
assertEquals(receiver.getName(), "张三");
91+
}
92+
93+
/**
94+
* 测试单个对象中的字段加密
95+
* 验证直接在对象上的 @SpecEncrypt 字段是否能被正确找到
96+
*/
97+
@Test
98+
public void testEncryptFieldsWithDirectField() {
99+
// 创建测试对象
100+
ProfitSharingReceiverV3Request request = ProfitSharingReceiverV3Request.newBuilder()
101+
.appid("test-appid")
102+
.subMchId("test-submchid")
103+
.type("PERSONAL_OPENID")
104+
.account("test-account")
105+
.name("李四")
106+
.relationType("STORE")
107+
.build();
108+
109+
// 验证 name 字段有 @SpecEncrypt 注解
110+
try {
111+
Field nameField = ProfitSharingReceiverV3Request.class.getDeclaredField("name");
112+
boolean hasAnnotation = nameField.isAnnotationPresent(SpecEncrypt.class);
113+
assertTrue(hasAnnotation, "name 字段应该有 @SpecEncrypt 注解");
114+
} catch (NoSuchFieldException e) {
115+
fail("应该能找到 name 字段");
116+
}
117+
118+
// 验证name字段不为null
119+
assertNotNull(request.getName());
120+
assertEquals(request.getName(), "李四");
121+
}
122+
123+
/**
124+
* 测试类继承场景下的字段加密
125+
* 验证父类中带 @SpecEncrypt 注解的字段是否能被正确找到和加密
126+
*/
127+
@Test
128+
public void testEncryptFieldsWithInheritance() {
129+
// 定义测试用的父类和子类
130+
@Data
131+
class ParentRequest {
132+
@SpecEncrypt
133+
@SerializedName("parent_name")
134+
private String parentName;
135+
}
136+
137+
@Data
138+
@lombok.EqualsAndHashCode(callSuper = false)
139+
class ChildRequest extends ParentRequest {
140+
@SpecEncrypt
141+
@SerializedName("child_name")
142+
private String childName;
143+
144+
@Override
145+
protected boolean canEqual(final Object other) {
146+
return other instanceof ChildRequest;
147+
}
148+
}
149+
150+
// 创建子类实例
151+
ChildRequest request = new ChildRequest();
152+
request.setParentName("父类字段");
153+
request.setChildName("子类字段");
154+
155+
// 验证能够找到父类和子类的字段
156+
// 使用 getDeclaredFields 只能找到子类字段
157+
Field[] childFields = ChildRequest.class.getDeclaredFields();
158+
159+
// 使用反射调用 RsaCryptoUtil 的私有 getAllFields 方法
160+
int annotatedFieldCount = 0;
161+
try {
162+
java.lang.reflect.Method getAllFieldsMethod = RsaCryptoUtil.class.getDeclaredMethod("getAllFields", Class.class);
163+
getAllFieldsMethod.setAccessible(true);
164+
@SuppressWarnings("unchecked")
165+
List<Field> allFields = (List<Field>) getAllFieldsMethod.invoke(null, ChildRequest.class);
166+
167+
for (Field field : allFields) {
168+
if (field.isAnnotationPresent(SpecEncrypt.class)) {
169+
annotatedFieldCount++;
170+
}
171+
}
172+
} catch (Exception e) {
173+
fail("无法调用 getAllFields 方法: " + e.getMessage());
174+
}
175+
176+
// 应该找到2个带注解的字段(parentName 和 childName)
177+
assertTrue(annotatedFieldCount >= 2, "应该能找到至少2个带 @SpecEncrypt 注解的字段");
178+
}
179+
}

0 commit comments

Comments
 (0)