Skip to content

Commit 451ba65

Browse files
committed
📝 添加API验签组件文档
1 parent 4fee663 commit 451ba65

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

docs/.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export default defineConfig({
8484
{text: 'WebSocket 组件', link: "/guide/feature/websocket"},
8585
{text: 'XSS 防注入组件', link: "/guide/feature/xss"},
8686
{text: '布隆过滤器', link: "/guide/feature/bloom-filter"},
87+
{text: 'API验签组件', link: "/guide/feature/api-signature"},
8788
],
8889
},
8990
{
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# API验签组件
2+
3+
> Since 2.0.0
4+
5+
## 简介
6+
7+
为确保接口调用者的身份合法性并防止请求篡改,项目对外提供的接口通常需要添加验签处理。当前组件定义了一套签名规则,接口提供方可轻松接入以保护接口安全。
8+
9+
## 签名流程概述
10+
11+
应用接入方在使用接口前,需先向接口提供方申请一对 **AccessKey****SecretKey** 作为身份凭证。
12+
13+
在每次调用接口时,接入方需根据请求参数构建签名串,使用 MD5 算法生成签名值。签名值将与其他认证信息一同添加至 HTTP 请求头中。
14+
15+
### 签名串构建规则
16+
17+
签名串是生成签名值的核心数据,需按照以下顺序和规则拼接各参数:
18+
19+
1. **HTTP Method**
20+
21+
请求的 HTTP 方法,大写字母形式(如:`POST``GET`)。
22+
23+
2. **HTTP Request** **URI**
24+
25+
请求的 URI,包括查询字符串(Query String)部分,但不包含域名。
26+
27+
例如:对于 `http://www.xxx.com/order?name=zhangsan` ,Request URI 为 `/order?name=zhangsan`
28+
29+
3. **Request Body**
30+
31+
请求体内容。若请求没有 Body,则该参数和分隔符 `#` 均不参与签名。
32+
33+
4. **Timestamp**
34+
35+
请求发起时的 UNIX 时间戳(从1970年1月1日 00:00:00 UTC 起的总毫秒数)。平台将拒绝处理超时请求,确保系统时间准确。
36+
37+
5. **Nonce**
38+
39+
随机生成的 32 位字符串,用于防止重放攻击。
40+
41+
6. **AccessKey**
42+
43+
接入方的访问标识,由平台提供。
44+
45+
7. **SecretKey**
46+
47+
访问密钥,用于签名生成,但 **** 在请求中携带。
48+
49+
### 签名值计算
50+
51+
将按上述顺序拼接好的签名串,使用 MD5 算法进行摘要计算,得到小写签名值。
52+
53+
签名串的格式为:
54+
55+
```
56+
{HTTP_METHOD}#{HTTP_URI}#{RequestBody}#{Timestamp}#{Nonce}#{AccessKey}#{SecretKey}
57+
```
58+
59+
### 请求头设置
60+
61+
签名计算完成后,将以下参数添加至 HTTP 请求头:
62+
63+
- **X-Access-Key**:申请到的 AccessKey
64+
- **X-Timestamp**:请求的时间戳
65+
- **X-Nonce**:32 位随机字符串
66+
- **X-Signature**:MD5 计算得到的签名值
67+
68+
## 具体示例
69+
70+
### 请求参数
71+
72+
假设以下请求参数:
73+
74+
- **HTTP_METHOD**: `GET`
75+
- **HTTP_URI**: `/product/add`
76+
- **RequestBody**: `{"productId":1}`
77+
- **Timestamp**: `1710924789130`
78+
- **Nonce**: `Js3eTl1I7oP5g8YpDnYX2danVrqRrqZg`
79+
- **SecretKey**: `0cec22334545eea97776c7d5e39`
80+
81+
### 签名计算
82+
83+
拼接后的签名串为:
84+
85+
```Plain
86+
GET#/product/add#{"productId":1}#1710924789130#Js3eTl1I7oP5g8YpDnYX2danVrqRrqZg#0cecd9245cc1107d8eea97776c7d5e39#0cec22334545eea97776c7d5e39
87+
```
88+
89+
MD5 计算后的签名值为:
90+
91+
```Plain
92+
0cecd9245cc1107d8eea97776c7d5e39
93+
```
94+
95+
### 最终请求头
96+
97+
设置 HTTP 请求头如下:
98+
99+
```Plain
100+
X-Access-Key: 0d30cfd0929a46ffb1200955d35bf18f
101+
X-Timestamp: 1710924789130
102+
X-Nonce: Js3eTl1I7oP5g8YpDnYX2danVrqRrqZg
103+
X-Signature: 0cecd9245cc1107d8eea97776c7d5e39
104+
```
105+
106+
## 组件接入
107+
108+
### 依赖引入
109+
110+
组件已经推送到私服,可以直接按坐标引入,下面提供了 maven 的引入示例:
111+
112+
spring boot 环境下,可以直接引入 ballcat-spring-boot-starter-apisignature 依赖包,该 starter 会在应用启动时进行自动配置。
113+
114+
```XML
115+
116+
<dependency>
117+
<groupId>org.ballcat</groupId>
118+
<artifactId>ballcat-spring-boot-starter-apisignature</artifactId>
119+
</dependency>
120+
```
121+
122+
### 代码实现
123+
124+
组件中有两个重要的实体,需要开发者自行实现,并注册到 Spring 容器中:
125+
126+
**ApiKeyManager**
127+
128+
对于 **AccessKey****SecretKey 、Subject** 的一个管理类,`Subject` 是调用方主体的一个抽象表示。在验签过程中,需要根据 *
129+
*AccessKey** 查找到对应的 `Subject` ,再根据 `Subject` 获取到对应的 **SecretKey**,进行签名校验。
130+
131+
```
132+
public interface ApiKeyManager {
133+
/**
134+
* 根据传入的 Access Key 获取用户主体信息
135+
* @param accessKey Access Key
136+
* @return subject
137+
*/
138+
Object getSubject(String accessKey);
139+
140+
/**
141+
* 根据用户主体获取到对应的 secretKey.
142+
* @param subject 用户主体
143+
* @return 如果找到对应的 secretKey,则返回该 secretKey;否则返回 null
144+
*/
145+
String getSecretKey(Object subject);
146+
}
147+
```
148+
149+
**NonceStore**
150+
用于存储随机和校验字符串,防止请求重放。
151+
152+
```Java
153+
public interface NonceStore {
154+
/**
155+
* 存储随机字符串,如果其不存在的话。
156+
* @param nonce 随机字符串
157+
* @param timeout 存储的过期时长
158+
* @param timeUnit 过期时长的单位
159+
* @return 如果当前随机字符串已存在,则返回 false.
160+
*/
161+
boolean storeIfAbsent(String nonce, Long timeout, TimeUnit timeUnit);
162+
}
163+
```
164+
165+
`Subject` 的具体类型和实现由开发者自行定义,`Subject` 会在验签成功后存入线程上线文,开发者可以通过 `SubjectHolder`
166+
随时获取主体信息。所以建议在主体对象中存储一些常用的主体属性,如userId 等。
167+
168+
### 组件配置
169+
170+
| 配置项 | 数据类型 | 默认值 | 描述 |
171+
|---------------------------------------------|--------------|------------------|--------------------------------------------------|
172+
| `wd.api.signature.include-url-pattens` | List\<String\> | `["/**"]` | 需要进行签名校验的 URL 规则列表。 |
173+
| `wd.api.signature.exclude-url-pattens` | List\<String\> | `[]` | 不需要进行签名校验的 URL 规则列表,优先级高于 `include-url-pattens`|
174+
| `wd.api.signature.uri-prefix` | String || 请求 URI 的前缀字符串,当经过网关或 Nginx 时,恢复被重写的 URI。 |
175+
| `wd.api.signature.signature-header` | String | `X-Signature` | 存放签名信息的请求头名称。 |
176+
| `wd.api.signature.timestamp-header` | String | `X-Timestamp` | 存放请求时间戳的请求头名称。 |
177+
| `wd.api.signature.nonce-header` | String | `X-Nonce` | 存放32位随机字符串的请求头名称。 |
178+
| `wd.api.signature.access-key-header` | String | `X-Access-Key` | 存放请求方标识的请求头名称。 |
179+
| `wd.api.signature.timestamp-diff-threshold` | long | `300000` (5 分钟) | 请求时间戳和服务器时间戳允许的最大时间差(毫秒)。 |
180+
| `wd.api.signature.nonce-timeout` | long | `900000` (15 分钟) | `nonce` 随机字符串的存储过期时长(毫秒)。 |
181+
| `wd.api.signature.nonce-timeout-unit` | TimeUnit | `MILLISECONDS` | `nonce-timeout` 的时间单位,默认为毫秒。 |

0 commit comments

Comments
 (0)