refactor(capability-alipay): 移除官方 SDK,直连 oauth.token

capability-alipay 仅用于认证登录/换 openId,改用 HTTP + RSA2 自实现
alipay.system.oauth.token,避免主应用引入约 32MB 的 alipay-sdk-java。
This commit is contained in:
DaxPay Dev
2026-07-09 21:44:00 +08:00
parent ad9e188b27
commit d2c9197f00
11 changed files with 540 additions and 68 deletions

View File

@@ -24,11 +24,22 @@
<artifactId>daxpay-platform-core</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 支付宝开放平台 SDK -->
<!-- hutool http (OpenAPI 直连) -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>${alipay-sdk.version}</version>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- hutool json (响应解析) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -6,8 +6,8 @@ import org.springframework.context.annotation.ComponentScan;
/// # 支付宝开放平台能力模块自动配置
///
/// 装配支付宝 OAuth 认证服务([AlipayAuthCapability]), 供 iam 模块(授权登录)
/// 与 payment 模块(通道认证)共用, 不耦合配置存储。
/// 装配支付宝 OAuth 认证服务([AlipayAuthCapability], OpenAPI 直连无官方 SDK),
/// 供 iam 模块(授权登录)与 payment 模块(通道认证)共用, 不耦合配置存储。
///
@Slf4j
@AutoConfiguration

View File

@@ -5,9 +5,9 @@ import lombok.Getter;
/// # 支付宝鉴权方式
///
/// 区分公钥模式与证书模式, 决定支付宝 SDK 的接口调用方式:
/// - [KEY] 公钥模式: 使用 alipayPublicKey, 调用 `AlipayClient.execute`
/// - [CERT] 证书模式: 使用三本证书, 调用 `AlipayClient.certificateExecute`
/// 区分公钥模式与证书模式, 决定 OpenAPI 请求参数与响应验签公钥来源:
/// - [KEY] 公钥模式: 使用 alipayPublicKey 验签
/// - [CERT] 证书模式: 请求附加 app_cert_sn / alipay_root_cert_sn, 从支付宝公钥证书取验签公钥
///
@Getter
@AllArgsConstructor

View File

