huace-aigc-auth-client 1.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- huace_aigc_auth_client/__init__.py +101 -0
- huace_aigc_auth_client/legacy_adapter.py +625 -0
- huace_aigc_auth_client/sdk.py +726 -0
- huace_aigc_auth_client/webhook.py +128 -0
- huace_aigc_auth_client-1.1.7.dist-info/METADATA +797 -0
- huace_aigc_auth_client-1.1.7.dist-info/RECORD +9 -0
- huace_aigc_auth_client-1.1.7.dist-info/WHEEL +5 -0
- huace_aigc_auth_client-1.1.7.dist-info/licenses/LICENSE +22 -0
- huace_aigc_auth_client-1.1.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: huace-aigc-auth-client
|
|
3
|
+
Version: 1.1.7
|
|
4
|
+
Summary: 华策AIGC Auth Client - 提供 Token 验证、用户信息获取、权限检查、旧系统接入等功能
|
|
5
|
+
Author-email: Huace <support@huace.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/huace/huace-aigc-auth-client
|
|
8
|
+
Project-URL: Repository, https://github.com/huace/huace-aigc-auth-client
|
|
9
|
+
Keywords: aigc,auth,huace,sdk,authentication
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Requires-Python: >=3.7
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: requests>=2.20.0
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# AIGC Auth Python SDK
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/huace-aigc-auth-client/)
|
|
29
|
+
[](https://pypi.org/project/huace-aigc-auth-client/)
|
|
30
|
+
|
|
31
|
+
Python 后端服务接入华策 AIGC 鉴权中心的 SDK 工具包。
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install huace-aigc-auth-client
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 环境变量配置
|
|
40
|
+
|
|
41
|
+
在项目根目录创建 `.env` 文件:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# 必填:应用 ID 和密钥(在鉴权中心创建应用后获取)
|
|
45
|
+
AIGC_AUTH_APP_ID=your_app_id
|
|
46
|
+
AIGC_AUTH_APP_SECRET=your_app_secret
|
|
47
|
+
|
|
48
|
+
# 可选:鉴权服务地址(默认为生产环境)- 测试环境鉴权地址:https://auth-test.aigc.huacemedia.com/aigc-auth/api/v1
|
|
49
|
+
AIGC_AUTH_BASE_URL=https://aigc-auth.huacemedia.com/aigc-auth/api/v1
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
如需通过 Nginx 代理:
|
|
53
|
+
|
|
54
|
+
```nginx
|
|
55
|
+
location /aigc-auth/ {
|
|
56
|
+
proxy_pass https://aigc-auth.huacemedia.com/aigc-auth/;
|
|
57
|
+
proxy_set_header Host $host;
|
|
58
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
59
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
60
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 快速开始
|
|
65
|
+
|
|
66
|
+
### 1. 初始化客户端
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from huace_aigc_auth_client import AigcAuthClient
|
|
70
|
+
|
|
71
|
+
# 方式一:从环境变量读取配置
|
|
72
|
+
client = AigcAuthClient()
|
|
73
|
+
|
|
74
|
+
# 方式二:直接传入参数
|
|
75
|
+
client = AigcAuthClient(
|
|
76
|
+
app_id="your_app_id",
|
|
77
|
+
app_secret="your_app_secret",
|
|
78
|
+
base_url="https://aigc-auth.huacemedia.com/aigc-auth/api/v1"
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. 验证 Token
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
result = client.verify_token(token)
|
|
86
|
+
|
|
87
|
+
if result.valid:
|
|
88
|
+
print(f"用户 ID: {result.user_id}")
|
|
89
|
+
print(f"用户名: {result.username}")
|
|
90
|
+
print(f"过期时间: {result.expires_at}")
|
|
91
|
+
else:
|
|
92
|
+
print("Token 无效")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 3. 获取用户信息
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from huace_aigc_auth_client import AigcAuthClient, AigcAuthError
|
|
99
|
+
|
|
100
|
+
client = AigcAuthClient()
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
user = client.get_user_info(token)
|
|
104
|
+
|
|
105
|
+
print(f"用户名: {user.username}")
|
|
106
|
+
print(f"昵称: {user.nickname}")
|
|
107
|
+
print(f"邮箱: {user.email}")
|
|
108
|
+
print(f"角色: {user.roles}")
|
|
109
|
+
print(f"权限: {user.permissions}")
|
|
110
|
+
|
|
111
|
+
# 检查角色(在 Auth 里面配置)
|
|
112
|
+
if user.has_role("admin") or user.is_admin:
|
|
113
|
+
print("是管理员")
|
|
114
|
+
|
|
115
|
+
# 检查权限(在 Auth 里面配置)
|
|
116
|
+
if user.has_permission("user:write"):
|
|
117
|
+
print("有用户写权限")
|
|
118
|
+
|
|
119
|
+
except AigcAuthError as e:
|
|
120
|
+
print(f"错误: {e.message}")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 4. 批量检查权限
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
results = client.check_permissions(token, ["user:read", "user:write", "admin:access"])
|
|
127
|
+
|
|
128
|
+
for permission, has_permission in results.items():
|
|
129
|
+
print(f"{permission}: {'✓' if has_permission else '✗'}")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## FastAPI 集成
|
|
133
|
+
|
|
134
|
+
### 方式一:使用中间件(推荐)
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
138
|
+
from huace_aigc_auth_client import AigcAuthClient, AuthMiddleware
|
|
139
|
+
|
|
140
|
+
app = FastAPI()
|
|
141
|
+
|
|
142
|
+
# 初始化客户端和中间件
|
|
143
|
+
client = AigcAuthClient()
|
|
144
|
+
auth_middleware = AuthMiddleware(
|
|
145
|
+
client,
|
|
146
|
+
exclude_paths=["/health", "/docs", "/openapi.json"],
|
|
147
|
+
exclude_prefixes=["/public/"]
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# 注册中间件
|
|
151
|
+
@app.middleware("http")
|
|
152
|
+
async def auth(request: Request, call_next):
|
|
153
|
+
return await auth_middleware.fastapi_middleware(request, call_next)
|
|
154
|
+
|
|
155
|
+
# 在路由中获取用户信息
|
|
156
|
+
@app.get("/me")
|
|
157
|
+
async def get_current_user(request: Request):
|
|
158
|
+
user = request.state.user_info
|
|
159
|
+
return {
|
|
160
|
+
"username": user.username,
|
|
161
|
+
"roles": user.roles
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@app.get("/admin")
|
|
165
|
+
async def admin_only(request: Request):
|
|
166
|
+
user = request.state.user_info
|
|
167
|
+
if not user.has_role("admin"):
|
|
168
|
+
raise HTTPException(status_code=403, detail="需要管理员权限")
|
|
169
|
+
return {"message": "欢迎管理员"}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 方式二:使用依赖注入
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from fastapi import FastAPI, Depends, HTTPException
|
|
176
|
+
from huace_aigc_auth_client import AigcAuthClient, create_fastapi_auth_dependency, UserInfo
|
|
177
|
+
|
|
178
|
+
app = FastAPI()
|
|
179
|
+
client = AigcAuthClient()
|
|
180
|
+
|
|
181
|
+
# 创建认证依赖
|
|
182
|
+
get_current_user = create_fastapi_auth_dependency(client)
|
|
183
|
+
|
|
184
|
+
@app.get("/me")
|
|
185
|
+
async def get_me(user: UserInfo = Depends(get_current_user)):
|
|
186
|
+
return {"username": user.username}
|
|
187
|
+
|
|
188
|
+
# 创建权限检查依赖
|
|
189
|
+
def require_permission(permission: str):
|
|
190
|
+
async def check(user: UserInfo = Depends(get_current_user)):
|
|
191
|
+
if not user.has_permission(permission):
|
|
192
|
+
raise HTTPException(status_code=403, detail="权限不足")
|
|
193
|
+
return user
|
|
194
|
+
return check
|
|
195
|
+
|
|
196
|
+
@app.get("/users")
|
|
197
|
+
async def list_users(user: UserInfo = Depends(require_permission("user:read"))):
|
|
198
|
+
return {"users": [...]}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 方式三:使用装饰器
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from fastapi import FastAPI, Request
|
|
205
|
+
from huace_aigc_auth_client import AigcAuthClient, require_auth, UserInfo
|
|
206
|
+
|
|
207
|
+
app = FastAPI()
|
|
208
|
+
client = AigcAuthClient()
|
|
209
|
+
|
|
210
|
+
@app.get("/protected")
|
|
211
|
+
@require_auth(client)
|
|
212
|
+
async def protected_route(request: Request, user_info: UserInfo):
|
|
213
|
+
return {"user": user_info.username}
|
|
214
|
+
|
|
215
|
+
@app.get("/admin")
|
|
216
|
+
@require_auth(client, permissions=["admin:access"])
|
|
217
|
+
async def admin_route(request: Request, user_info: UserInfo):
|
|
218
|
+
return {"admin": True}
|
|
219
|
+
|
|
220
|
+
# 只需要任意一个权限
|
|
221
|
+
@app.get("/editor")
|
|
222
|
+
@require_auth(client, permissions=["article:write", "article:edit"], any_permission=True)
|
|
223
|
+
async def editor_route(request: Request, user_info: UserInfo):
|
|
224
|
+
return {"editor": True}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Flask 集成
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from flask import Flask, g, jsonify
|
|
231
|
+
from functools import wraps
|
|
232
|
+
from huace_aigc_auth_client import AigcAuthClient, AuthMiddleware
|
|
233
|
+
|
|
234
|
+
app = Flask(__name__)
|
|
235
|
+
|
|
236
|
+
# 初始化客户端和中间件
|
|
237
|
+
client = AigcAuthClient()
|
|
238
|
+
auth_middleware = AuthMiddleware(
|
|
239
|
+
client,
|
|
240
|
+
exclude_paths=["/health", "/login"],
|
|
241
|
+
exclude_prefixes=["/public/"]
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# 注册 before_request
|
|
245
|
+
@app.before_request
|
|
246
|
+
def before_request():
|
|
247
|
+
return auth_middleware.flask_before_request()
|
|
248
|
+
|
|
249
|
+
# 在路由中获取用户信息
|
|
250
|
+
@app.route("/me")
|
|
251
|
+
def get_me():
|
|
252
|
+
user = g.user_info
|
|
253
|
+
return jsonify({
|
|
254
|
+
"username": user.username,
|
|
255
|
+
"roles": user.roles
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
# 权限检查装饰器
|
|
259
|
+
def require_permission(permission):
|
|
260
|
+
def decorator(f):
|
|
261
|
+
@wraps(f)
|
|
262
|
+
def decorated_function(*args, **kwargs):
|
|
263
|
+
user = g.user_info
|
|
264
|
+
if not user.has_permission(permission):
|
|
265
|
+
return jsonify({"error": "权限不足"}), 403
|
|
266
|
+
return f(*args, **kwargs)
|
|
267
|
+
return decorated_function
|
|
268
|
+
return decorator
|
|
269
|
+
|
|
270
|
+
@app.route("/admin")
|
|
271
|
+
@require_permission("admin:access")
|
|
272
|
+
def admin_only():
|
|
273
|
+
return jsonify({"message": "欢迎管理员"})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## API 参考
|
|
277
|
+
|
|
278
|
+
### UserInfo 对象
|
|
279
|
+
|
|
280
|
+
| 属性 | 类型 | 说明 |
|
|
281
|
+
|------|------|------|
|
|
282
|
+
| `id` | int | 用户 ID |
|
|
283
|
+
| `username` | str | 用户名 |
|
|
284
|
+
| `nickname` | str | 昵称 |
|
|
285
|
+
| `email` | str | 邮箱 |
|
|
286
|
+
| `phone` | str | 手机号 |
|
|
287
|
+
| `avatar` | str | 头像 URL |
|
|
288
|
+
| `roles` | List[str] | 角色代码列表 |
|
|
289
|
+
| `permissions` | List[str] | 权限代码列表 |
|
|
290
|
+
| `department` | str | 部门 |
|
|
291
|
+
| `company` | str | 公司 |
|
|
292
|
+
|
|
293
|
+
| 方法 | 说明 |
|
|
294
|
+
|------|------|
|
|
295
|
+
| `has_role(role)` | 检查是否拥有指定角色 |
|
|
296
|
+
| `has_permission(permission)` | 检查是否拥有指定权限 |
|
|
297
|
+
| `has_any_permission(permissions)` | 检查是否拥有任意一个权限 |
|
|
298
|
+
| `has_all_permissions(permissions)` | 检查是否拥有所有权限 |
|
|
299
|
+
|
|
300
|
+
### 异常处理
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from huace_aigc_auth_client import AigcAuthClient, AigcAuthError
|
|
304
|
+
|
|
305
|
+
client = AigcAuthClient()
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
user = client.get_user_info(token)
|
|
309
|
+
except AigcAuthError as e:
|
|
310
|
+
print(f"错误码: {e.code}")
|
|
311
|
+
print(f"错误信息: {e.message}")
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
| 错误码 | 说明 |
|
|
315
|
+
|--------|------|
|
|
316
|
+
| 401 | 未授权(Token 无效或已过期) |
|
|
317
|
+
| 403 | 禁止访问(用户被禁用或权限不足) |
|
|
318
|
+
| 404 | 用户不存在 |
|
|
319
|
+
| -1 | 网络请求失败 |
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 旧系统接入(用户同步)
|
|
324
|
+
|
|
325
|
+
如果你的系统已有用户表,可通过 SDK 提供的「旧系统适配器」实现低成本接入,**无需修改历史代码和表结构**。
|
|
326
|
+
|
|
327
|
+
### 接入原理
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
331
|
+
│ aigc-auth │────▶│ SDK 同步层 │────▶│ 旧系统 │
|
|
332
|
+
│ (鉴权中心) │ │ (字段映射) │ │ (用户表) │
|
|
333
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
334
|
+
│ │ │
|
|
335
|
+
│ 1. 初始化同步 │ │
|
|
336
|
+
│◀──────────────────────────────────────────────│
|
|
337
|
+
│ (批量同步旧用户到 auth) │
|
|
338
|
+
│ │ │
|
|
339
|
+
│ 2. 增量同步 │ │
|
|
340
|
+
│──────────────────────▶│──────────────────────▶│
|
|
341
|
+
│ (auth 新用户自动同步到旧系统) │
|
|
342
|
+
│ │ │
|
|
343
|
+
│ 3. Webhook 推送 │ │
|
|
344
|
+
│──────────────────────────────────────────────▶│
|
|
345
|
+
│ (用户变更主动通知) │
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### 快速开始
|
|
349
|
+
|
|
350
|
+
#### 1. 配置环境变量
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
# .env 文件
|
|
354
|
+
AIGC_AUTH_APP_ID=your_app_id
|
|
355
|
+
AIGC_AUTH_APP_SECRET=your_app_secret
|
|
356
|
+
AIGC_AUTH_BASE_URL=https://aigc-auth.huacemedia.com/aigc-auth/api/v1
|
|
357
|
+
|
|
358
|
+
# 同步配置
|
|
359
|
+
AIGC_AUTH_SYNC_ENABLED=true
|
|
360
|
+
AIGC_AUTH_SYNC_PASSWORD=通用密码
|
|
361
|
+
AIGC_AUTH_WEBHOOK_URL=https://your-domain.com/api/v1/webhook/auth
|
|
362
|
+
AIGC_AUTH_WEBHOOK_SECRET=your_secret
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### 2. 创建字段映射
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
from huace_aigc_auth_client import (
|
|
369
|
+
FieldMapping,
|
|
370
|
+
SyncConfig,
|
|
371
|
+
PasswordMode,
|
|
372
|
+
create_sync_config
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# 定义字段映射
|
|
376
|
+
field_mappings = [
|
|
377
|
+
FieldMapping(
|
|
378
|
+
auth_field="username",
|
|
379
|
+
legacy_field="username",
|
|
380
|
+
required=True
|
|
381
|
+
),
|
|
382
|
+
FieldMapping(
|
|
383
|
+
auth_field="email",
|
|
384
|
+
legacy_field="email"
|
|
385
|
+
),
|
|
386
|
+
FieldMapping(
|
|
387
|
+
auth_field="nickname",
|
|
388
|
+
legacy_field="nickname"
|
|
389
|
+
),
|
|
390
|
+
# 状态字段转换:auth 的 status 映射到旧系统的 is_active
|
|
391
|
+
FieldMapping(
|
|
392
|
+
auth_field="status",
|
|
393
|
+
legacy_field="is_active",
|
|
394
|
+
transform_to_legacy=lambda s: s == "active",
|
|
395
|
+
transform_to_auth=lambda a: "active" if a else "disabled"
|
|
396
|
+
),
|
|
397
|
+
# 角色字段转换:auth 的 roles 列表映射到旧系统的 role 字符串
|
|
398
|
+
FieldMapping(
|
|
399
|
+
auth_field="roles",
|
|
400
|
+
legacy_field="role",
|
|
401
|
+
transform_to_legacy=lambda r: r[0] if r else "viewer",
|
|
402
|
+
transform_to_auth=lambda r: [r] if r else ["viewer"]
|
|
403
|
+
),
|
|
404
|
+
]
|
|
405
|
+
|
|
406
|
+
# 创建同步配置
|
|
407
|
+
sync_config = create_sync_config(
|
|
408
|
+
field_mappings=field_mappings,
|
|
409
|
+
password_mode=PasswordMode.UNIFIED,
|
|
410
|
+
unified_password="通用密码",
|
|
411
|
+
webhook_url="https://your-domain.com/api/v1/webhook/auth",
|
|
412
|
+
)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
#### 3. 实现旧系统适配器
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
from huace_aigc_auth_client import LegacySystemAdapter, LegacyUserData
|
|
419
|
+
|
|
420
|
+
class MyLegacyAdapter(LegacySystemAdapter):
|
|
421
|
+
"""实现与旧系统用户表的交互"""
|
|
422
|
+
|
|
423
|
+
def __init__(self, db, sync_config):
|
|
424
|
+
super().__init__(sync_config)
|
|
425
|
+
self.db = db
|
|
426
|
+
|
|
427
|
+
def get_user_by_unique_field(self, username: str):
|
|
428
|
+
"""通过用户名获取旧系统用户"""
|
|
429
|
+
user = self.db.query(User).filter(User.username == username).first()
|
|
430
|
+
if not user:
|
|
431
|
+
return None
|
|
432
|
+
return LegacyUserData({
|
|
433
|
+
"id": user.id,
|
|
434
|
+
"username": user.username,
|
|
435
|
+
"email": user.email,
|
|
436
|
+
# ... 其他字段
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
def create_user(self, user_data: dict):
|
|
440
|
+
"""在旧系统创建用户"""
|
|
441
|
+
user = User(
|
|
442
|
+
username=user_data["username"],
|
|
443
|
+
email=user_data.get("email"),
|
|
444
|
+
hashed_password=hash_password(user_data["password"]),
|
|
445
|
+
# ... 其他字段
|
|
446
|
+
)
|
|
447
|
+
self.db.add(user)
|
|
448
|
+
self.db.commit()
|
|
449
|
+
return user.id
|
|
450
|
+
|
|
451
|
+
def update_user(self, username: str, user_data: dict):
|
|
452
|
+
"""更新旧系统用户"""
|
|
453
|
+
user = self.db.query(User).filter(User.username == username).first()
|
|
454
|
+
if user:
|
|
455
|
+
for key, value in user_data.items():
|
|
456
|
+
setattr(user, key, value)
|
|
457
|
+
self.db.commit()
|
|
458
|
+
|
|
459
|
+
def get_all_users(self):
|
|
460
|
+
"""获取所有用户(用于初始化同步)"""
|
|
461
|
+
users = self.db.query(User).all()
|
|
462
|
+
return [LegacyUserData({"username": u.username, ...}) for u in users]
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### 4. 集成到登录流程
|
|
466
|
+
|
|
467
|
+
```python
|
|
468
|
+
from huace_aigc_auth_client import AigcAuthClient, UserSyncService
|
|
469
|
+
|
|
470
|
+
client = AigcAuthClient()
|
|
471
|
+
adapter = MyLegacyAdapter(db, sync_config)
|
|
472
|
+
sync_service = UserSyncService(client, adapter)
|
|
473
|
+
|
|
474
|
+
# 在获取用户信息后调用同步
|
|
475
|
+
@app.middleware("http")
|
|
476
|
+
async def auth_middleware(request, call_next):
|
|
477
|
+
response = await auth.fastapi_middleware(request, call_next)
|
|
478
|
+
|
|
479
|
+
# 登录成功后,同步用户到旧系统
|
|
480
|
+
if hasattr(request.state, "user_info"):
|
|
481
|
+
user_info = request.state.user_info
|
|
482
|
+
sync_service.sync_on_login(user_info)
|
|
483
|
+
|
|
484
|
+
return response
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
#### 5. 添加 Webhook 接收端点
|
|
488
|
+
|
|
489
|
+
**推荐方式:使用 SDK 提供的通用 Webhook 路由**
|
|
490
|
+
|
|
491
|
+
```python
|
|
492
|
+
from fastapi import APIRouter
|
|
493
|
+
from huace_aigc_auth_client import register_webhook_router
|
|
494
|
+
|
|
495
|
+
api_router = APIRouter()
|
|
496
|
+
|
|
497
|
+
# 定义 webhook 处理函数
|
|
498
|
+
async def handle_webhook(data: dict) -> dict:
|
|
499
|
+
"""
|
|
500
|
+
处理来自 aigc-auth 的 webhook 通知
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
data: webhook 数据,包含 event 和 data 字段
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
dict: 处理结果
|
|
507
|
+
"""
|
|
508
|
+
from your_app.db import get_db_session
|
|
509
|
+
|
|
510
|
+
event = data.get("event")
|
|
511
|
+
user_data = data.get("data", {})
|
|
512
|
+
|
|
513
|
+
# 创建数据库会话
|
|
514
|
+
async with get_db_session() as db:
|
|
515
|
+
adapter = MyLegacyAdapter(db, sync_config)
|
|
516
|
+
|
|
517
|
+
if event == "user.created":
|
|
518
|
+
# 处理用户创建事件
|
|
519
|
+
if not adapter.get_user_by_unique_field(user_data["username"]):
|
|
520
|
+
legacy_data = adapter.transform_auth_to_legacy(user_data)
|
|
521
|
+
legacy_data["password"] = sync_config.unified_password
|
|
522
|
+
adapter.create_user(legacy_data)
|
|
523
|
+
await db.commit()
|
|
524
|
+
|
|
525
|
+
elif event == "user.updated":
|
|
526
|
+
# 处理用户更新事件
|
|
527
|
+
legacy_data = adapter.transform_auth_to_legacy(user_data)
|
|
528
|
+
adapter.update_user(user_data["username"], legacy_data)
|
|
529
|
+
await db.commit()
|
|
530
|
+
|
|
531
|
+
return {"status": "ok", "event": event}
|
|
532
|
+
|
|
533
|
+
# 注册 webhook 路由(自动处理签名验证)
|
|
534
|
+
register_webhook_router(
|
|
535
|
+
api_router,
|
|
536
|
+
handler=handle_webhook,
|
|
537
|
+
prefix="/webhook", # 可选,默认 "/webhook"
|
|
538
|
+
secret_env_key="aigc-auth-webhook-secret", # 可选,在 auth 后台配置
|
|
539
|
+
tags=["Webhook"] # 可选,默认 ["Webhook"]
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
# webhook 端点将自动创建在: /webhook/auth
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**传统方式:手动实现 Webhook 端点**
|
|
546
|
+
|
|
547
|
+
```python
|
|
548
|
+
import hmac
|
|
549
|
+
import hashlib
|
|
550
|
+
import os
|
|
551
|
+
|
|
552
|
+
@app.post("/api/v1/webhook/auth")
|
|
553
|
+
async def receive_auth_webhook(request: Request, db: Session = Depends(get_db)):
|
|
554
|
+
"""接收 aigc-auth 的用户变更通知"""
|
|
555
|
+
# 获取原始请求体
|
|
556
|
+
body = await request.body()
|
|
557
|
+
|
|
558
|
+
# 验证签名
|
|
559
|
+
signature = request.headers.get("X-Webhook-Signature", "")
|
|
560
|
+
secret = os.getenv("AIGC_AUTH_WEBHOOK_SECRET", "")
|
|
561
|
+
|
|
562
|
+
if secret:
|
|
563
|
+
expected = hmac.new(
|
|
564
|
+
secret.encode('utf-8'),
|
|
565
|
+
body,
|
|
566
|
+
hashlib.sha256
|
|
567
|
+
).hexdigest()
|
|
568
|
+
|
|
569
|
+
if not hmac.compare_digest(expected, signature):
|
|
570
|
+
raise HTTPException(status_code=401, detail="Invalid signature")
|
|
571
|
+
|
|
572
|
+
# 解析数据
|
|
573
|
+
data = await request.json()
|
|
574
|
+
event = data.get("event")
|
|
575
|
+
user_data = data.get("data", {})
|
|
576
|
+
|
|
577
|
+
if event == "user.created":
|
|
578
|
+
adapter = MyLegacyAdapter(db, sync_config)
|
|
579
|
+
if not adapter.get_user_by_unique_field(user_data["username"]):
|
|
580
|
+
legacy_data = adapter.transform_auth_to_legacy(user_data)
|
|
581
|
+
legacy_data["password"] = sync_config.unified_password
|
|
582
|
+
adapter.create_user(legacy_data)
|
|
583
|
+
db.commit()
|
|
584
|
+
|
|
585
|
+
elif event == "user.updated":
|
|
586
|
+
adapter = MyLegacyAdapter(db, sync_config)
|
|
587
|
+
legacy_data = adapter.transform_auth_to_legacy(user_data)
|
|
588
|
+
adapter.update_user(user_data["username"], legacy_data)
|
|
589
|
+
db.commit()
|
|
590
|
+
|
|
591
|
+
return {"success": True}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**Webhook 签名验证说明**
|
|
595
|
+
|
|
596
|
+
SDK 的 `register_webhook_router` 会自动处理签名验证:
|
|
597
|
+
- 从请求头 `X-Webhook-Signature` 获取签名
|
|
598
|
+
- 从环境变量读取密钥(默认 `AIGC_AUTH_WEBHOOK_SECRET`)
|
|
599
|
+
- 使用 HMAC-SHA256 算法验证签名
|
|
600
|
+
- 签名不匹配时返回 401 错误
|
|
601
|
+
|
|
602
|
+
#### 6. 执行初始化同步
|
|
603
|
+
|
|
604
|
+
```python
|
|
605
|
+
# 一次性脚本:将旧系统用户同步到 aigc-auth
|
|
606
|
+
async def init_sync():
|
|
607
|
+
adapter = MyLegacyAdapter(db, sync_config)
|
|
608
|
+
users = adapter.get_all_users()
|
|
609
|
+
|
|
610
|
+
for user in users:
|
|
611
|
+
auth_data = adapter.transform_legacy_to_auth(user)
|
|
612
|
+
|
|
613
|
+
# 获取密码(支持元组返回格式)
|
|
614
|
+
password_result = adapter.get_password_for_sync(user)
|
|
615
|
+
if isinstance(password_result, tuple):
|
|
616
|
+
password, is_hashed = password_result
|
|
617
|
+
else:
|
|
618
|
+
password, is_hashed = password_result, False
|
|
619
|
+
|
|
620
|
+
# 根据是否已加密选择不同的字段
|
|
621
|
+
if is_hashed:
|
|
622
|
+
auth_data["password_hashed"] = password # 直接传递已加密密码
|
|
623
|
+
else:
|
|
624
|
+
auth_data["password"] = password # 传递明文密码,服务端会加密
|
|
625
|
+
|
|
626
|
+
client.sync_user_to_auth(auth_data)
|
|
627
|
+
|
|
628
|
+
print(f"同步完成:{len(users)} 个用户")
|
|
629
|
+
|
|
630
|
+
# 运行
|
|
631
|
+
asyncio.run(init_sync())
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### 密码处理策略
|
|
635
|
+
|
|
636
|
+
| 模式 | 说明 | 适用场景 |
|
|
637
|
+
|------|------|----------|
|
|
638
|
+
| `UNIFIED` | 统一初始密码 | 新系统接入,安全性高 |
|
|
639
|
+
| `CUSTOM_MAPPING` | 自定义映射函数 | 需要保留原密码时 |
|
|
640
|
+
| `CUSTOM_MAPPING` + `password_is_hashed=True` | 直接同步已加密密码 | **推荐**,两系统使用相同加密方式 (bcrypt) |
|
|
641
|
+
|
|
642
|
+
```python
|
|
643
|
+
from huace_aigc_auth_client import PasswordMode, create_sync_config
|
|
644
|
+
|
|
645
|
+
# 统一密码模式
|
|
646
|
+
sync_config = create_sync_config(
|
|
647
|
+
password_mode=PasswordMode.UNIFIED,
|
|
648
|
+
unified_password="Abc@123456"
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
# 自定义映射模式(返回明文密码)
|
|
652
|
+
def password_mapper(user_data):
|
|
653
|
+
return user_data.get("password", "default")
|
|
654
|
+
|
|
655
|
+
sync_config = create_sync_config(
|
|
656
|
+
password_mode=PasswordMode.CUSTOM_MAPPING,
|
|
657
|
+
password_mapper=password_mapper
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
# 直接同步已加密密码(推荐,适用于两系统使用相同的 bcrypt 加密)
|
|
661
|
+
def hashed_password_mapper(user_data):
|
|
662
|
+
return user_data.get("hashed_password", "")
|
|
663
|
+
|
|
664
|
+
sync_config = create_sync_config(
|
|
665
|
+
password_mode=PasswordMode.CUSTOM_MAPPING,
|
|
666
|
+
password_mapper=hashed_password_mapper,
|
|
667
|
+
password_is_hashed=True # 标记返回的是已加密密码
|
|
668
|
+
)
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
> **注意**:当 `password_is_hashed=True` 时,SDK 会使用 `password_hashed` 字段传递到服务端,
|
|
672
|
+
> 服务端会直接使用该哈希值而不再重新加密。
|
|
673
|
+
|
|
674
|
+
### 同步方向配置
|
|
675
|
+
|
|
676
|
+
```python
|
|
677
|
+
from huace_aigc_auth_client import SyncDirection, create_sync_config
|
|
678
|
+
|
|
679
|
+
# 仅从 auth 同步到旧系统(默认)
|
|
680
|
+
sync_config = create_sync_config(
|
|
681
|
+
direction=SyncDirection.AUTH_TO_LEGACY
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
# 仅从旧系统同步到 auth(初始化用)
|
|
685
|
+
sync_config = create_sync_config(
|
|
686
|
+
direction=SyncDirection.LEGACY_TO_AUTH
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
# 双向同步(需谨慎使用)
|
|
690
|
+
sync_config = create_sync_config(
|
|
691
|
+
direction=SyncDirection.BIDIRECTIONAL
|
|
692
|
+
)
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## 导出清单
|
|
698
|
+
|
|
699
|
+
```python
|
|
700
|
+
from huace_aigc_auth_client import (
|
|
701
|
+
# 核心类
|
|
702
|
+
AigcAuthClient,
|
|
703
|
+
require_auth,
|
|
704
|
+
AuthMiddleware,
|
|
705
|
+
UserInfo,
|
|
706
|
+
TokenVerifyResult,
|
|
707
|
+
AigcAuthError,
|
|
708
|
+
create_fastapi_auth_dependency,
|
|
709
|
+
|
|
710
|
+
# 旧系统接入
|
|
711
|
+
LegacySystemAdapter,
|
|
712
|
+
LegacyUserData,
|
|
713
|
+
SyncConfig,
|
|
714
|
+
SyncDirection,
|
|
715
|
+
PasswordMode,
|
|
716
|
+
FieldMapping,
|
|
717
|
+
UserSyncService,
|
|
718
|
+
WebhookSender,
|
|
719
|
+
SyncResult,
|
|
720
|
+
create_sync_config,
|
|
721
|
+
create_default_field_mappings,
|
|
722
|
+
|
|
723
|
+
# Webhook 接收
|
|
724
|
+
register_webhook_router,
|
|
725
|
+
verify_webhook_signature,
|
|
726
|
+
)
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
---5 (2026-01-15)
|
|
730
|
+
|
|
731
|
+
#### 新增功能
|
|
732
|
+
|
|
733
|
+
1. **Webhook 接收功能**
|
|
734
|
+
- 新增 `register_webhook_router()` 函数,快速注册 webhook 接收路由
|
|
735
|
+
- 自动处理签名验证、请求解析和错误处理
|
|
736
|
+
- 支持自定义前缀、密钥环境变量和标签
|
|
737
|
+
- 新增 `verify_webhook_signature()` 工具函数
|
|
738
|
+
|
|
739
|
+
#### 使用示例
|
|
740
|
+
|
|
741
|
+
```python
|
|
742
|
+
from fastapi import APIRouter
|
|
743
|
+
from huace_aigc_auth_client import register_webhook_router
|
|
744
|
+
|
|
745
|
+
api_router = APIRouter()
|
|
746
|
+
|
|
747
|
+
async def my_handler(data: dict) -> dict:
|
|
748
|
+
event = data.get("event")
|
|
749
|
+
# 处理逻辑...
|
|
750
|
+
return {"status": "ok"}
|
|
751
|
+
|
|
752
|
+
# 注册 webhook 路由
|
|
753
|
+
register_webhook_router(api_router, my_handler)
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### v1.1.
|
|
757
|
+
|
|
758
|
+
## API 变更日志
|
|
759
|
+
|
|
760
|
+
### v1.1.0 (2026-01-13)
|
|
761
|
+
|
|
762
|
+
#### 新增功能
|
|
763
|
+
|
|
764
|
+
1. **支持直接同步已加密密码**
|
|
765
|
+
- `SyncConfig` 新增 `password_is_hashed` 参数
|
|
766
|
+
- `SyncUserRequest` 新增 `password_hashed` 字段
|
|
767
|
+
- 当两个系统使用相同的密码加密方式(bcrypt)时,可直接同步已加密密码
|
|
768
|
+
|
|
769
|
+
2. **`_request` 方法支持自定义 headers**
|
|
770
|
+
- `sync_user_to_auth(user_data, headers=None)` 支持传入自定义请求头
|
|
771
|
+
- `batch_sync_users_to_auth(users, headers=None)` 支持传入自定义请求头
|
|
772
|
+
|
|
773
|
+
3. **`get_password_for_sync` 返回格式变更**
|
|
774
|
+
- 旧版:返回 `str`(密码字符串)
|
|
775
|
+
- 新版:返回 `tuple[str, bool]`(密码字符串, 是否已加密)
|
|
776
|
+
|
|
777
|
+
#### 使用示例
|
|
778
|
+
|
|
779
|
+
```python
|
|
780
|
+
# 直接同步已加密密码
|
|
781
|
+
sync_config = create_sync_config(
|
|
782
|
+
password_mode=PasswordMode.CUSTOM_MAPPING,
|
|
783
|
+
password_mapper=lambda user: user.get("hashed_password"),
|
|
784
|
+
password_is_hashed=True, # 新增参数
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
# SDK 调用
|
|
788
|
+
client.sync_user_to_auth({
|
|
789
|
+
"username": "test",
|
|
790
|
+
"password_hashed": "$2b$12$xxxxx...", # 直接传递 bcrypt 哈希值
|
|
791
|
+
"email": "test@example.com"
|
|
792
|
+
})
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
## 许可证
|
|
796
|
+
|
|
797
|
+
MIT License
|