authhub-rbac-sdk 0.1.0__tar.gz
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.
- authhub_rbac_sdk-0.1.0/LICENSE +21 -0
- authhub_rbac_sdk-0.1.0/PKG-INFO +322 -0
- authhub_rbac_sdk-0.1.0/README.md +291 -0
- authhub_rbac_sdk-0.1.0/pyproject.toml +46 -0
- authhub_rbac_sdk-0.1.0/setup.cfg +4 -0
- authhub_rbac_sdk-0.1.0/src/authhub_rbac_sdk.egg-info/PKG-INFO +322 -0
- authhub_rbac_sdk-0.1.0/src/authhub_rbac_sdk.egg-info/SOURCES.txt +21 -0
- authhub_rbac_sdk-0.1.0/src/authhub_rbac_sdk.egg-info/dependency_links.txt +1 -0
- authhub_rbac_sdk-0.1.0/src/authhub_rbac_sdk.egg-info/requires.txt +17 -0
- authhub_rbac_sdk-0.1.0/src/authhub_rbac_sdk.egg-info/top_level.txt +1 -0
- authhub_rbac_sdk-0.1.0/src/authhub_sdk/__init__.py +30 -0
- authhub_rbac_sdk-0.1.0/src/authhub_sdk/client.py +284 -0
- authhub_rbac_sdk-0.1.0/src/authhub_sdk/config.py +70 -0
- authhub_rbac_sdk-0.1.0/src/authhub_sdk/django/__init__.py +8 -0
- authhub_rbac_sdk-0.1.0/src/authhub_sdk/django/middleware.py +129 -0
- authhub_rbac_sdk-0.1.0/src/authhub_sdk/errors.py +44 -0
- authhub_rbac_sdk-0.1.0/src/authhub_sdk/fastapi/__init__.py +22 -0
- authhub_rbac_sdk-0.1.0/src/authhub_sdk/fastapi/dependency.py +110 -0
- authhub_rbac_sdk-0.1.0/src/authhub_sdk/models.py +79 -0
- authhub_rbac_sdk-0.1.0/tests/test_client.py +182 -0
- authhub_rbac_sdk-0.1.0/tests/test_django_middleware.py +116 -0
- authhub_rbac_sdk-0.1.0/tests/test_fastapi.py +90 -0
- authhub_rbac_sdk-0.1.0/tests/test_fastapi_dependency.py +95 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2025 Vortek Inc. and Tuanliu (Hainan Special Economic Zone) Technology Co., Ltd.
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
本软件版权归 Vortek Inc.(除中国大陆地区)与 湍流(海南经济特区)科技有限责任公司(中国大陆地区)所有。
|
|
6
|
+
|
|
7
|
+
请根据许可协议使用本软件。
|
|
8
|
+
|
|
9
|
+
PROPRIETARY AND CONFIDENTIAL
|
|
10
|
+
|
|
11
|
+
This software is proprietary and confidential. Unauthorized copying, transfer, or
|
|
12
|
+
use of this software, via any medium, is strictly prohibited without the express
|
|
13
|
+
written permission of the copyright holders.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: authhub-rbac-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 子平台接入 AuthHub 的官方 Python SDK:登录、运行时鉴权一站直达。
|
|
5
|
+
Author: AuthHub Team
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Keywords: authhub,rbac,philvault,auth,sdk
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: License :: Other/Proprietary License
|
|
13
|
+
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: httpx>=0.27
|
|
18
|
+
Provides-Extra: django
|
|
19
|
+
Requires-Dist: Django<6,>=4.2; extra == "django"
|
|
20
|
+
Provides-Extra: fastapi
|
|
21
|
+
Requires-Dist: fastapi>=0.110; extra == "fastapi"
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: Django<6,>=4.2; extra == "all"
|
|
24
|
+
Requires-Dist: fastapi>=0.110; extra == "all"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
28
|
+
Requires-Dist: Django<6,>=4.2; extra == "dev"
|
|
29
|
+
Requires-Dist: fastapi>=0.110; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# authhub_sdk
|
|
33
|
+
|
|
34
|
+
子平台(cortexa / athanor / …)接入 AuthHub 的官方 SDK。
|
|
35
|
+
|
|
36
|
+
**核心约束**:子平台**只调 AuthHub**,绝不直接连 PhilVault;登录、token 刷新、
|
|
37
|
+
每次请求的鉴权决策都走 AuthHub HTTP API(`/api/v2/auth/*`、`/api/v2/sdk/*`)。
|
|
38
|
+
路由 → 资源/动作 的映射由 AuthHub 服务端匹配,SDK 端无感。
|
|
39
|
+
|
|
40
|
+
## 与历史 `authz/` 的关系
|
|
41
|
+
|
|
42
|
+
旧的 `authz/` 是 Vortek/湍流版权的 Django 中间件,直接调用 `philvault_sdk`。
|
|
43
|
+
本 SDK 是其替代品:
|
|
44
|
+
|
|
45
|
+
| 维度 | 旧 `authz/` (Vortek) | 新 `authhub_sdk/` |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| 通信对象 | 直连 PhilVault | 只调 AuthHub |
|
|
48
|
+
| 路由映射匹配 | 子平台拉 `/authz/mapping` 本地缓存 | AuthHub 服务端匹配 |
|
|
49
|
+
| 鉴权决策 | 本地 + PhilVault check | 一次 `POST /sdk/authorize` 拿结论 |
|
|
50
|
+
| 跨框架 | 仅 Django | Django + FastAPI |
|
|
51
|
+
| Token 缓存 | 30s 本地缓存 | 默认无缓存(运行时鉴权链路不缓存) |
|
|
52
|
+
|
|
53
|
+
新接入的子平台**只用本包**,不要再引入 `authz/`。
|
|
54
|
+
|
|
55
|
+
## 安装
|
|
56
|
+
|
|
57
|
+
仓库内开发:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# 基础安装(只用 AuthhubClient / AsyncAuthhubClient)
|
|
61
|
+
pip install /path/to/authhub/authhub_sdk
|
|
62
|
+
|
|
63
|
+
# Django 接入(自动带上 Django >=4.2)
|
|
64
|
+
pip install "authhub-sdk[django] @ file:///path/to/authhub/authhub_sdk"
|
|
65
|
+
|
|
66
|
+
# FastAPI 接入
|
|
67
|
+
pip install "authhub-sdk[fastapi] @ file:///path/to/authhub/authhub_sdk"
|
|
68
|
+
|
|
69
|
+
# Django + FastAPI 都装
|
|
70
|
+
pip install "authhub-sdk[all] @ file:///path/to/authhub/authhub_sdk"
|
|
71
|
+
|
|
72
|
+
# 开发依赖(含 pytest)
|
|
73
|
+
pip install -e "/path/to/authhub/authhub_sdk[dev]"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
依赖:
|
|
77
|
+
|
|
78
|
+
- `httpx >= 0.27`(基础)
|
|
79
|
+
- Python 3.10+
|
|
80
|
+
- Django >=4.2(extra `[django]`)
|
|
81
|
+
- FastAPI >=0.110(extra `[fastapi]`)
|
|
82
|
+
|
|
83
|
+
> 包采用 `src/` 布局:源码在 `authhub_sdk/src/authhub_sdk/`,安装后通过
|
|
84
|
+
> `import authhub_sdk` 引用,与 Django/FastAPI 上游包不会发生命名冲突。
|
|
85
|
+
|
|
86
|
+
## 三件套环境变量
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
export AUTHHUB_BASE_URL=http://authhub.internal:6001 # 不带 /api/v2
|
|
90
|
+
export AUTHHUB_APP_CODE=cortexa
|
|
91
|
+
# 可选
|
|
92
|
+
export AUTHHUB_TIMEOUT=5
|
|
93
|
+
export AUTHHUB_VERIFY_SSL=true
|
|
94
|
+
export AUTHHUB_BYPASS_PATHS=/health,/static/,/api/v2/sdk/healthz
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`AUTHHUB_BYPASS_PATHS` 是逗号分隔的**前缀**列表,命中后不调 AuthHub。
|
|
98
|
+
|
|
99
|
+
## 基础用法:HTTP 客户端
|
|
100
|
+
|
|
101
|
+
### 同步
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from authhub_sdk import AuthhubClient
|
|
105
|
+
|
|
106
|
+
with AuthhubClient.from_env() as client:
|
|
107
|
+
# 登录(子平台前端或后端都可发起)
|
|
108
|
+
result = client.login("alice", "User@123456")
|
|
109
|
+
access_token = result["access_token"]
|
|
110
|
+
|
|
111
|
+
# 当前用户信息
|
|
112
|
+
me = client.me(access_token)
|
|
113
|
+
print(me.user_id, me.username, me.domain_roles)
|
|
114
|
+
|
|
115
|
+
# 每次业务请求前调一次(中间件已自动做了,这里仅演示)
|
|
116
|
+
outcome = client.authorize(
|
|
117
|
+
access_token,
|
|
118
|
+
method="GET",
|
|
119
|
+
path="/api/v1/projects/123",
|
|
120
|
+
)
|
|
121
|
+
if not outcome.allow:
|
|
122
|
+
raise PermissionError(outcome.reason)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 异步
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from authhub_sdk import AsyncAuthhubClient
|
|
129
|
+
|
|
130
|
+
async with AsyncAuthhubClient.from_env() as client:
|
|
131
|
+
outcome = await client.authorize(token, method="POST", path="/api/v1/orders")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
显式传配置(不走 env):
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from authhub_sdk import AuthhubClient
|
|
138
|
+
from authhub_sdk.config import AuthhubConfig
|
|
139
|
+
|
|
140
|
+
cfg = AuthhubConfig(base_url="http://authhub.internal:6001", app_code="cortexa")
|
|
141
|
+
client = AuthhubClient(config=cfg)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Django 接入
|
|
145
|
+
|
|
146
|
+
### 1. 安装中间件
|
|
147
|
+
|
|
148
|
+
`settings.py`:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
INSTALLED_APPS = [
|
|
152
|
+
# ...
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
MIDDLEWARE = [
|
|
156
|
+
"django.middleware.security.SecurityMiddleware",
|
|
157
|
+
"django.middleware.common.CommonMiddleware",
|
|
158
|
+
"corsheaders.middleware.CorsMiddleware", # 若用 corsheaders
|
|
159
|
+
"authhub_sdk.django.AuthhubMiddleware", # ← 放在业务中间件之前
|
|
160
|
+
# ...
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
# 与 from_env 等价;也可以省略改用环境变量
|
|
164
|
+
AUTHHUB_SDK = {
|
|
165
|
+
"base_url": "http://authhub.internal:6001",
|
|
166
|
+
"app_code": "cortexa",
|
|
167
|
+
"bypass_paths": ["/health/", "/static/", "/admin/login/"],
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 2. 在 view 里拿用户
|
|
172
|
+
|
|
173
|
+
中间件鉴权通过后会注入:
|
|
174
|
+
|
|
175
|
+
| 属性 | 类型 | 含义 |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `request.authhub_outcome` | `AuthorizeOutcome` | 完整鉴权结果 |
|
|
178
|
+
| `request.authhub_user` | `dict` | PhilVault `/auth/me` 返回的用户字典 |
|
|
179
|
+
| `request.authhub_user_id` | `str` | 用户 ObjectId |
|
|
180
|
+
| `request.authhub_access_token` | `str` | 透出本次请求的 token |
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from django.http import JsonResponse
|
|
184
|
+
|
|
185
|
+
def get_my_projects(request):
|
|
186
|
+
user_id = request.authhub_user_id
|
|
187
|
+
return JsonResponse({"user_id": user_id})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 3. 错误响应
|
|
191
|
+
|
|
192
|
+
| 场景 | HTTP | body |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| 缺/无效 Bearer token | 401 | `{"detail": "Unauthorized: ...", "request_id": "..."}` |
|
|
195
|
+
| AuthHub 决策为 deny | 403 | `{"detail": <reason>, "decision": "deny", "mapping": {...}}` |
|
|
196
|
+
| AuthHub 不可达 / 5xx | 502 | `{"detail": "Auth backend unavailable"}` |
|
|
197
|
+
| CORS preflight (OPTIONS) | — | 不鉴权,直接放行 |
|
|
198
|
+
|
|
199
|
+
## FastAPI 接入
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from fastapi import FastAPI, Depends
|
|
203
|
+
from authhub_sdk.fastapi import AuthhubContext, authhub_required
|
|
204
|
+
|
|
205
|
+
app = FastAPI()
|
|
206
|
+
guard = authhub_required() # 从 AUTHHUB_* env 读配置
|
|
207
|
+
|
|
208
|
+
@app.get("/api/v1/projects/{project_id}")
|
|
209
|
+
async def get_project(
|
|
210
|
+
project_id: str,
|
|
211
|
+
ctx: AuthhubContext = Depends(guard),
|
|
212
|
+
):
|
|
213
|
+
return {
|
|
214
|
+
"project_id": project_id,
|
|
215
|
+
"viewer": ctx.user_id,
|
|
216
|
+
"decision": ctx.outcome.decision,
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
按 view 粒度切换鉴权:把不需要鉴权的 view 不依赖 `guard` 即可。或者全局挂依赖:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
app = FastAPI(dependencies=[Depends(guard)])
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
错误响应(标准 FastAPI HTTPException):
|
|
227
|
+
|
|
228
|
+
- 401 `{"detail": "Unauthorized: missing bearer token"}`
|
|
229
|
+
- 403 `{"detail": "<reason>"}`
|
|
230
|
+
- 502 `{"detail": "Auth backend unavailable"}`
|
|
231
|
+
|
|
232
|
+
## 登录链路(前端直调 AuthHub)
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
┌──────────┐ ┌──────────┐ ┌────────────┐
|
|
236
|
+
│ 子平台前端 │ ──login()──▶ │ AuthHub │ ──login()────▶ │ PhilVault │
|
|
237
|
+
│ │ ◀─token────── │ │ ◀─token─────── │ │
|
|
238
|
+
└──────────┘ └──────────┘ └────────────┘
|
|
239
|
+
│
|
|
240
|
+
│ Authorization: Bearer <token>
|
|
241
|
+
▼
|
|
242
|
+
┌──────────┐ ┌──────────┐ ┌────────────┐
|
|
243
|
+
│ 子平台后端 │ ◀──HTTP────── │ 中间件 │ ─authorize()─▶│ AuthHub │
|
|
244
|
+
│ (view) │ │ (SDK) │ │ /sdk/... │
|
|
245
|
+
└──────────┘ └──────────┘ └────────────┘
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
- 用户在子平台前端直接 `POST /api/v2/auth/login` 到 **AuthHub**(不经过子平台后端)
|
|
249
|
+
- 拿到 `access_token` 后所有业务请求都带 `Authorization: Bearer <token>`
|
|
250
|
+
- 子平台后端的中间件收到请求 → 调 `POST /api/v2/sdk/authorize` → 按 `allow` 字段放/拒
|
|
251
|
+
- token 过期:前端用 `refresh_token` 调 `/api/v2/auth/refresh` 拿新 access_token
|
|
252
|
+
|
|
253
|
+
## 异常体系
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from authhub_sdk import AuthhubError, AuthhubHTTPError, AuthhubUnauthorized
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
client.authorize(token, method="GET", path="/api/v1/items")
|
|
260
|
+
except AuthhubUnauthorized:
|
|
261
|
+
# 401:token 无效或过期。让用户重新登录
|
|
262
|
+
...
|
|
263
|
+
except AuthhubHTTPError as exc:
|
|
264
|
+
# 其它 4xx/5xx。exc.status_code / exc.payload / exc.request_id
|
|
265
|
+
...
|
|
266
|
+
except AuthhubError:
|
|
267
|
+
# 网络层(超时、DNS、连接拒绝)
|
|
268
|
+
...
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
继承关系:
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
Exception
|
|
275
|
+
└── AuthhubError # 网络层 / SDK 本身错误
|
|
276
|
+
└── AuthhubHTTPError # 后端返回 4xx/5xx
|
|
277
|
+
└── AuthhubUnauthorized # 后端返回 401(特殊处理用)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 行为约定
|
|
281
|
+
|
|
282
|
+
- **不缓存**任何 token → user_id 信息。AuthHub 是单点 SOT,权限变更秒级生效
|
|
283
|
+
- **OPTIONS** 请求直接放行(CORS preflight 兼容)
|
|
284
|
+
- **bypass_paths** 走前缀匹配;不走鉴权但仍注入空 `ctx`(FastAPI)
|
|
285
|
+
- **超时默认 5 秒**;线上建议结合熔断和重试策略由调用方控制
|
|
286
|
+
- 调用 `/sdk/authorize` 时若 AuthHub 不可达 → SDK 抛 `AuthhubError` → 中间件返回 502;
|
|
287
|
+
AuthHub 决策为 deny → 中间件返回 403。两种情况要分清楚
|
|
288
|
+
|
|
289
|
+
## 调试
|
|
290
|
+
|
|
291
|
+
需要看每次鉴权的细节?AuthHub `/sdk/authorize` 返回包含:
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"allow": false,
|
|
296
|
+
"decision": "deny",
|
|
297
|
+
"reason": "PhilVault 拒绝:用户 ... 在 cortexa 域对 rbac_cortexa::asset(read) 无授权",
|
|
298
|
+
"user": {"id": "...", "username": "..."},
|
|
299
|
+
"mapping": {"app_code": "cortexa", "method": "GET", "path": "/api/v1/asset/<id>", ...},
|
|
300
|
+
"philvault_resource_id": "rbac_cortexa::asset",
|
|
301
|
+
"action_code": "read"
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
在 Django view 里:`print(request.authhub_outcome.raw)` 看完整内容。
|
|
306
|
+
|
|
307
|
+
打开 AuthHub 「授权排查」页面(`/diagnose`),输入 user + method + path 可以
|
|
308
|
+
**离线**还原决策路径,不消耗 PhilVault 调用配额。
|
|
309
|
+
|
|
310
|
+
## 测试
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
cd authhub_sdk
|
|
314
|
+
pytest
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
22 个单测覆盖 client / Django 中间件 / FastAPI 依赖三个层,使用
|
|
318
|
+
`httpx.MockTransport` 模拟 AuthHub,零外部依赖。
|
|
319
|
+
|
|
320
|
+
## 版本
|
|
321
|
+
|
|
322
|
+
0.1.0 —— 初版。后续兼容性以 [SemVer](https://semver.org/) 为准。
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# authhub_sdk
|
|
2
|
+
|
|
3
|
+
子平台(cortexa / athanor / …)接入 AuthHub 的官方 SDK。
|
|
4
|
+
|
|
5
|
+
**核心约束**:子平台**只调 AuthHub**,绝不直接连 PhilVault;登录、token 刷新、
|
|
6
|
+
每次请求的鉴权决策都走 AuthHub HTTP API(`/api/v2/auth/*`、`/api/v2/sdk/*`)。
|
|
7
|
+
路由 → 资源/动作 的映射由 AuthHub 服务端匹配,SDK 端无感。
|
|
8
|
+
|
|
9
|
+
## 与历史 `authz/` 的关系
|
|
10
|
+
|
|
11
|
+
旧的 `authz/` 是 Vortek/湍流版权的 Django 中间件,直接调用 `philvault_sdk`。
|
|
12
|
+
本 SDK 是其替代品:
|
|
13
|
+
|
|
14
|
+
| 维度 | 旧 `authz/` (Vortek) | 新 `authhub_sdk/` |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| 通信对象 | 直连 PhilVault | 只调 AuthHub |
|
|
17
|
+
| 路由映射匹配 | 子平台拉 `/authz/mapping` 本地缓存 | AuthHub 服务端匹配 |
|
|
18
|
+
| 鉴权决策 | 本地 + PhilVault check | 一次 `POST /sdk/authorize` 拿结论 |
|
|
19
|
+
| 跨框架 | 仅 Django | Django + FastAPI |
|
|
20
|
+
| Token 缓存 | 30s 本地缓存 | 默认无缓存(运行时鉴权链路不缓存) |
|
|
21
|
+
|
|
22
|
+
新接入的子平台**只用本包**,不要再引入 `authz/`。
|
|
23
|
+
|
|
24
|
+
## 安装
|
|
25
|
+
|
|
26
|
+
仓库内开发:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# 基础安装(只用 AuthhubClient / AsyncAuthhubClient)
|
|
30
|
+
pip install /path/to/authhub/authhub_sdk
|
|
31
|
+
|
|
32
|
+
# Django 接入(自动带上 Django >=4.2)
|
|
33
|
+
pip install "authhub-sdk[django] @ file:///path/to/authhub/authhub_sdk"
|
|
34
|
+
|
|
35
|
+
# FastAPI 接入
|
|
36
|
+
pip install "authhub-sdk[fastapi] @ file:///path/to/authhub/authhub_sdk"
|
|
37
|
+
|
|
38
|
+
# Django + FastAPI 都装
|
|
39
|
+
pip install "authhub-sdk[all] @ file:///path/to/authhub/authhub_sdk"
|
|
40
|
+
|
|
41
|
+
# 开发依赖(含 pytest)
|
|
42
|
+
pip install -e "/path/to/authhub/authhub_sdk[dev]"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
依赖:
|
|
46
|
+
|
|
47
|
+
- `httpx >= 0.27`(基础)
|
|
48
|
+
- Python 3.10+
|
|
49
|
+
- Django >=4.2(extra `[django]`)
|
|
50
|
+
- FastAPI >=0.110(extra `[fastapi]`)
|
|
51
|
+
|
|
52
|
+
> 包采用 `src/` 布局:源码在 `authhub_sdk/src/authhub_sdk/`,安装后通过
|
|
53
|
+
> `import authhub_sdk` 引用,与 Django/FastAPI 上游包不会发生命名冲突。
|
|
54
|
+
|
|
55
|
+
## 三件套环境变量
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export AUTHHUB_BASE_URL=http://authhub.internal:6001 # 不带 /api/v2
|
|
59
|
+
export AUTHHUB_APP_CODE=cortexa
|
|
60
|
+
# 可选
|
|
61
|
+
export AUTHHUB_TIMEOUT=5
|
|
62
|
+
export AUTHHUB_VERIFY_SSL=true
|
|
63
|
+
export AUTHHUB_BYPASS_PATHS=/health,/static/,/api/v2/sdk/healthz
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`AUTHHUB_BYPASS_PATHS` 是逗号分隔的**前缀**列表,命中后不调 AuthHub。
|
|
67
|
+
|
|
68
|
+
## 基础用法:HTTP 客户端
|
|
69
|
+
|
|
70
|
+
### 同步
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from authhub_sdk import AuthhubClient
|
|
74
|
+
|
|
75
|
+
with AuthhubClient.from_env() as client:
|
|
76
|
+
# 登录(子平台前端或后端都可发起)
|
|
77
|
+
result = client.login("alice", "User@123456")
|
|
78
|
+
access_token = result["access_token"]
|
|
79
|
+
|
|
80
|
+
# 当前用户信息
|
|
81
|
+
me = client.me(access_token)
|
|
82
|
+
print(me.user_id, me.username, me.domain_roles)
|
|
83
|
+
|
|
84
|
+
# 每次业务请求前调一次(中间件已自动做了,这里仅演示)
|
|
85
|
+
outcome = client.authorize(
|
|
86
|
+
access_token,
|
|
87
|
+
method="GET",
|
|
88
|
+
path="/api/v1/projects/123",
|
|
89
|
+
)
|
|
90
|
+
if not outcome.allow:
|
|
91
|
+
raise PermissionError(outcome.reason)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 异步
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from authhub_sdk import AsyncAuthhubClient
|
|
98
|
+
|
|
99
|
+
async with AsyncAuthhubClient.from_env() as client:
|
|
100
|
+
outcome = await client.authorize(token, method="POST", path="/api/v1/orders")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
显式传配置(不走 env):
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from authhub_sdk import AuthhubClient
|
|
107
|
+
from authhub_sdk.config import AuthhubConfig
|
|
108
|
+
|
|
109
|
+
cfg = AuthhubConfig(base_url="http://authhub.internal:6001", app_code="cortexa")
|
|
110
|
+
client = AuthhubClient(config=cfg)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Django 接入
|
|
114
|
+
|
|
115
|
+
### 1. 安装中间件
|
|
116
|
+
|
|
117
|
+
`settings.py`:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
INSTALLED_APPS = [
|
|
121
|
+
# ...
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
MIDDLEWARE = [
|
|
125
|
+
"django.middleware.security.SecurityMiddleware",
|
|
126
|
+
"django.middleware.common.CommonMiddleware",
|
|
127
|
+
"corsheaders.middleware.CorsMiddleware", # 若用 corsheaders
|
|
128
|
+
"authhub_sdk.django.AuthhubMiddleware", # ← 放在业务中间件之前
|
|
129
|
+
# ...
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
# 与 from_env 等价;也可以省略改用环境变量
|
|
133
|
+
AUTHHUB_SDK = {
|
|
134
|
+
"base_url": "http://authhub.internal:6001",
|
|
135
|
+
"app_code": "cortexa",
|
|
136
|
+
"bypass_paths": ["/health/", "/static/", "/admin/login/"],
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 2. 在 view 里拿用户
|
|
141
|
+
|
|
142
|
+
中间件鉴权通过后会注入:
|
|
143
|
+
|
|
144
|
+
| 属性 | 类型 | 含义 |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `request.authhub_outcome` | `AuthorizeOutcome` | 完整鉴权结果 |
|
|
147
|
+
| `request.authhub_user` | `dict` | PhilVault `/auth/me` 返回的用户字典 |
|
|
148
|
+
| `request.authhub_user_id` | `str` | 用户 ObjectId |
|
|
149
|
+
| `request.authhub_access_token` | `str` | 透出本次请求的 token |
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from django.http import JsonResponse
|
|
153
|
+
|
|
154
|
+
def get_my_projects(request):
|
|
155
|
+
user_id = request.authhub_user_id
|
|
156
|
+
return JsonResponse({"user_id": user_id})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 3. 错误响应
|
|
160
|
+
|
|
161
|
+
| 场景 | HTTP | body |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| 缺/无效 Bearer token | 401 | `{"detail": "Unauthorized: ...", "request_id": "..."}` |
|
|
164
|
+
| AuthHub 决策为 deny | 403 | `{"detail": <reason>, "decision": "deny", "mapping": {...}}` |
|
|
165
|
+
| AuthHub 不可达 / 5xx | 502 | `{"detail": "Auth backend unavailable"}` |
|
|
166
|
+
| CORS preflight (OPTIONS) | — | 不鉴权,直接放行 |
|
|
167
|
+
|
|
168
|
+
## FastAPI 接入
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from fastapi import FastAPI, Depends
|
|
172
|
+
from authhub_sdk.fastapi import AuthhubContext, authhub_required
|
|
173
|
+
|
|
174
|
+
app = FastAPI()
|
|
175
|
+
guard = authhub_required() # 从 AUTHHUB_* env 读配置
|
|
176
|
+
|
|
177
|
+
@app.get("/api/v1/projects/{project_id}")
|
|
178
|
+
async def get_project(
|
|
179
|
+
project_id: str,
|
|
180
|
+
ctx: AuthhubContext = Depends(guard),
|
|
181
|
+
):
|
|
182
|
+
return {
|
|
183
|
+
"project_id": project_id,
|
|
184
|
+
"viewer": ctx.user_id,
|
|
185
|
+
"decision": ctx.outcome.decision,
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
按 view 粒度切换鉴权:把不需要鉴权的 view 不依赖 `guard` 即可。或者全局挂依赖:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
app = FastAPI(dependencies=[Depends(guard)])
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
错误响应(标准 FastAPI HTTPException):
|
|
196
|
+
|
|
197
|
+
- 401 `{"detail": "Unauthorized: missing bearer token"}`
|
|
198
|
+
- 403 `{"detail": "<reason>"}`
|
|
199
|
+
- 502 `{"detail": "Auth backend unavailable"}`
|
|
200
|
+
|
|
201
|
+
## 登录链路(前端直调 AuthHub)
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
┌──────────┐ ┌──────────┐ ┌────────────┐
|
|
205
|
+
│ 子平台前端 │ ──login()──▶ │ AuthHub │ ──login()────▶ │ PhilVault │
|
|
206
|
+
│ │ ◀─token────── │ │ ◀─token─────── │ │
|
|
207
|
+
└──────────┘ └──────────┘ └────────────┘
|
|
208
|
+
│
|
|
209
|
+
│ Authorization: Bearer <token>
|
|
210
|
+
▼
|
|
211
|
+
┌──────────┐ ┌──────────┐ ┌────────────┐
|
|
212
|
+
│ 子平台后端 │ ◀──HTTP────── │ 中间件 │ ─authorize()─▶│ AuthHub │
|
|
213
|
+
│ (view) │ │ (SDK) │ │ /sdk/... │
|
|
214
|
+
└──────────┘ └──────────┘ └────────────┘
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
- 用户在子平台前端直接 `POST /api/v2/auth/login` 到 **AuthHub**(不经过子平台后端)
|
|
218
|
+
- 拿到 `access_token` 后所有业务请求都带 `Authorization: Bearer <token>`
|
|
219
|
+
- 子平台后端的中间件收到请求 → 调 `POST /api/v2/sdk/authorize` → 按 `allow` 字段放/拒
|
|
220
|
+
- token 过期:前端用 `refresh_token` 调 `/api/v2/auth/refresh` 拿新 access_token
|
|
221
|
+
|
|
222
|
+
## 异常体系
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from authhub_sdk import AuthhubError, AuthhubHTTPError, AuthhubUnauthorized
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
client.authorize(token, method="GET", path="/api/v1/items")
|
|
229
|
+
except AuthhubUnauthorized:
|
|
230
|
+
# 401:token 无效或过期。让用户重新登录
|
|
231
|
+
...
|
|
232
|
+
except AuthhubHTTPError as exc:
|
|
233
|
+
# 其它 4xx/5xx。exc.status_code / exc.payload / exc.request_id
|
|
234
|
+
...
|
|
235
|
+
except AuthhubError:
|
|
236
|
+
# 网络层(超时、DNS、连接拒绝)
|
|
237
|
+
...
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
继承关系:
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
Exception
|
|
244
|
+
└── AuthhubError # 网络层 / SDK 本身错误
|
|
245
|
+
└── AuthhubHTTPError # 后端返回 4xx/5xx
|
|
246
|
+
└── AuthhubUnauthorized # 后端返回 401(特殊处理用)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## 行为约定
|
|
250
|
+
|
|
251
|
+
- **不缓存**任何 token → user_id 信息。AuthHub 是单点 SOT,权限变更秒级生效
|
|
252
|
+
- **OPTIONS** 请求直接放行(CORS preflight 兼容)
|
|
253
|
+
- **bypass_paths** 走前缀匹配;不走鉴权但仍注入空 `ctx`(FastAPI)
|
|
254
|
+
- **超时默认 5 秒**;线上建议结合熔断和重试策略由调用方控制
|
|
255
|
+
- 调用 `/sdk/authorize` 时若 AuthHub 不可达 → SDK 抛 `AuthhubError` → 中间件返回 502;
|
|
256
|
+
AuthHub 决策为 deny → 中间件返回 403。两种情况要分清楚
|
|
257
|
+
|
|
258
|
+
## 调试
|
|
259
|
+
|
|
260
|
+
需要看每次鉴权的细节?AuthHub `/sdk/authorize` 返回包含:
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"allow": false,
|
|
265
|
+
"decision": "deny",
|
|
266
|
+
"reason": "PhilVault 拒绝:用户 ... 在 cortexa 域对 rbac_cortexa::asset(read) 无授权",
|
|
267
|
+
"user": {"id": "...", "username": "..."},
|
|
268
|
+
"mapping": {"app_code": "cortexa", "method": "GET", "path": "/api/v1/asset/<id>", ...},
|
|
269
|
+
"philvault_resource_id": "rbac_cortexa::asset",
|
|
270
|
+
"action_code": "read"
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
在 Django view 里:`print(request.authhub_outcome.raw)` 看完整内容。
|
|
275
|
+
|
|
276
|
+
打开 AuthHub 「授权排查」页面(`/diagnose`),输入 user + method + path 可以
|
|
277
|
+
**离线**还原决策路径,不消耗 PhilVault 调用配额。
|
|
278
|
+
|
|
279
|
+
## 测试
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
cd authhub_sdk
|
|
283
|
+
pytest
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
22 个单测覆盖 client / Django 中间件 / FastAPI 依赖三个层,使用
|
|
287
|
+
`httpx.MockTransport` 模拟 AuthHub,零外部依赖。
|
|
288
|
+
|
|
289
|
+
## 版本
|
|
290
|
+
|
|
291
|
+
0.1.0 —— 初版。后续兼容性以 [SemVer](https://semver.org/) 为准。
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "authhub-rbac-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "子平台接入 AuthHub 的官方 Python SDK:登录、运行时鉴权一站直达。"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "Proprietary" }
|
|
12
|
+
authors = [{ name = "AuthHub Team" }]
|
|
13
|
+
keywords = ["authhub", "rbac", "philvault", "auth", "sdk"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"License :: Other/Proprietary License",
|
|
20
|
+
"Topic :: System :: Systems Administration :: Authentication/Directory",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"httpx>=0.27",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
django = ["Django>=4.2,<6"]
|
|
28
|
+
fastapi = ["fastapi>=0.110"]
|
|
29
|
+
all = [
|
|
30
|
+
"Django>=4.2,<6",
|
|
31
|
+
"fastapi>=0.110",
|
|
32
|
+
]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8",
|
|
35
|
+
"pytest-asyncio>=0.23",
|
|
36
|
+
"Django>=4.2,<6",
|
|
37
|
+
"fastapi>=0.110",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
asyncio_mode = "auto"
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
pythonpath = ["src"]
|