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.
@@ -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
+ [![PyPI version](https://badge.fury.io/py/huace-aigc-auth-client.svg)](https://pypi.org/project/huace-aigc-auth-client/)
29
+ [![Python Version](https://img.shields.io/pypi/pyversions/huace-aigc-auth-client.svg)](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