@uno-org/request-signing
v0.0.4
Published
Shared request signing protocol utilities for Uno clients
Readme
@uno-org/request-signing
请求签名工具包。它帮你统一生成签名 header,并提供 React Native 和 Web 两个平台的 RSA signer。
你通常只需要关心三件事:
- 创建平台 signer。
- 登录时把 signer 的
publicKey传给后端。 - 每个请求用同一个 signer 调
createSignedRequest()注入 header。
安装
Web 项目:
npm install @uno-org/request-signingReact Native 项目:
npm install @uno-org/request-signing react-native-rsa-native react-native-device-infoRN 的两个 native 包需要由业务 app 安装,这样它们才能正常 autolinking。业务代码不需要直接 import 这两个 native 包。
入口
// 通用协议和请求签名
import { createSignedRequest } from '@uno-org/request-signing';
// React Native signer
import { createReactNativeDeviceSigner } from '@uno-org/request-signing/react-native';
// Web signer
import { createWebCryptoRsaDeviceSigner } from '@uno-org/request-signing/web';不要在 Web 代码里 import /react-native,也不要在 RN 代码里 import /web。
React Native 接入
先创建 signer。storage 和 cipher 由业务传入,方便复用项目已有的存储和加密方式。
import { createReactNativeDeviceSigner } from '@uno-org/request-signing/react-native';
const signer = createReactNativeDeviceSigner({
storage,
cipher: {
encrypt,
decrypt,
},
privateKeyStorageKey: 'signature_private',
publicKeyStorageKey: 'signature_public',
keySize: 1024,
});登录时,必须从当前 signer 读取公钥:
const publicKey = await signer.getPublicKey();
await login({
phoneNumber,
verifyCode,
publicKey,
});请求拦截器里复用同一个 signer:
import {
EMPTY_BODY_SHA256,
createSignedRequest,
stringifyRequestBody,
} from '@uno-org/request-signing';
async function createBodyHash(data: unknown) {
const body = stringifyRequestBody(data);
if (!body) return EMPTY_BODY_SHA256;
return sha256Hex(body);
}
const signed = await createSignedRequest({
signer,
method: config.method || 'get',
url: axiosInstance.getUri(config),
bodyHash: await createBodyHash(config.data),
});
Object.entries(signed.headers).forEach(([key, value]) => {
headers.set(key, value);
});当你需要重置签名 key 时,必须同步清理 token,并在下一次登录前重新读取 publicKey:
await signer.clear();
await removeAccessToken();
const publicKey = await signer.getPublicKey();RN RSA signer 支持用当前 public key 做本地自检。这个能力适合在启动时确认本地 private key 和 public key 是否仍是一组:
const message = 'signature-health-check';
const signature = await signer.sign(message);
const verified = await signer.verify(message, signature);需要校验外部 public key 时,也可以显式传入第三个参数:
const verified = await signer.verify(message, signature, publicKey);Web 接入
Web 侧使用 WebCrypto signer。业务需要提供 keyStore,决定 key pair 如何持久化。
import { createWebCryptoRsaDeviceSigner } from '@uno-org/request-signing/web';
const signer = createWebCryptoRsaDeviceSigner({
keyStore,
getDeviceId,
});然后和 RN 一样,登录传 publicKey,请求时调用 createSignedRequest()。
const publicKey = await signer.getPublicKey();
await login({ publicKey });
const signed = await createSignedRequest({
signer,
method: 'get',
url: '/user/info',
});WebCrypto 需要 HTTPS 或 localhost。业务侧在不支持 crypto 的环境里需要阻止签名请求或走降级逻辑。
公钥一致性
这是最容易出问题的地方:登录时传给后端的 publicKey,必须和后续签名请求用的 private key 属于同一组 key pair。
正确做法:
const publicKey = await signer.getPublicKey();
await login({ publicKey });
await createSignedRequest({
signer,
method,
url,
bodyHash,
});不要把 publicKey 长期缓存成业务状态后直接用于登录。退出登录、token 过期、重置 key 时,如果只删 storage 但内存 signer 还拿着旧 key,就可能出现 token 中的 public key 和本地 private key 不匹配,后续接口会一直验签失败。
协议说明
createSignedRequest() 生成的签名内容固定为:
METHOD
PATH_WITH_QUERY
BODY_SHA256
TIMESTAMP
NONCE生成的 header 包括:
x-device-idx-timestampx-noncex-body-sha256x-signaturex-key-id
服务端验签时必须使用同样的 body stringify 和 hash 规则。
发布前检查
pnpm --filter @uno-org/request-signing typecheck
pnpm exec vitest run packages/request-signing/src/index.test.ts packages/request-signing/src/signer.test.ts packages/request-signing/src/react-native-rsa-signer.test.ts packages/request-signing/src/react-native/index.test.ts packages/request-signing/src/web-crypto-rsa-signer.test.ts
pnpm --filter @uno-org/request-signing build
npm pack --dry-run --json发布前确认 npm tarball 中包含:
dist/index.*dist/react-native/*dist/web/*README.md
