# authapi-sdk **Repository Path**: ufudig/authapi-sdk ## Basic Information - **Project Name**: authapi-sdk - **Description**: 公开服务用RSA签名/加密开发库 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: 1.0.0 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-09-07 - **Last Updated**: 2026-05-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Auth Api SDK 一个面向 Java / Spring Boot 场景的认证 SDK,提供 RSA 签名验签、RSA 字段加解密、SM4 报文加解密和证书生成能力。 ## 特性 - 基于 `SHA256withRSA` 生成和验证认证签名 - 支持生成 RSA 私钥、公钥、X509 证书和 PKCS12 证书 - 支持 RSA 短字段加解密 - 支持 SM4 整体报文加解密 - 支持第三方回调通知验签、AES-256-GCM 解密与发送 - 提供 Spring Boot 配置绑定,接入成本低 ## 兼容基线 - Java 17 - Maven - Spring Boot 2.7.x - 当前版本:`1.0.0` ## 快速开始 ### 1. 添加依赖 ```xml com.touscm authapi-sdk 1.0.0 ``` ### 2. 配置 SDK ```yaml auth-api: auth-type: OPEN-SHA256-RSA2048 cert-identity: merchant_id cert-root-path: /data/auth/certs cert-prefix: api_client notify: mode: row allowed-timestamp-skew-seconds: 300 api-key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx mch-cert-root-path: /data/auth/platform ``` 必填项: - `auth-api.cert-root-path` - `auth-api.cert-prefix` 默认项: - `auth-api.auth-type=OPEN-SHA256-RSA2048` - `auth-api.cert-identity=merchant_id` - `auth-api.notify.mode=row` 安全约束: - `certIdentity` 仅支持字母、数字、下划线和中划线 - 验签默认校验请求时间窗,超出 5 分钟的请求会被拒绝 ### 3. 生成证书并生成认证头 ```java var certIdentity = "APP-ID-000001"; var certSerialNo = 2025L; var body = """ { "id": "002212025060509552710776517720207020032" } """; signAuthService.generateCert(certIdentity, certSerialNo); var authorization = signAuthService.getAuthorization( certIdentity, certSerialNo, "/open/api/v1/pay", "POST", body ); ``` ## 核心能力 ### 签名与验签 签名原文格式固定为: ```text 请求方法 + "\n" + 请求路由 + "\n" + 时间戳 + "\n" + 随机字符串 + "\n" + 请求体 + "\n" ``` 认证头格式为: ```text 认证类型 认证标识字段名=认证标识值,nonce_str=随机字符串,signature=签名,timestamp=时间戳,serial_no=证书序号 ``` 验签时必须保证请求方法、URI、时间戳、随机串和请求体与签名时完全一致。 当前实现默认仅接受 5 分钟内的请求时间戳。 ### RSA 字段加解密 - 当前实现算法:`RSA/ECB/OAEPWithSHA-1AndMGF1Padding` - 适合短文本敏感字段 - 不适合直接加密完整 JSON 报文 ### SM4 报文加解密 - 当前实现算法:`SM4/ECB/PKCS5Padding` - 适合整体请求体或较长载荷 - 加密结果使用 Base64 字符串表示 ## 典型场景 ### 生成认证头 ```java var authorization = signAuthService.getAuthorization( "APP-ID-000001", 2025L, "/open/api/v1/pay", "POST", body ); ``` ### 验证签名 ```java var result = signAuthService.authCheck( "POST", "/open/api/v1/pay", authorization, body, Map.of("Content-Type", "application/json") ); ``` ### RSA 字段加解密 ```java var encryptResult = signAuthService.rsaEncrypt("APP-ID-000001", "sensitive-value"); var decryptResult = signAuthService.rsaDecrypt("APP-ID-000001", encryptResult.getData()); ``` ### SM4 报文加解密 ```java var key = Sm4Utils.generateKey(); var encrypted = Sm4Utils.encryptEcb(key, body); var decrypted = Sm4Utils.decryptEcb(key, encrypted); ``` ### 第三方回调通知 验证回调通知: ```java var result = notifyService.verifyNotify(headers, rawBody); if (!result.isSuccess()) { throw new IllegalArgumentException(result.getMessage()); } var notify = result.getData(); System.out.println(notify.notificationId()); System.out.println(notify.eventType()); System.out.println(notify.resource()); ``` 发送回调通知: ```java // row 模式(默认) var result = notifyService.sendNotifyRow( "https://example.com/callback", resourceData, privateKeyPem ); // row 模式,私钥通过文件路径指定 var result = notifyService.sendNotify( "https://example.com/callback", resourceData, "/data/auth/certs/merchant/api_client_key_pkcs8.pem" ); // wechatpay-apiv3 模式 var result = notifyService.sendNotify( "https://example.com/callback", "NOTIFY-001", "TRANSACTION.SUCCESS", resourceData, apiKey, privateKeyPem, serialNo ); if (!result.isSuccess()) { // 失败时 getMessage() 返回回调服务的响应内容 System.err.println("发送失败: " + result.getMessage()); } ``` 发送结果说明: - 返回码 `200` 或 `204` 时表示成功,`getData()` 返回回调响应内容 - 其他返回码表示失败,`getMessage()` 返回回调服务的响应内容用于排查 通知接入要求: - 必须使用原始请求体 `rawBody` 参与验签,不要先重排 JSON - 需要保留原始请求头中的 `signature`、`timestamp`、`nonce`,`serial` 为可选(有则使用平台证书验签,无则使用公钥验签) - 建议业务层按 `notificationId` 或业务单号做幂等 - 建议处理成功后返回 `200` 或 `204` Controller 示例: ```java @RestController @RequestMapping("/pay/notify") public class NotifyController { @Resource private NotifyService notifyService; @PostMapping("/callback") public ResponseEntity notifyCallback( @RequestHeader Map headers, @RequestBody String rawBody ) { var result = notifyService.verifyNotify(headers, rawBody); if (!result.isSuccess()) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } var notify = result.getData(); // 业务层建议按 notificationId 或业务单号做幂等 // doBusiness(notify); return ResponseEntity.noContent().build(); } } ``` ## 证书目录结构 SDK 会按 `certIdentity` 生成独立目录: ```text {cert-root-path}/ └── {certIdentity}/ ├── {cert-prefix}_key_rsa.pem ├── {cert-prefix}_key_pkcs8.pem ├── {cert-prefix}_public.pem ├── {cert-prefix}_cert_{serialNo}.crt └── {cert-prefix}_cert.p12 ``` ## FAQ ### 为什么验签失败 优先检查以下内容是否与签名时完全一致: - 请求方法 - 请求 URI - 请求体 - 认证头中的时间戳和随机串 - 本地证书序号和证书文件 ### 为什么提示未定义证书根目录或证书前缀 因为 `auth-api.cert-root-path` 和 `auth-api.cert-prefix` 是运行必填配置。 ### 为什么不建议用 RSA 直接加密整个请求体 因为当前实现基于 RSA 2048,适合短文本字段,不适合较长报文。整包数据优先使用 SM4。 ### 通知验签需要哪些额外配置 至少需要: - `auth-api.notify.api-key`(wechatpay-apiv3 模式必填,row 模式不需要) - 平台证书模式下的 `auth-api.notify.mch-cert-root-path` 如果使用公钥模式,还需要: - `auth-api.notify.server-public-key-id` - `auth-api.notify.server-public-key` SDK 根据通知头中是否包含 `serial` 自动选择验签模式:有 `serial` 使用平台证书验签,无 `serial` 使用公钥验签。 ### row 模式和 wechatpay-apiv3 模式有什么区别 - `row`(默认):验签后直接解析请求体,发送时直接发送原始数据,不做加解密 - `wechatpay-apiv3`:验签后做 AES-256-GCM 解密,发送时加密并封装 NotifyRequest wechatpay-apiv3 模式适用于微信支付 APIv3 等需要加解密的场景,配置时设置 `auth-api.notify.mode=wechatpay-apiv3`。 ### 平台证书目录怎么放 当通知头包含 `serial` 时(即使用平台证书模式),SDK 默认会在 `mch-cert-root-path` 下按下面顺序查找: - `{serial}.pem` - `{serial}.crt` ### 平台证书模式和公钥模式怎么选 SDK 根据通知头中是否包含 `serial` 自动选择验签模式: - **平台证书模式**(通知头包含 `serial` 时自动使用) 适合已经有证书管理流程的系统,根据证书序列号查找本地证书文件 - **公钥模式**(通知头不包含 `serial` 时自动使用) 配置更轻,适合不想管理本地证书文件的系统 推荐: - 已有证书目录管理能力的系统,发送方在通知头中包含 `serial`,SDK 自动使用平台证书模式 - 只想快速接通知验签,发送方不在通知头中包含 `serial`,配置 `server-public-key`,SDK 自动使用公钥模式 ### 通知幂等怎么做 SDK 当前负责验签和解密,不替业务做幂等。建议业务层至少按下面任一键处理: - `notificationId` - 业务单号,例如 `out_trade_no` 推荐顺序: 1. 先调用 SDK 验签和解密 2. 再查幂等记录 3. 未处理则执行业务并落库 4. 已处理则直接返回成功 更完整的业务处理模板见: - [docs/DEVELOPER_GUIDE.md](/Users/juming/Documents/Workspase/coding/touscm/authapi-sdk/docs/DEVELOPER_GUIDE.md) - [docs/WECHATPAY_NOTIFY_GUIDE.md](/Users/juming/Documents/Workspase/coding/touscm/authapi-sdk/docs/WECHATPAY_NOTIFY_GUIDE.md) ### 平台证书如何轮换 建议: 1. 新平台证书下发后,先放入 `mch-cert-root-path` 2. 保留旧证书一段时间,不要立即删除 3. 观察通知头中的序列号是否已经切换 4. 确认不再使用旧序列号后,再清理旧证书 ### 通知失败时怎么响应 建议: - 验签失败、时间戳过期、请求头缺失时返回 `400` - 服务内部异常时返回 `500` - 已成功处理或重复通知时返回 `204` ## 文档导航 - 我是 SDK 使用者 看 [docs/DEVELOPER_GUIDE.md](/Users/juming/Documents/Workspase/coding/touscm/authapi-sdk/docs/DEVELOPER_GUIDE.md) - 我要接第三方通知验签 看 [docs/WECHATPAY_NOTIFY_GUIDE.md](/Users/juming/Documents/Workspase/coding/touscm/authapi-sdk/docs/WECHATPAY_NOTIFY_GUIDE.md) - 我要理解项目内部约定 看 [docs/PROJECT_SPEC.md](/Users/juming/Documents/Workspase/coding/touscm/authapi-sdk/docs/PROJECT_SPEC.md) - 我要参与维护或提交代码 看 [CONTRIBUTING.md](/Users/juming/Documents/Workspase/coding/touscm/authapi-sdk/CONTRIBUTING.md) - 我要看这轮变更总结 看 [CHANGELOG.md](/Users/juming/Documents/Workspase/coding/touscm/authapi-sdk/CHANGELOG.md) - 我要直接复用 PR 描述 看 [docs/PR_SUMMARY.md](/Users/juming/Documents/Workspase/coding/touscm/authapi-sdk/docs/PR_SUMMARY.md) ## 发布说明 - 当前仓库版本:`1.0.0` - 如对外协议、配置项或证书命名规则发生变化,发布前应同步更新 README、接入指南和项目规范