# 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、接入指南和项目规范