@@ -3,12 +3,8 @@ package cn.daxpay.open.platform.capability.alipay.auth.service;
import cn.daxpay.open.platform.capability.alipay.auth.config.AlipayAuthConfig;
import cn.daxpay.open.platform.capability.alipay.auth.config.AlipayAuthTypeEnum;
import cn.daxpay.open.platform.capability.alipay.auth.result.AlipayAuthResult;
import cn.daxpay.open.platform.capability.alipay.openapi.AlipayOpenApiClient;
import cn.daxpay.open.platform.core.exception.operation.OperationFailException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipaySystemOauthTokenRequest;
import com.alipay.api.response.AlipaySystemOauthTokenResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -22,8 +18,8 @@ import java.util.Objects;
/// 从各自配置来源装载 [AlipayAuthConfig] 后传入本服务。
///
/// ## 能力边界
/// - 生成授权链接(纯字符串拼接, 不需要 SDK)
/// - authCode 换 userId/openId/accessToken(调用 `alipay.system.oauth.token`)
/// - 生成授权链接(纯字符串拼接)
/// - authCode 换 userId/openId/accessToken(直连 `alipay.system.oauth.token`, 无官方 SDK)
/// - 不做 state/queryCode 缓存(由调用方自行管理 Redis 会话)
///
@Slf4j
@@ -59,69 +55,25 @@ public class AlipayAuthCapability {
/// 通过 authCode 换取用户标识
///
/// 调用支付宝 `alipay.system.oauth.token` 接口(grant_type=authorization_code),
/// 直连支付宝 `alipay.system.oauth.token`(grant_type=authorization_code),
/// 返回 userId(传统) / openId(新标准) / accessToken(可用于后续 user.info.share)。
/// 平台级配置固定生产网关。
///
/// @param config 支付宝开放平台配置(完整凭据)
/// @param authCode 支付宝回调回传的 auth_code
/// @return 含 userId / openId / accessToken 的授权结果
public AlipayAuthResult getUserId(AlipayAuthConfig config, String authCode) {
AlipayClient client = this.buildClient(config);
AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
request.setCode(authCode);
request.setGrantType("authorization_code");
try {
AlipayAuthTypeEnum authType = AlipayAuthTypeEnum.fromCode(config.getAuthType());
AlipaySystemOauthTokenResponse response = authType.isCert()
? client.certificateExecute(request)
: client.execute(request);
if (!response.isSuccess()) {
log.warn("支付宝换取用户标识失败, code={}, msg={}, subMsg={}",
response.getCode(), response.getMsg(), response.getSubMsg());
// 支付宝: 换取用户标识失败: {0}
throw new OperationFailException("error.alipay.authFailed", response.getSubMsg());
}
return new AlipayAuthResult()
.setUserId(response.getUserId())
.setOpenId(response.getOpenId())
.setAccessToken(response.getAccessToken());
return AlipayOpenApiClient.oauthToken(config, authCode, false);
} catch (OperationFailException e) {
throw e;
} catch (Exception e) {
log.error("支付宝换取用户标识异常", e);
// 支付宝: 换取用户标识异常: {0}
// 支付宝: 换取用户标识失败: {0}
throw new OperationFailException("error.alipay.authFailed", e.getMessage());
}
}
/// 根据 [AlipayAuthConfig] 构造 [AlipayClient]
///
/// - 公钥模式: appId + privateKey + alipayPublicKey
/// - 证书模式: appId + privateKey + 三本证书内容
private AlipayClient buildClient(AlipayAuthConfig config) {
try {
AlipayConfig alipayConfig = new AlipayConfig();
alipayConfig.setAppId(config.getAppId());
AlipayAuthTypeEnum authType = AlipayAuthTypeEnum.fromCode(config.getAuthType());
if (authType.isCert()) {
// 证书模式
alipayConfig.setPrivateKey(config.getPrivateKey());
alipayConfig.setAppCertContent(config.getAppCert());
alipayConfig.setAlipayPublicCertContent(config.getAlipayCert());
alipayConfig.setRootCertContent(config.getAlipayRootCert());
} else {
// 公钥模式
alipayConfig.setPrivateKey(config.getPrivateKey());
alipayConfig.setAlipayPublicKey(config.getAlipayPublicKey());
}
return new DefaultAlipayClient(alipayConfig);
} catch (Exception e) {
log.error("支付宝 AlipayClient 构造失败", e);
// 支付宝: SDK 客户端构造失败: {0}
throw new OperationFailException("error.alipay.clientInitFailed", e.getMessage());
}
}
/// 判断配置是否完整可用(appId + privateKey 必填, 公钥/证书按 authType 二选一)
public boolean isConfigured(AlipayAuthConfig config) {
if (config == null) {

View File

@@ -0,0 +1,113 @@
package cn.daxpay.open.platform.capability.alipay.openapi;
import lombok.experimental.UtilityClass;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
/// # 支付宝证书工具
///
/// 对齐官方 SDK `AntCertificationUtil`:
/// - app_cert_sn: MD5(issuerDN + serialNumber) 补齐 32 位十六进制
/// - alipay_root_cert_sn: 根证书链中 RSA 签名算法(OID 1.2.840.113549.1.1.*) 证书 SN, 多值 `_` 连接
/// - 从支付宝公钥证书提取 Base64 编码的 X509 公钥
///
@UtilityClass
public class AlipayCertUtil {
/// RSA 签名算法 OID 前缀
private static final String RSA_SIG_ALG_OID_PREFIX = "1.2.840.113549.1.1";
static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
/// 计算应用公钥证书 SN(`app_cert_sn`)
public String getAppCertSn(String appCertContent) {
X509Certificate cert = parseCertificate(appCertContent);
return getCertSn(cert);
}
/// 计算支付宝根证书 SN(`alipay_root_cert_sn`)
public String getRootCertSn(String rootCertContent) {
X509Certificate[] certs = parseCertificateChain(rootCertContent);
StringBuilder rootCertSn = null;
for (X509Certificate cert : certs) {
if (cert.getSigAlgOID() != null && cert.getSigAlgOID().startsWith(RSA_SIG_ALG_OID_PREFIX)) {
String certSn = getCertSn(cert);
if (rootCertSn == null || (rootCertSn.isEmpty())) {
rootCertSn = certSn == null ? null : new StringBuilder(certSn);
} else {
rootCertSn.append("_").append(certSn);
}
}
}
return rootCertSn == null ? null : rootCertSn.toString();
}
/// 从支付宝公钥证书提取 Base64 公钥(X509 编码)
public String getAlipayPublicKeyFromCert(String alipayCertContent) {
X509Certificate cert = parseCertificate(alipayCertContent);
PublicKey publicKey = cert.getPublicKey();
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}
/// 计算单本证书 SN
public String getCertSn(X509Certificate cert) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
// 与官方 SDK 一致: 使用平台默认字符集对 issuerDN+serial 做 digest
md.update((cert.getIssuerX500Principal().getName() + cert.getSerialNumber()).getBytes());
return fillMd5(new BigInteger(1, md.digest()).toString(16));
} catch (Exception e) {
throw new IllegalStateException("alipay cert sn failed: " + e.getMessage(), e);
}
}
/// 解析单本证书
public X509Certificate parseCertificate(String certContent) {
try {
CertificateFactory factory = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
try (ByteArrayInputStream input = new ByteArrayInputStream(certContent.getBytes(StandardCharsets.UTF_8))) {
return (X509Certificate) factory.generateCertificate(input);
}
} catch (Exception e) {
throw new IllegalStateException("alipay cert parse failed: " + e.getMessage(), e);
}
}
/// 解析证书链(根证书可能含多本)
public X509Certificate[] parseCertificateChain(String certContent) {
try {
CertificateFactory factory = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
try (ByteArrayInputStream input = new ByteArrayInputStream(certContent.getBytes(StandardCharsets.UTF_8))) {
Collection<? extends Certificate> certificates = factory.generateCertificates(input);
List<X509Certificate> list = new ArrayList<>(certificates.size());
for (Certificate certificate : certificates) {
list.add((X509Certificate) certificate);
}
return list.toArray(new X509Certificate[0]);
}
} catch (Exception e) {
throw new IllegalStateException("alipay cert chain parse failed: " + e.getMessage(), e);
}
}
private String fillMd5(String md5) {
return md5.length() == 32 ? md5 : fillMd5("0" + md5);
}
}

View File

@@ -0,0 +1,243 @@
package cn.daxpay.open.platform.capability.alipay.openapi;
import cn.daxpay.open.platform.capability.alipay.auth.config.AlipayAuthConfig;
import cn.daxpay.open.platform.capability.alipay.auth.config.AlipayAuthTypeEnum;
import cn.daxpay.open.platform.capability.alipay.auth.result.AlipayAuthResult;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
/// # 支付宝开放平台 OpenAPI 客户端
///
/// 仅实现 `alipay.system.oauth.token`(auth_code 换 userId/openId/accessToken),
/// 不依赖官方 SDK。支持公钥模式与证书模式。
///
@Slf4j
@UtilityClass
public class AlipayOpenApiClient {
private static final String GATEWAY_PRODUCTION = "https://openapi.alipay.com/gateway.do";
private static final String GATEWAY_SANDBOX = "https://openapi-sandbox.dl.alipaydev.com/gateway.do";
private static final String METHOD_OAUTH_TOKEN = "alipay.system.oauth.token";
private static final String RESPONSE_NODE = "alipay_system_oauth_token_response";
private static final String ERROR_NODE = "error_response";
private static final DateTimeFormatter TIMESTAMP_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final int HTTP_TIMEOUT_MS = 15_000;
/// 调用 `alipay.system.oauth.token` 换取用户标识
///
/// @param config 支付宝凭据
/// @param authCode 授权码
/// @param sandbox 是否沙箱网关
public AlipayAuthResult oauthToken(AlipayAuthConfig config, String authCode, boolean sandbox) {
Map<String, String> params = buildCommonParams(config);
params.put("grant_type", "authorization_code");
params.put("code", authCode);
// 证书模式附加 SN
if (AlipayAuthTypeEnum.fromCode(config.getAuthType()).isCert()) {
params.put("app_cert_sn", AlipayCertUtil.getAppCertSn(config.getAppCert()));
params.put("alipay_root_cert_sn", AlipayCertUtil.getRootCertSn(config.getAlipayRootCert()));
}
String signContent = AlipayOpenApiSigner.getSignContent(params);
params.put("sign", AlipayOpenApiSigner.rsa2Sign(signContent, config.getPrivateKey()));
String gateway = sandbox ? GATEWAY_SANDBOX : GATEWAY_PRODUCTION;
String body;
try (HttpResponse response = HttpRequest.post(gateway)
.form(new HashMap<>(params))
.timeout(HTTP_TIMEOUT_MS)
.execute()) {
body = response.body();
} catch (Exception e) {
log.error("支付宝 OpenAPI 请求失败, method={}", METHOD_OAUTH_TOKEN, e);
throw new IllegalStateException("alipay openapi request failed: " + e.getMessage(), e);
}
if (body == null || body.isBlank()) {
throw new IllegalStateException("alipay openapi empty response");
}
// 验签
verifyResponse(body, config);
// 解析业务节点
JSONObject root = JSONUtil.parseObj(body);
JSONObject biz = root.getJSONObject(RESPONSE_NODE);
if (biz == null) {
biz = root.getJSONObject(ERROR_NODE);
}
if (biz == null) {
throw new IllegalStateException("alipay openapi missing response node");
}
String code = biz.getStr("code");
if (code != null && !code.isBlank() && !"10000".equals(code)) {
String subMsg = biz.getStr("sub_msg");
String msg = subMsg != null && !subMsg.isBlank() ? subMsg : biz.getStr("msg");
throw new IllegalStateException(msg != null ? msg : code);
}
return new AlipayAuthResult()
.setUserId(biz.getStr("user_id"))
.setOpenId(biz.getStr("open_id"))
.setAccessToken(biz.getStr("access_token"));
}
/// 组装公共请求参数
private Map<String, String> buildCommonParams(AlipayAuthConfig config) {
Map<String, String> params = new HashMap<>();
params.put("app_id", config.getAppId());
params.put("method", METHOD_OAUTH_TOKEN);
params.put("format", "JSON");
params.put("charset", "UTF-8");
params.put("sign_type", "RSA2");
params.put("timestamp", LocalDateTime.now().format(TIMESTAMP_FMT));
params.put("version", "1.0");
return params;
}
/// 响应验签: 截取 response 节点原文 + RSA2
private void verifyResponse(String body, AlipayAuthConfig config) {
String sign = extractJsonStringField(body, "sign");
if (sign == null || sign.isBlank()) {
// 部分错误响应可能无 sign, 交给业务解析抛错
return;
}
String source = extractSignSource(body);
if (source == null || source.isBlank()) {
throw new IllegalStateException("alipay response sign source empty");
}
String publicKey = resolveVerifyPublicKey(config);
if (!AlipayOpenApiSigner.rsa2Verify(source, sign, publicKey)) {
throw new IllegalStateException("alipay response sign verify failed");
}
}
/// 公钥模式用配置公钥; 证书模式从支付宝公钥证书提取
private String resolveVerifyPublicKey(AlipayAuthConfig config) {
if (AlipayAuthTypeEnum.fromCode(config.getAuthType()).isCert()) {
return AlipayCertUtil.getAlipayPublicKeyFromCert(config.getAlipayCert());
}
return config.getAlipayPublicKey();
}
/// 截取 `alipay_system_oauth_token_response` 或 `error_response` 节点的 JSON 原文
private String extractSignSource(String body) {
int index = body.indexOf(RESPONSE_NODE);
String node = RESPONSE_NODE;
if (index < 0) {
index = body.indexOf(ERROR_NODE);
node = ERROR_NODE;
}
if (index < 0) {
return null;
}
// 节点名后的 `":` 再往后找 `{` 或 `"`
int startSearch = index + node.length() + 2;
return extractSignContent(body, startSearch);
}
/// 对齐官方 `AlipaySignature.extractSignContent`: 从 begin 起找到 `{...}` 或 `"..."` 片段
private String extractSignContent(String responseString, int begin) {
int beginIndex = begin;
while (beginIndex < responseString.length()
&& responseString.charAt(beginIndex) != '{'
&& responseString.charAt(beginIndex) != '"') {
beginIndex++;
}
if (beginIndex >= responseString.length()) {
return null;
}
int endIndex;
if (responseString.charAt(beginIndex) == '{') {
endIndex = extractJsonObjectEndPosition(responseString, beginIndex);
} else {
endIndex = extractJsonStringEndPosition(responseString, beginIndex);
}
return responseString.substring(beginIndex, endIndex);
}
private int extractJsonObjectEndPosition(String responseString, int beginPosition) {
LinkedList<Character> braces = new LinkedList<>();
boolean inQuotes = false;
int consecutiveEscapeCount = 0;
for (int index = beginPosition; index < responseString.length(); index++) {
char currentChar = responseString.charAt(index);
if (currentChar == '"' && consecutiveEscapeCount % 2 == 0) {
inQuotes = !inQuotes;
} else if (currentChar == '{' && !inQuotes) {
braces.push('{');
} else if (currentChar == '}' && !inQuotes) {
braces.pop();
if (braces.isEmpty()) {
return index + 1;
}
}
if (currentChar == '\\') {
consecutiveEscapeCount++;
} else {
consecutiveEscapeCount = 0;
}
}
return responseString.length();
}
private int extractJsonStringEndPosition(String responseString, int beginPosition) {
for (int index = beginPosition; index < responseString.length(); index++) {
if (responseString.charAt(index) == '"' && index != beginPosition) {
return index + 1;
}
}
return responseString.length();
}
/// 粗提取顶层 JSON 字符串字段(用于 sign)
private String extractJsonStringField(String body, String field) {
String pattern = "\"" + field + "\"";
int idx = body.lastIndexOf(pattern);
if (idx < 0) {
return null;
}
int colon = body.indexOf(':', idx + pattern.length());
if (colon < 0) {
return null;
}
int start = colon + 1;
while (start < body.length() && Character.isWhitespace(body.charAt(start))) {
start++;
}
if (start >= body.length() || body.charAt(start) != '"') {
return null;
}
start++;
StringBuilder sb = new StringBuilder();
boolean escape = false;
for (int i = start; i < body.length(); i++) {
char c = body.charAt(i);
if (escape) {
sb.append(c);
escape = false;
continue;
}
if (c == '\\') {
escape = true;
continue;
}
if (c == '"') {
return sb.toString();
}
sb.append(c);
}
return null;
}
}

View File

@@ -0,0 +1,110 @@
package cn.daxpay.open.platform.capability.alipay.openapi;
import lombok.experimental.UtilityClass;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/// # 支付宝开放平台 RSA2 签名/验签
///
/// 对齐官方 SDK `AlipaySignature` 的 RSA2 行为:
/// - 签名串: 除 sign 外非空参数按 key 字典序 `k=v&k=v`
/// - 算法: SHA256withRSA + Base64
/// - 密钥: 支持 PEM 与裸 Base64(PKCS8 私钥 / X509 公钥)
/// - 字符集固定 UTF-8(避免平台默认编码差异)
///
@UtilityClass
public class AlipayOpenApiSigner {
private static final String SIGN_ALGORITHM = "SHA256withRSA";
/// 将参数拼成待签名字符串(排除 sign, 跳过空值, key 字典序)
public String getSignContent(Map<String, String> params) {
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
StringBuilder content = new StringBuilder();
int index = 0;
for (String key : keys) {
if ("sign".equals(key)) {
continue;
}
String value = params.get(key);
if (key == null || key.isEmpty() || value == null || value.isEmpty()) {
continue;
}
if (index > 0) {
content.append('&');
}
content.append(key).append('=').append(value);
index++;
}
return content.toString();
}
/// RSA2 私钥签名, 返回 Base64
public String rsa2Sign(String content, String privateKeyContent) {
try {
PrivateKey privateKey = loadPrivateKey(privateKeyContent);
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initSign(privateKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(signature.sign());
} catch (Exception e) {
throw new IllegalStateException("alipay rsa2 sign failed: " + e.getMessage(), e);
}
}
/// RSA2 公钥验签
public boolean rsa2Verify(String content, String sign, String publicKeyContent) {
try {
PublicKey publicKey = loadPublicKey(publicKeyContent);
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initVerify(publicKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.getDecoder().decode(sign));
} catch (Exception e) {
return false;
}
}
/// 加载 PKCS8 私钥(兼容 PEM 头与裸 Base64)
public PrivateKey loadPrivateKey(String keyContent) {
try {
byte[] decoded = Base64.getDecoder().decode(stripKey(keyContent));
return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
} catch (Exception e) {
throw new IllegalStateException("alipay private key parse failed: " + e.getMessage(), e);
}
}
/// 加载 X509 公钥(兼容 PEM 头与裸 Base64)
public PublicKey loadPublicKey(String keyContent) {
try {
byte[] decoded = Base64.getDecoder().decode(stripKey(keyContent));
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
} catch (Exception e) {
throw new IllegalStateException("alipay public key parse failed: " + e.getMessage(), e);
}
}
private String stripKey(String keyContent) {
return keyContent
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("-----BEGIN RSA PRIVATE KEY-----", "")
.replace("-----END RSA PRIVATE KEY-----", "")
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
}
}

View File

@@ -1,4 +1,4 @@
{
"authFailed": "Failed to get Alipay user identifier: {0}",
"clientInitFailed": "Failed to init Alipay SDK client: {0}"
"clientInitFailed": "Failed to init Alipay OpenAPI client: {0}"
}

View File

@@ -1,4 +1,4 @@
{
"authFailed": "换取支付宝用户标识失败: {0}",
"clientInitFailed": "支付宝 SDK 客户端构造失败: {0}"
"clientInitFailed": "支付宝 OpenAPI 初始化失败: {0}"
}

View File

@@ -0,0 +1,45 @@
package cn.daxpay.open.platform.capability.alipay.openapi;
import org.junit.jupiter.api.Test;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/// 支付宝 OpenAPI 签名工具单测
class AlipayOpenApiSignerTest {
@Test
void getSignContent_sortsAndSkipsEmpty() {
Map<String, String> params = new LinkedHashMap<>();
params.put("method", "alipay.system.oauth.token");
params.put("app_id", "2021000000000000");
params.put("sign", "should-skip");
params.put("code", "authcode");
params.put("empty", "");
params.put("charset", "UTF-8");
String content = AlipayOpenApiSigner.getSignContent(params);
assertEquals(
"app_id=2021000000000000&charset=UTF-8&code=authcode&method=alipay.system.oauth.token",
content);
}
@Test
void rsa2SignAndVerify_roundTrip() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair keyPair = generator.generateKeyPair();
String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
String content = "app_id=2021&method=alipay.system.oauth.token";
String sign = AlipayOpenApiSigner.rsa2Sign(content, privateKey);
assertTrue(AlipayOpenApiSigner.rsa2Verify(content, sign, publicKey));
}
}

View File

@@ -56,8 +56,6 @@
<ip2region.version>3.3.7</ip2region.version>
<wxjava.version>4.8.4.B</wxjava.version>
<commonmark.version>0.24.0</commonmark.version>
<!-- 支付宝开放平台 SDK (capability-alipay 使用) -->
<alipay-sdk.version>4.40.272.ALL</alipay-sdk.version>
</properties>
<build>