1
0
mirror of synced 2026-10-09 08:16:39 +08:00

🎨 #4130 【小程序】【开放平台】优化代码,防止 code2Session 凭证通过异常和日志泄露

This commit is contained in:
yearliny
2026-09-26 15:14:18 +08:00
committed by GitHub
parent db880bb0da
commit a91a0140fc
15 changed files with 754 additions and 25 deletions

View File

@@ -0,0 +1,69 @@
package me.chanjar.weixin.common.util.http;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.error.WxRuntimeException;
/**
* Safe parameter and exception handling for requests containing credentials.
*/
public final class SensitiveRequestUtils {
private SensitiveRequestUtils() {
}
/**
* Encodes one raw query parameter value, without interpreting existing percent escapes.
*
* @param value raw, non-null parameter value
* @return UTF-8 form-encoded value
* @throws NullPointerException if the value is null
*/
public static String encodeQueryValue(String value) {
try {
return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 is not available");
}
}
/**
* Retains the WeChat error code and stack frames without exposing response data or causes.
* This is intended for credential-bearing entry points, not general exception conversion.
*
* @param failure original failure
* @return safe exception without the original message, JSON, cause or suppressed exceptions
*/
public static WxErrorException sanitize(WxErrorException failure) {
WxErrorException safe = new WxErrorException(new WxError(failure.getError().getErrorCode(),
"Sensitive request failed"));
safe.setStackTrace(failure.getStackTrace());
return safe;
}
/**
* Removes request data from a runtime failure. Common argument, state and null failures
* keep their categories; other runtime failures become {@link WxRuntimeException}.
* Original exception class names and stack frames remain available for diagnosis.
*
* @param failure original failure
* @return safe exception without the original message, cause or suppressed exceptions
*/
public static RuntimeException sanitize(RuntimeException failure) {
String message = "Sensitive request failed (" + failure.getClass().getName() + ")";
RuntimeException safe;
if (failure instanceof IllegalArgumentException) {
safe = new IllegalArgumentException(message);
} else if (failure instanceof IllegalStateException) {
safe = new IllegalStateException(message);
} else if (failure instanceof NullPointerException) {
safe = new NullPointerException(message);
} else {
safe = new WxRuntimeException(message);
}
safe.setStackTrace(failure.getStackTrace());
return safe;
}
}

View File

@@ -0,0 +1,67 @@
package me.chanjar.weixin.common.util.http;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URLDecoder;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.error.WxRuntimeException;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
public class SensitiveRequestUtilsTest {
@Test
public void encodesRawValuesExactlyOnce() throws Exception {
for (String value : new String[]{"NORMAL_CODE", "", "a b\n", "+&=#?%2F", "中文\uD83D\uDE00"}) {
String encoded = SensitiveRequestUtils.encodeQueryValue(value);
assertEquals(URLDecoder.decode(encoded, "UTF-8"), value);
assertFalse(encoded.contains("&"));
assertFalse(encoded.contains("#"));
}
assertEquals(SensitiveRequestUtils.encodeQueryValue("%2F"), "%252F");
}
@Test(expectedExceptions = NullPointerException.class)
public void doesNotConvertNullToAValue() {
SensitiveRequestUtils.encodeQueryValue(null);
}
@Test
public void removesAllOriginalErrorRepresentations() {
WxError error = WxError.builder().errorCode(40029).errorMsg("FAKE_SECRET")
.errorMsgEn("FAKE_SECRET").json("FAKE_SECRET").build();
WxErrorException original = new WxErrorException(error, new IllegalArgumentException("FAKE_SECRET"));
original.addSuppressed(new IllegalStateException("FAKE_SECRET"));
WxErrorException safe = SensitiveRequestUtils.sanitize(original);
assertEquals(safe.getError().getErrorCode(), 40029);
assertNull(safe.getError().getJson());
assertNull(safe.getError().getErrorMsgEn());
assertSafe(safe, original);
assertEquals(original.getError().getJson(), "FAKE_SECRET");
}
@Test
public void retainsCommonRuntimeCategoriesWithoutCauses() {
for (RuntimeException original : new RuntimeException[]{new IllegalArgumentException("FAKE_SECRET"),
new IllegalStateException("FAKE_SECRET"), new NullPointerException("FAKE_SECRET"),
new WxRuntimeException("FAKE_SECRET")}) {
original.initCause(new RuntimeException("FAKE_SECRET"));
original.addSuppressed(new RuntimeException("FAKE_SECRET"));
RuntimeException safe = SensitiveRequestUtils.sanitize(original);
assertEquals(safe.getClass(), original.getClass());
assertSafe(safe, original);
}
assertTrue(SensitiveRequestUtils.sanitize(new UnsupportedOperationException("FAKE_SECRET"))
instanceof WxRuntimeException);
}
private void assertSafe(Throwable safe, Throwable original) {
StringWriter trace = new StringWriter();
safe.printStackTrace(new PrintWriter(trace));
assertFalse(trace.toString().contains("FAKE_SECRET"));
assertNull(safe.getCause());
assertEquals(safe.getSuppressed().length, 0);
assertEquals(safe.getStackTrace(), original.getStackTrace());
}
}

View File

@@ -3,6 +3,7 @@
<suite name="Weixin-java-tool-suite" verbose="1">
<test name="Bean_Test">
<classes>
<class name="me.chanjar.weixin.common.util.http.SensitiveRequestUtilsTest"/>
<class name="me.chanjar.weixin.common.bean.WxAccessTokenTest"/>
<class name="me.chanjar.weixin.common.error.WxErrorTest"/>
<class name="me.chanjar.weixin.common.bean.WxMenuTest"/>