skyplatform-iam 1.2.0__tar.gz → 1.2.1__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.
- {skyplatform_iam-1.2.0 → skyplatform_iam-1.2.1}/PKG-INFO +1 -1
- {skyplatform_iam-1.2.0 → skyplatform_iam-1.2.1}/pyproject.toml +1 -1
- {skyplatform_iam-1.2.0 → skyplatform_iam-1.2.1}/skyplatform_iam/__init__.py +34 -1
- {skyplatform_iam-1.2.0 → skyplatform_iam-1.2.1}/skyplatform_iam/config.py +4 -0
- {skyplatform_iam-1.2.0 → skyplatform_iam-1.2.1}/skyplatform_iam/connect_agenterra_iam.py +44 -21
- {skyplatform_iam-1.2.0 → skyplatform_iam-1.2.1}/skyplatform_iam/middleware.py +38 -25
- {skyplatform_iam-1.2.0 → skyplatform_iam-1.2.1}/README.md +0 -0
- {skyplatform_iam-1.2.0 → skyplatform_iam-1.2.1}/skyplatform_iam/exceptions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skyplatform-iam
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: SkyPlatform IAM认证SDK,提供FastAPI中间件和认证路由
|
|
5
5
|
Project-URL: Homepage, https://github.com/xinmayoujiang12621/agenterra_iam
|
|
6
6
|
Project-URL: Documentation, https://skyplatform-iam.readthedocs.io/
|
|
@@ -17,7 +17,7 @@ from .exceptions import (
|
|
|
17
17
|
NetworkError
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
-
__version__ = "1.2.
|
|
20
|
+
__version__ = "1.2.1"
|
|
21
21
|
__author__ = "x9"
|
|
22
22
|
__description__ = "SkyPlatform IAM认证SDK,提供FastAPI中间件和IAM服务连接功能"
|
|
23
23
|
|
|
@@ -100,6 +100,9 @@ def init_skyplatform_iam(app, config: AuthConfig = None):
|
|
|
100
100
|
# 验证配置的完整性
|
|
101
101
|
config.validate_config()
|
|
102
102
|
|
|
103
|
+
# 配置SDK日志级别
|
|
104
|
+
_configure_sdk_logging(config.enable_sdk_logging)
|
|
105
|
+
|
|
103
106
|
# 初始化全局认证服务
|
|
104
107
|
setup_auth_middleware(config)
|
|
105
108
|
|
|
@@ -110,6 +113,36 @@ def init_skyplatform_iam(app, config: AuthConfig = None):
|
|
|
110
113
|
return middleware
|
|
111
114
|
|
|
112
115
|
|
|
116
|
+
def _configure_sdk_logging(enable_sdk_logging: bool):
|
|
117
|
+
"""
|
|
118
|
+
配置SDK日志级别
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
enable_sdk_logging: 是否启用SDK日志
|
|
122
|
+
"""
|
|
123
|
+
import logging
|
|
124
|
+
|
|
125
|
+
# SDK相关的logger名称列表
|
|
126
|
+
sdk_loggers = [
|
|
127
|
+
'skyplatform_iam.config',
|
|
128
|
+
'skyplatform_iam.middleware',
|
|
129
|
+
'skyplatform_iam.connect_agenterra_iam',
|
|
130
|
+
'skyplatform_iam.exceptions'
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
if enable_sdk_logging:
|
|
134
|
+
# 启用SDK日志,设置为INFO级别
|
|
135
|
+
log_level = logging.INFO
|
|
136
|
+
else:
|
|
137
|
+
# 禁用SDK日志,设置为WARNING级别,只显示警告和错误
|
|
138
|
+
log_level = logging.WARNING
|
|
139
|
+
|
|
140
|
+
# 配置所有SDK相关的logger
|
|
141
|
+
for logger_name in sdk_loggers:
|
|
142
|
+
logger = logging.getLogger(logger_name)
|
|
143
|
+
logger.setLevel(log_level)
|
|
144
|
+
|
|
145
|
+
|
|
113
146
|
def get_iam_client(config: AuthConfig = None) -> ConnectAgenterraIam:
|
|
114
147
|
"""
|
|
115
148
|
获取全局IAM客户端实例
|
|
@@ -32,6 +32,9 @@ class AuthConfig(BaseModel):
|
|
|
32
32
|
|
|
33
33
|
# 错误处理配置
|
|
34
34
|
enable_debug: bool = False
|
|
35
|
+
|
|
36
|
+
# SDK日志控制配置
|
|
37
|
+
enable_sdk_logging: bool = True
|
|
35
38
|
|
|
36
39
|
# 白名单路径配置(实例变量)
|
|
37
40
|
whitelist_paths: List[str] = Field(default_factory=list)
|
|
@@ -49,6 +52,7 @@ class AuthConfig(BaseModel):
|
|
|
49
52
|
server_name=os.environ.get('AGENTERRA_SERVER_NAME', ''),
|
|
50
53
|
access_key=os.environ.get('AGENTERRA_ACCESS_KEY', ''),
|
|
51
54
|
enable_debug=os.environ.get('AGENTERRA_ENABLE_DEBUG', 'false').lower() == 'true',
|
|
55
|
+
enable_sdk_logging=os.environ.get('AGENTERRA_ENABLE_SDK_LOGGING', 'true').lower() == 'true',
|
|
52
56
|
machine_token=os.environ.get('MACHINE_TOKEN', ''),
|
|
53
57
|
whitelist_paths=[] # 初始化空的白名单路径列表
|
|
54
58
|
)
|
|
@@ -2,11 +2,16 @@ import requests
|
|
|
2
2
|
import logging
|
|
3
3
|
import traceback
|
|
4
4
|
import copy
|
|
5
|
+
import urllib3
|
|
5
6
|
from enum import Enum
|
|
6
7
|
from fastapi import HTTPException, status
|
|
7
8
|
|
|
8
9
|
from .config import decode_jwt_token
|
|
9
10
|
|
|
11
|
+
# 禁用 urllib3 的 InsecureRequestWarning 警告
|
|
12
|
+
# 这样可以避免在使用自定义 HTTPS 地址(可能没有有效证书)时出现警告信息
|
|
13
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
14
|
+
|
|
10
15
|
|
|
11
16
|
class CredentialTypeEnum(str, Enum):
|
|
12
17
|
"""凭证类型枚举,与后端API保持一致"""
|
|
@@ -42,6 +47,21 @@ class ConnectAgenterraIam(object):
|
|
|
42
47
|
if self._initialized:
|
|
43
48
|
return
|
|
44
49
|
|
|
50
|
+
# 必须传入config参数,不再支持从环境变量读取
|
|
51
|
+
if config is None:
|
|
52
|
+
raise ValueError("必须传入AuthConfig配置对象,不再支持从环境变量读取配置")
|
|
53
|
+
|
|
54
|
+
# 保存配置对象,用于日志控制
|
|
55
|
+
self.config = config
|
|
56
|
+
|
|
57
|
+
# 根据配置设置日志级别
|
|
58
|
+
if hasattr(config, 'enable_sdk_logging') and not config.enable_sdk_logging:
|
|
59
|
+
# 禁用SDK日志时,设置为WARNING级别,只显示警告和错误
|
|
60
|
+
effective_log_level = logging.WARNING
|
|
61
|
+
else:
|
|
62
|
+
# 启用SDK日志时,使用传入的日志级别
|
|
63
|
+
effective_log_level = log_level
|
|
64
|
+
|
|
45
65
|
# 配置日志记录器
|
|
46
66
|
self.logger = logging.getLogger(logger_name)
|
|
47
67
|
if not self.logger.handlers:
|
|
@@ -51,17 +71,15 @@ class ConnectAgenterraIam(object):
|
|
|
51
71
|
)
|
|
52
72
|
handler.setFormatter(formatter)
|
|
53
73
|
self.logger.addHandler(handler)
|
|
54
|
-
self.logger.setLevel(
|
|
55
|
-
|
|
56
|
-
# 必须传入config参数,不再支持从环境变量读取
|
|
57
|
-
if config is None:
|
|
58
|
-
raise ValueError("必须传入AuthConfig配置对象,不再支持从环境变量读取配置")
|
|
74
|
+
self.logger.setLevel(effective_log_level)
|
|
59
75
|
|
|
60
76
|
self.agenterra_iam_host = config.agenterra_iam_host
|
|
61
77
|
self.server_name = config.server_name
|
|
62
78
|
self.access_key = config.access_key
|
|
63
79
|
self.machine_token = config.machine_token
|
|
64
|
-
|
|
80
|
+
|
|
81
|
+
if hasattr(config, 'enable_sdk_logging') and config.enable_sdk_logging:
|
|
82
|
+
self.logger.info("使用传入的AuthConfig配置")
|
|
65
83
|
|
|
66
84
|
# 验证必要的配置
|
|
67
85
|
if not self.agenterra_iam_host:
|
|
@@ -71,8 +89,9 @@ class ConnectAgenterraIam(object):
|
|
|
71
89
|
if not self.access_key:
|
|
72
90
|
self.logger.warning("AGENTERRA_ACCESS_KEY 配置未设置")
|
|
73
91
|
|
|
74
|
-
|
|
75
|
-
|
|
92
|
+
if hasattr(config, 'enable_sdk_logging') and config.enable_sdk_logging:
|
|
93
|
+
self.logger.info(
|
|
94
|
+
f"初始化AgenterraIAM连接器 - Host: {self.agenterra_iam_host}, Server: {self._mask_sensitive(self.server_name)}")
|
|
76
95
|
|
|
77
96
|
self.headers = {
|
|
78
97
|
"Content-Type": "application/json",
|
|
@@ -178,23 +197,27 @@ class ConnectAgenterraIam(object):
|
|
|
178
197
|
|
|
179
198
|
def _log_request(self, method_name, url, headers, body):
|
|
180
199
|
"""记录请求信息"""
|
|
181
|
-
|
|
182
|
-
|
|
200
|
+
# 只有在启用SDK日志时才输出详细的请求信息
|
|
201
|
+
if hasattr(self, 'config') and hasattr(self.config, 'enable_sdk_logging') and self.config.enable_sdk_logging:
|
|
202
|
+
sanitized_headers = self._sanitize_log_data(headers)
|
|
203
|
+
sanitized_body = self._sanitize_log_data(body)
|
|
183
204
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
205
|
+
self.logger.info(f"[{method_name}] 发送请求 - URL: {url}")
|
|
206
|
+
self.logger.info(f"[{method_name}] 请求头: {sanitized_headers}")
|
|
207
|
+
self.logger.info(f"[{method_name}] 请求体: {sanitized_body}")
|
|
187
208
|
|
|
188
209
|
def _log_response(self, method_name, response):
|
|
189
210
|
"""记录响应信息"""
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
211
|
+
# 只有在启用SDK日志时才输出详细的响应信息
|
|
212
|
+
if hasattr(self, 'config') and hasattr(self.config, 'enable_sdk_logging') and self.config.enable_sdk_logging:
|
|
213
|
+
try:
|
|
214
|
+
response_data = response.json() if response.content else {}
|
|
215
|
+
sanitized_response = self._sanitize_log_data(response_data)
|
|
216
|
+
self.logger.info(f"[{method_name}] 响应状态码: {response.status_code}")
|
|
217
|
+
self.logger.info(f"[{method_name}] 响应内容: {sanitized_response}")
|
|
218
|
+
except Exception as e:
|
|
219
|
+
self.logger.info(f"[{method_name}] 响应状态码: {response.status_code}")
|
|
220
|
+
self.logger.info(f"[{method_name}] 响应内容解析失败: {str(e)}")
|
|
198
221
|
|
|
199
222
|
def register(self, cred_type=None, cred_value=None, password=None, nickname=None, avatar_url=None,
|
|
200
223
|
username=None, phone=None):
|
|
@@ -76,7 +76,8 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
76
76
|
|
|
77
77
|
# 首先检查路径是否在本地白名单中
|
|
78
78
|
if self.is_path_whitelisted(api_path):
|
|
79
|
-
|
|
79
|
+
if self.config.enable_sdk_logging:
|
|
80
|
+
logger.info(f"路径 {api_path} 在本地白名单中,跳过认证直接允许访问")
|
|
80
81
|
# 设置白名单标识
|
|
81
82
|
request.state.user = None
|
|
82
83
|
request.state.authenticated = False
|
|
@@ -106,9 +107,14 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
106
107
|
request.state.is_whitelist = True
|
|
107
108
|
else:
|
|
108
109
|
if machine_token:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
try:
|
|
111
|
+
payload = jwt.decode(machine_token, algorithms=["HS256"], options={"verify_signature": False})
|
|
112
|
+
user_id = payload.get("sub", None)
|
|
113
|
+
request.state.user_id = user_id
|
|
114
|
+
except Exception as jwt_error:
|
|
115
|
+
if self.config.enable_sdk_logging:
|
|
116
|
+
logger.error(f"JWT解码失败: {str(jwt_error)}")
|
|
117
|
+
# JWT解码失败时不设置user_id,但继续处理
|
|
112
118
|
|
|
113
119
|
# 正常认证接口,设置用户信息
|
|
114
120
|
request.state.user = user_info
|
|
@@ -138,9 +144,10 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
138
144
|
detail=e.detail
|
|
139
145
|
)
|
|
140
146
|
except Exception as e:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
if self.config.enable_sdk_logging:
|
|
148
|
+
logger.error(f"认证中间件处理异常: {str(e)}")
|
|
149
|
+
if self.config.enable_debug:
|
|
150
|
+
logger.exception("详细异常信息:")
|
|
144
151
|
|
|
145
152
|
return self._create_error_response(
|
|
146
153
|
status_code=500,
|
|
@@ -155,13 +162,11 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
155
162
|
# 从Authorization头提取
|
|
156
163
|
auth_header = request.headers.get(self.config.token_header)
|
|
157
164
|
if auth_header and auth_header.startswith(self.config.token_prefix):
|
|
158
|
-
print("has auth_header: ", auth_header)
|
|
159
165
|
return auth_header[len(self.config.token_prefix):].strip()
|
|
160
166
|
|
|
161
167
|
# 从查询参数提取(备选方案)
|
|
162
168
|
token = request.query_params.get("token")
|
|
163
169
|
if token:
|
|
164
|
-
print("has token: ", token)
|
|
165
170
|
return token
|
|
166
171
|
|
|
167
172
|
return None
|
|
@@ -173,7 +178,6 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
173
178
|
# 从Authorization头提取
|
|
174
179
|
machine_token = request.headers.get("MACHINE-TOKEN")
|
|
175
180
|
if machine_token:
|
|
176
|
-
print("has MACHINE-TOKEN': ", machine_token)
|
|
177
181
|
return machine_token
|
|
178
182
|
|
|
179
183
|
return None
|
|
@@ -208,9 +212,10 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
208
212
|
# 重新抛出HTTP异常
|
|
209
213
|
raise
|
|
210
214
|
except Exception as e:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
215
|
+
if self.config.enable_sdk_logging:
|
|
216
|
+
logger.error(f"Token验证异常: {str(e)}")
|
|
217
|
+
if self.config.enable_debug:
|
|
218
|
+
logger.exception("详细异常信息:")
|
|
214
219
|
return None
|
|
215
220
|
|
|
216
221
|
def _create_error_response(
|
|
@@ -265,7 +270,8 @@ class AuthService:
|
|
|
265
270
|
|
|
266
271
|
# 首先检查路径是否在白名单中
|
|
267
272
|
if self.is_path_whitelisted(api_path):
|
|
268
|
-
|
|
273
|
+
if self.auth_config.enable_sdk_logging:
|
|
274
|
+
logger.info(f"路径 {api_path} 在白名单中,跳过IAM鉴权")
|
|
269
275
|
return True
|
|
270
276
|
|
|
271
277
|
credentials: HTTPAuthorizationCredentials = await self.security(request)
|
|
@@ -274,7 +280,6 @@ class AuthService:
|
|
|
274
280
|
server_ak = request.headers.get("SERVER-AK", "")
|
|
275
281
|
server_sk = request.headers.get("SERVER-SK", "")
|
|
276
282
|
machine_token = request.headers.get("MACHINE-TOKEN", "")
|
|
277
|
-
print("248 machine_token:", machine_token)
|
|
278
283
|
|
|
279
284
|
token = ""
|
|
280
285
|
if credentials is not None:
|
|
@@ -301,7 +306,8 @@ class AuthService:
|
|
|
301
306
|
# 直接解析JWT token获取payload
|
|
302
307
|
payload = self.decode_jwt_token(token)
|
|
303
308
|
if not payload:
|
|
304
|
-
|
|
309
|
+
if self.auth_config.enable_sdk_logging:
|
|
310
|
+
logger.error("JWT token解析失败")
|
|
305
311
|
return None
|
|
306
312
|
|
|
307
313
|
# 从payload中提取用户信息
|
|
@@ -357,20 +363,23 @@ class AuthService:
|
|
|
357
363
|
cred_types = [cred.get("type") for cred in all_credentials]
|
|
358
364
|
cred_type_count = {cred_type: cred_types.count(cred_type) for cred_type in set(cred_types)}
|
|
359
365
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
366
|
+
if self.auth_config.enable_sdk_logging:
|
|
367
|
+
logger.info(
|
|
368
|
+
f"用户认证成功: user_id={iam_user_id}, username={username}, 凭证数量={total_credentials}, 凭证类型分布={cred_type_count}")
|
|
369
|
+
logger.debug(f"JWT payload: {payload}")
|
|
363
370
|
|
|
364
371
|
# 将用户信息添加到请求状态中
|
|
365
372
|
request.state.user = user_info
|
|
366
373
|
return user_info
|
|
367
374
|
|
|
368
375
|
except HTTPException as e:
|
|
369
|
-
|
|
376
|
+
if self.auth_config.enable_sdk_logging:
|
|
377
|
+
logger.error(f"获取当前用户信息失败: {str(e)}")
|
|
370
378
|
# 重新抛出HTTP异常(403权限不足)
|
|
371
379
|
return None
|
|
372
380
|
except Exception as e:
|
|
373
|
-
|
|
381
|
+
if self.auth_config.enable_sdk_logging:
|
|
382
|
+
logger.error(f"获取当前用户信息失败: {str(e)}")
|
|
374
383
|
return None
|
|
375
384
|
|
|
376
385
|
async def require_auth(self, request: Request) -> Dict:
|
|
@@ -402,13 +411,16 @@ class AuthService:
|
|
|
402
411
|
try:
|
|
403
412
|
# 不验证签名,只解析payload(因为token已经通过verify_token验证过)
|
|
404
413
|
decoded_payload = jwt.decode(token, options={"verify_signature": False})
|
|
405
|
-
|
|
414
|
+
if self.auth_config.enable_sdk_logging:
|
|
415
|
+
logger.debug(f"JWT token解析成功: {decoded_payload}")
|
|
406
416
|
return decoded_payload
|
|
407
417
|
except jwt.InvalidTokenError as e:
|
|
408
|
-
|
|
418
|
+
if self.auth_config.enable_sdk_logging:
|
|
419
|
+
logger.error(f"JWT token解析失败: {str(e)}")
|
|
409
420
|
return None
|
|
410
421
|
except Exception as e:
|
|
411
|
-
|
|
422
|
+
if self.auth_config.enable_sdk_logging:
|
|
423
|
+
logger.error(f"JWT token解析异常: {str(e)}")
|
|
412
424
|
return None
|
|
413
425
|
|
|
414
426
|
|
|
@@ -425,7 +437,8 @@ def setup_auth_middleware(auth_config: AuthConfig) -> None:
|
|
|
425
437
|
"""
|
|
426
438
|
global auth_service
|
|
427
439
|
auth_service = AuthService(auth_config)
|
|
428
|
-
|
|
440
|
+
if auth_config.enable_sdk_logging:
|
|
441
|
+
logger.info(f"认证中间件已配置,白名单路径数量: {len(auth_config.get_whitelist_paths())}")
|
|
429
442
|
|
|
430
443
|
|
|
431
444
|
# 便捷的依赖函数
|
|
File without changes
|
|
File without changes
|