skyplatform-iam 1.1.0__py3-none-any.whl → 1.2.1__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.
@@ -17,7 +17,7 @@ from .exceptions import (
17
17
  NetworkError
18
18
  )
19
19
 
20
- __version__ = "1.0.0"
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客户端实例
skyplatform_iam/config.py CHANGED
@@ -1,14 +1,18 @@
1
1
  """
2
2
  SkyPlatform IAM SDK 配置模块
3
3
  """
4
+ import logging
4
5
  import os
5
6
  import fnmatch
6
- from typing import Optional, List
7
+ from typing import List, Optional, Dict
8
+
9
+ import jwt
7
10
  from pydantic import BaseModel, Field
8
11
  from dotenv import load_dotenv
9
12
 
10
13
  # 加载环境变量
11
14
  load_dotenv()
15
+ logger = logging.getLogger(__name__)
12
16
 
13
17
 
14
18
  class AuthConfig(BaseModel):
@@ -20,6 +24,7 @@ class AuthConfig(BaseModel):
20
24
  agenterra_iam_host: str
21
25
  server_name: str
22
26
  access_key: str
27
+ machine_token: str
23
28
 
24
29
  # Token配置
25
30
  token_header: str = "Authorization"
@@ -27,6 +32,9 @@ class AuthConfig(BaseModel):
27
32
 
28
33
  # 错误处理配置
29
34
  enable_debug: bool = False
35
+
36
+ # SDK日志控制配置
37
+ enable_sdk_logging: bool = True
30
38
 
31
39
  # 白名单路径配置(实例变量)
32
40
  whitelist_paths: List[str] = Field(default_factory=list)
@@ -44,6 +52,8 @@ class AuthConfig(BaseModel):
44
52
  server_name=os.environ.get('AGENTERRA_SERVER_NAME', ''),
45
53
  access_key=os.environ.get('AGENTERRA_ACCESS_KEY', ''),
46
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',
56
+ machine_token=os.environ.get('MACHINE_TOKEN', ''),
47
57
  whitelist_paths=[] # 初始化空的白名单路径列表
48
58
  )
49
59
 
@@ -63,15 +73,15 @@ class AuthConfig(BaseModel):
63
73
  """
64
74
  if not path:
65
75
  return path
66
-
76
+
67
77
  # 确保路径以 / 开头
68
78
  if not path.startswith('/'):
69
79
  path = '/' + path
70
-
80
+
71
81
  # 移除重复的斜杠
72
82
  while '//' in path:
73
83
  path = path.replace('//', '/')
74
-
84
+
75
85
  return path
76
86
 
77
87
  def add_whitelist_path(self, path: str) -> None:
@@ -80,7 +90,7 @@ class AuthConfig(BaseModel):
80
90
  """
81
91
  if not path:
82
92
  return
83
-
93
+
84
94
  normalized_path = self._normalize_path(path)
85
95
  if normalized_path not in self.whitelist_paths:
86
96
  self.whitelist_paths.append(normalized_path)
@@ -98,7 +108,7 @@ class AuthConfig(BaseModel):
98
108
  """
99
109
  if not path:
100
110
  return
101
-
111
+
102
112
  normalized_path = self._normalize_path(path)
103
113
  if normalized_path in self.whitelist_paths:
104
114
  self.whitelist_paths.remove(normalized_path)
@@ -121,12 +131,27 @@ class AuthConfig(BaseModel):
121
131
  """
122
132
  if not path:
123
133
  return False
124
-
134
+
125
135
  normalized_path = self._normalize_path(path)
126
-
136
+
127
137
  for whitelist_path in self.whitelist_paths:
128
138
  # 支持通配符匹配
129
139
  if fnmatch.fnmatch(normalized_path, whitelist_path):
130
140
  return True
131
-
141
+
132
142
  return False
143
+
144
+
145
+ def decode_jwt_token(token: str) -> Optional[Dict]:
146
+ """直接解析JWT token获取payload"""
147
+ try:
148
+ # 不验证签名,只解析payload(因为token已经通过verify_token验证过)
149
+ decoded_payload = jwt.decode(token, options={"verify_signature": False})
150
+ logger.debug(f"JWT token解析成功: {decoded_payload}")
151
+ return decoded_payload
152
+ except jwt.InvalidTokenError as e:
153
+ logger.error(f"JWT token解析失败: {str(e)}")
154
+ return None
155
+ except Exception as e:
156
+ logger.error(f"JWT token解析异常: {str(e)}")
157
+ return None
@@ -2,9 +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
 
9
+ from .config import decode_jwt_token
10
+
11
+ # 禁用 urllib3 的 InsecureRequestWarning 警告
12
+ # 这样可以避免在使用自定义 HTTPS 地址(可能没有有效证书)时出现警告信息
13
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
14
+
8
15
 
9
16
  class CredentialTypeEnum(str, Enum):
10
17
  """凭证类型枚举,与后端API保持一致"""
@@ -17,7 +24,7 @@ class CredentialTypeEnum(str, Enum):
17
24
  class ConnectAgenterraIam(object):
18
25
  _instance = None
19
26
  _initialized = False
20
-
27
+
21
28
  def __new__(cls, config=None, logger_name="skyplatform_iam", log_level=logging.INFO):
22
29
  """
23
30
  单例模式实现
@@ -26,11 +33,11 @@ class ConnectAgenterraIam(object):
26
33
  if cls._instance is None:
27
34
  cls._instance = super(ConnectAgenterraIam, cls).__new__(cls)
28
35
  return cls._instance
29
-
36
+
30
37
  def __init__(self, config=None, logger_name="skyplatform_iam", log_level=logging.INFO):
31
38
  """
32
39
  初始化AgenterraIAM连接器
33
-
40
+
34
41
  参数:
35
42
  - config: AuthConfig配置对象,如果为None则从环境变量读取
36
43
  - logger_name: 日志记录器名称
@@ -39,7 +46,22 @@ class ConnectAgenterraIam(object):
39
46
  # 防止重复初始化
40
47
  if self._initialized:
41
48
  return
42
-
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
+
43
65
  # 配置日志记录器
44
66
  self.logger = logging.getLogger(logger_name)
45
67
  if not self.logger.handlers:
@@ -49,17 +71,16 @@ class ConnectAgenterraIam(object):
49
71
  )
50
72
  handler.setFormatter(formatter)
51
73
  self.logger.addHandler(handler)
52
- self.logger.setLevel(log_level)
53
-
54
- # 必须传入config参数,不再支持从环境变量读取
55
- if config is None:
56
- raise ValueError("必须传入AuthConfig配置对象,不再支持从环境变量读取配置")
57
-
74
+ self.logger.setLevel(effective_log_level)
75
+
58
76
  self.agenterra_iam_host = config.agenterra_iam_host
59
77
  self.server_name = config.server_name
60
78
  self.access_key = config.access_key
61
- self.logger.info("使用传入的AuthConfig配置")
79
+ self.machine_token = config.machine_token
62
80
 
81
+ if hasattr(config, 'enable_sdk_logging') and config.enable_sdk_logging:
82
+ self.logger.info("使用传入的AuthConfig配置")
83
+
63
84
  # 验证必要的配置
64
85
  if not self.agenterra_iam_host:
65
86
  self.logger.warning("AGENTERRA_IAM_HOST 配置未设置")
@@ -67,9 +88,11 @@ class ConnectAgenterraIam(object):
67
88
  self.logger.warning("AGENTERRA_SERVER_NAME 配置未设置")
68
89
  if not self.access_key:
69
90
  self.logger.warning("AGENTERRA_ACCESS_KEY 配置未设置")
70
-
71
- self.logger.info(f"初始化AgenterraIAM连接器 - Host: {self.agenterra_iam_host}, Server: {self._mask_sensitive(self.server_name)}")
72
-
91
+
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)}")
95
+
73
96
  self.headers = {
74
97
  "Content-Type": "application/json",
75
98
  "SERVER-AK": self.server_name,
@@ -77,9 +100,10 @@ class ConnectAgenterraIam(object):
77
100
  }
78
101
  self.body = {
79
102
  "server_name": self.server_name,
80
- "access_key": self.access_key
103
+ "access_key": self.access_key,
104
+ "machine_token": self.machine_token
81
105
  }
82
-
106
+
83
107
  # 标记为已初始化
84
108
  self._initialized = True
85
109
 
@@ -87,20 +111,20 @@ class ConnectAgenterraIam(object):
87
111
  """
88
112
  重新加载配置
89
113
  用于在运行时更新配置
90
-
114
+
91
115
  参数:
92
116
  - config: AuthConfig配置对象
93
117
  """
94
118
  if config is None:
95
119
  raise ValueError("必须传入AuthConfig配置对象")
96
-
120
+
97
121
  self.logger.info("重新加载配置")
98
-
122
+
99
123
  # 更新配置
100
124
  self.agenterra_iam_host = config.agenterra_iam_host
101
125
  self.server_name = config.server_name
102
126
  self.access_key = config.access_key
103
-
127
+
104
128
  # 验证必要的配置
105
129
  if not self.agenterra_iam_host:
106
130
  self.logger.warning("AGENTERRA_IAM_HOST 配置未设置")
@@ -108,7 +132,7 @@ class ConnectAgenterraIam(object):
108
132
  self.logger.warning("AGENTERRA_SERVER_NAME 配置未设置")
109
133
  if not self.access_key:
110
134
  self.logger.warning("AGENTERRA_ACCESS_KEY 配置未设置")
111
-
135
+
112
136
  # 更新headers和body
113
137
  self.headers = {
114
138
  "Content-Type": "application/json",
@@ -117,77 +141,83 @@ class ConnectAgenterraIam(object):
117
141
  }
118
142
  self.body = {
119
143
  "server_name": self.server_name,
120
- "access_key": self.access_key
144
+ "access_key": self.access_key,
145
+ "machine_token": self.machine_token,
121
146
  }
122
-
123
- self.logger.info(f"配置重新加载完成 - Host: {self.agenterra_iam_host}, Server: {self._mask_sensitive(self.server_name)}")
147
+
148
+ self.logger.info(
149
+ f"配置重新加载完成 - Host: {self.agenterra_iam_host}, Server: {self._mask_sensitive(self.server_name)}")
124
150
 
125
151
  def _mask_sensitive(self, value, mask_char="*", show_chars=4):
126
152
  """
127
153
  脱敏处理敏感信息
128
-
154
+
129
155
  参数:
130
156
  - value: 要脱敏的值
131
157
  - mask_char: 脱敏字符
132
158
  - show_chars: 显示的字符数量
133
-
159
+
134
160
  返回: 脱敏后的字符串
135
161
  """
136
162
  if not value or not isinstance(value, str):
137
163
  return str(value) if value else "None"
138
-
164
+
139
165
  if len(value) <= show_chars:
140
166
  return mask_char * len(value)
141
-
167
+
142
168
  return value[:show_chars] + mask_char * (len(value) - show_chars)
143
169
 
144
170
  def _sanitize_log_data(self, data):
145
171
  """
146
172
  清理日志数据,脱敏敏感信息
147
-
173
+
148
174
  参数:
149
175
  - data: 要清理的数据(字典或其他类型)
150
-
176
+
151
177
  返回: 清理后的数据
152
178
  """
153
179
  if not isinstance(data, dict):
154
180
  return data
155
-
181
+
156
182
  # 需要脱敏的字段列表
157
183
  sensitive_fields = [
158
- 'password', 'access_key', 'token', 'refresh_token',
184
+ 'password', 'access_key', 'token', 'refresh_token',
159
185
  'SERVER-SK', 'new_password', 'server_sk'
160
186
  ]
161
-
187
+
162
188
  sanitized = copy.deepcopy(data)
163
-
189
+
164
190
  for key, value in sanitized.items():
165
191
  if key.lower() in [field.lower() for field in sensitive_fields]:
166
192
  sanitized[key] = self._mask_sensitive(str(value))
167
193
  elif isinstance(value, dict):
168
194
  sanitized[key] = self._sanitize_log_data(value)
169
-
195
+
170
196
  return sanitized
171
197
 
172
198
  def _log_request(self, method_name, url, headers, body):
173
199
  """记录请求信息"""
174
- sanitized_headers = self._sanitize_log_data(headers)
175
- sanitized_body = self._sanitize_log_data(body)
176
-
177
- self.logger.info(f"[{method_name}] 发送请求 - URL: {url}")
178
- self.logger.info(f"[{method_name}] 请求头: {sanitized_headers}")
179
- self.logger.info(f"[{method_name}] 请求体: {sanitized_body}")
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)
204
+
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}")
180
208
 
181
209
  def _log_response(self, method_name, response):
182
210
  """记录响应信息"""
183
- try:
184
- response_data = response.json() if response.content else {}
185
- sanitized_response = self._sanitize_log_data(response_data)
186
- self.logger.info(f"[{method_name}] 响应状态码: {response.status_code}")
187
- self.logger.info(f"[{method_name}] 响应内容: {sanitized_response}")
188
- except Exception as e:
189
- self.logger.info(f"[{method_name}] 响应状态码: {response.status_code}")
190
- self.logger.info(f"[{method_name}] 响应内容解析失败: {str(e)}")
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)}")
191
221
 
192
222
  def register(self, cred_type=None, cred_value=None, password=None, nickname=None, avatar_url=None,
193
223
  username=None, phone=None):
@@ -346,6 +376,11 @@ class ConnectAgenterraIam(object):
346
376
 
347
377
  if response.status_code == 200:
348
378
  self.logger.info(f"[{method_name}] 密码登录成功")
379
+ data = response.json()["data"]
380
+ access_token = data["access_token"]
381
+ payload = decode_jwt_token(access_token)
382
+ self.machine_token = payload["machine_access_token"]
383
+
349
384
  return response
350
385
  else:
351
386
  self.logger.warning(f"[{method_name}] 密码登录失败 - 状态码: {response.status_code}")
@@ -452,9 +487,12 @@ class ConnectAgenterraIam(object):
452
487
  # 记录请求信息
453
488
  self._log_request(method_name, url, self.headers, body)
454
489
 
490
+ headers = self.headers.copy()
491
+ headers['MACHINE-TOKEN'] = self.machine_token
492
+
455
493
  response = requests.post(
456
494
  url=url,
457
- headers=self.headers,
495
+ headers=headers,
458
496
  json=body,
459
497
  verify=False
460
498
  )
@@ -474,7 +512,7 @@ class ConnectAgenterraIam(object):
474
512
  self.logger.error(f"[{method_name}] 异常堆栈: {traceback.format_exc()}")
475
513
  return False
476
514
 
477
- def verify_token(self, token, api, method, server_ak="", server_sk=""):
515
+ def verify_token(self, token, api, method, server_ak="", server_sk="", machine_token=""):
478
516
  """
479
517
  请求iam进行鉴权
480
518
  server_name: 服务名称
@@ -494,6 +532,7 @@ class ConnectAgenterraIam(object):
494
532
  body = {
495
533
  "server_name": self.server_name,
496
534
  "access_key": self.access_key,
535
+ "machine_token": machine_token if machine_token else self.machine_token,
497
536
  "token": token,
498
537
  "api": api,
499
538
  "method": method,
@@ -600,6 +639,7 @@ class ConnectAgenterraIam(object):
600
639
  body = {
601
640
  "server_name": self.server_name,
602
641
  "access_key": self.access_key,
642
+ "machine_token": self.machine_token,
603
643
  "user_id": user_id,
604
644
  "new_password": new_password
605
645
  }
@@ -647,6 +687,7 @@ class ConnectAgenterraIam(object):
647
687
  body = {
648
688
  "server_name": self.server_name,
649
689
  "access_key": self.access_key,
690
+ "machine_token": self.machine_token,
650
691
  "refresh_token": refresh_token
651
692
  }
652
693
  uri = "/api/v2/service/refresh_token"
@@ -694,6 +735,7 @@ class ConnectAgenterraIam(object):
694
735
  body = {
695
736
  "server_name": self.server_name,
696
737
  "access_key": self.access_key,
738
+ "machine_token": self.machine_token,
697
739
  "user_id": user_id,
698
740
  "role_id": role_id
699
741
  }
@@ -736,6 +778,7 @@ class ConnectAgenterraIam(object):
736
778
  body = {
737
779
  "server_name": self.server_name,
738
780
  "access_key": self.access_key,
781
+ "machine_token": self.machine_token,
739
782
  "token": token,
740
783
  }
741
784
  uri = "/api/v2/service/token"
@@ -788,6 +831,7 @@ class ConnectAgenterraIam(object):
788
831
  body = {
789
832
  "server_name": self.server_name,
790
833
  "access_key": self.access_key,
834
+ "machine_token": self.machine_token,
791
835
  "user_id": user_id,
792
836
  "config_name": config_name
793
837
  }
@@ -844,6 +888,7 @@ class ConnectAgenterraIam(object):
844
888
  body = {
845
889
  "server_name": self.server_name,
846
890
  "access_key": self.access_key,
891
+ "machine_token": self.machine_token,
847
892
  "user_id": user_id
848
893
  }
849
894
 
@@ -896,6 +941,7 @@ class ConnectAgenterraIam(object):
896
941
  body = {
897
942
  "server_name": self.server_name,
898
943
  "access_key": self.access_key,
944
+ "machine_token": self.machine_token,
899
945
  "user_id": user_id,
900
946
  "config_name": config_name
901
947
  }
@@ -957,6 +1003,7 @@ class ConnectAgenterraIam(object):
957
1003
  body = {
958
1004
  "server_name": self.server_name,
959
1005
  "access_key": self.access_key,
1006
+ "machine_token": self.machine_token,
960
1007
  "target_user_id": target_user_id,
961
1008
  "cred_type": cred_type.value,
962
1009
  "cred_value": cred_value
@@ -1021,6 +1068,7 @@ class ConnectAgenterraIam(object):
1021
1068
  body = {
1022
1069
  "server_name": self.server_name,
1023
1070
  "access_key": self.access_key,
1071
+ "machine_token": self.machine_token,
1024
1072
  "cred_type": cred_type.value,
1025
1073
  "cred_value": cred_value
1026
1074
  }
@@ -66,10 +66,18 @@ class AuthMiddleware(BaseHTTPMiddleware):
66
66
  try:
67
67
  # 获取请求路径
68
68
  api_path = request.url.path
69
-
69
+ method = request.method
70
+
71
+ # 检查是否为机机接口鉴权 来自其他服务
72
+ server_ak = request.headers.get('SERVER-AK')
73
+ server_sk = request.headers.get('SERVER-SK')
74
+ # 检查是否为人机接口鉴权 来自前端
75
+ # token = request.headers.get('Authorization')
76
+
70
77
  # 首先检查路径是否在本地白名单中
71
78
  if self.is_path_whitelisted(api_path):
72
- logger.info(f"路径 {api_path} 在本地白名单中,跳过认证直接允许访问")
79
+ if self.config.enable_sdk_logging:
80
+ logger.info(f"路径 {api_path} 在本地白名单中,跳过认证直接允许访问")
73
81
  # 设置白名单标识
74
82
  request.state.user = None
75
83
  request.state.authenticated = False
@@ -80,6 +88,7 @@ class AuthMiddleware(BaseHTTPMiddleware):
80
88
 
81
89
  # 提取Token(可能为空,白名单接口不需要token)
82
90
  token = self._extract_token(request)
91
+ machine_token = self._extract_machine_token(request)
83
92
 
84
93
  # 验证Token和权限(即使token为空也要调用IAM验证,因为可能是白名单接口)
85
94
  user_info = await self._verify_token_and_permission(request, token)
@@ -97,6 +106,16 @@ class AuthMiddleware(BaseHTTPMiddleware):
97
106
  request.state.authenticated = False
98
107
  request.state.is_whitelist = True
99
108
  else:
109
+ if machine_token:
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,但继续处理
118
+
100
119
  # 正常认证接口,设置用户信息
101
120
  request.state.user = user_info
102
121
  request.state.authenticated = True
@@ -125,9 +144,10 @@ class AuthMiddleware(BaseHTTPMiddleware):
125
144
  detail=e.detail
126
145
  )
127
146
  except Exception as e:
128
- logger.error(f"认证中间件处理异常: {str(e)}")
129
- if self.config.enable_debug:
130
- logger.exception("详细异常信息:")
147
+ if self.config.enable_sdk_logging:
148
+ logger.error(f"认证中间件处理异常: {str(e)}")
149
+ if self.config.enable_debug:
150
+ logger.exception("详细异常信息:")
131
151
 
132
152
  return self._create_error_response(
133
153
  status_code=500,
@@ -151,6 +171,17 @@ class AuthMiddleware(BaseHTTPMiddleware):
151
171
 
152
172
  return None
153
173
 
174
+ def _extract_machine_token(self, request: Request) -> Optional[str]:
175
+ """
176
+ 从请求中提取Token
177
+ """
178
+ # 从Authorization头提取
179
+ machine_token = request.headers.get("MACHINE-TOKEN")
180
+ if machine_token:
181
+ return machine_token
182
+
183
+ return None
184
+
154
185
  async def _verify_token_and_permission(self, request: Request, token: Optional[str]) -> Optional[Dict[str, Any]]:
155
186
  """
156
187
  验证Token和权限
@@ -163,6 +194,7 @@ class AuthMiddleware(BaseHTTPMiddleware):
163
194
  # 从请求头获取服务认证信息(可选)
164
195
  server_ak = request.headers.get("SERVER-AK", "")
165
196
  server_sk = request.headers.get("SERVER-SK", "")
197
+ machine_token = request.headers.get("MACHINE-TOKEN", "")
166
198
 
167
199
  # 调用IAM验证接口(即使token为空也要调用,因为可能是白名单接口)
168
200
  user_info = self.iam_client.verify_token(
@@ -170,7 +202,8 @@ class AuthMiddleware(BaseHTTPMiddleware):
170
202
  api=api_path,
171
203
  method=method,
172
204
  server_ak=server_ak,
173
- server_sk=server_sk
205
+ server_sk=server_sk,
206
+ machine_token=machine_token
174
207
  )
175
208
 
176
209
  return user_info
@@ -179,9 +212,10 @@ class AuthMiddleware(BaseHTTPMiddleware):
179
212
  # 重新抛出HTTP异常
180
213
  raise
181
214
  except Exception as e:
182
- logger.error(f"Token验证异常: {str(e)}")
183
- if self.config.enable_debug:
184
- logger.exception("详细异常信息:")
215
+ if self.config.enable_sdk_logging:
216
+ logger.error(f"Token验证异常: {str(e)}")
217
+ if self.config.enable_debug:
218
+ logger.exception("详细异常信息:")
185
219
  return None
186
220
 
187
221
  def _create_error_response(
@@ -233,22 +267,24 @@ class AuthService:
233
267
  """验证token和权限"""
234
268
  # 通过token, server_ak, server_sk判断是否有权限
235
269
  api_path = request.url.path
236
-
270
+
237
271
  # 首先检查路径是否在白名单中
238
272
  if self.is_path_whitelisted(api_path):
239
- logger.info(f"路径 {api_path} 在白名单中,跳过IAM鉴权")
273
+ if self.auth_config.enable_sdk_logging:
274
+ logger.info(f"路径 {api_path} 在白名单中,跳过IAM鉴权")
240
275
  return True
241
-
276
+
242
277
  credentials: HTTPAuthorizationCredentials = await self.security(request)
243
278
  method = request.method
244
279
 
245
280
  server_ak = request.headers.get("SERVER-AK", "")
246
281
  server_sk = request.headers.get("SERVER-SK", "")
282
+ machine_token = request.headers.get("MACHINE-TOKEN", "")
247
283
 
248
284
  token = ""
249
285
  if credentials is not None:
250
286
  token = credentials.credentials
251
- user_info_by_iam = self.iam_client.verify_token(token, api_path, method, server_ak, server_sk)
287
+ user_info_by_iam = self.iam_client.verify_token(token, api_path, method, server_ak, server_sk, machine_token)
252
288
  if user_info_by_iam:
253
289
  return True
254
290
  return False
@@ -264,13 +300,14 @@ class AuthService:
264
300
  credentials: HTTPAuthorizationCredentials = await self.security(request)
265
301
  if not credentials:
266
302
  return None
267
-
303
+
268
304
  token = credentials.credentials
269
305
 
270
306
  # 直接解析JWT token获取payload
271
307
  payload = self.decode_jwt_token(token)
272
308
  if not payload:
273
- logger.error("JWT token解析失败")
309
+ if self.auth_config.enable_sdk_logging:
310
+ logger.error("JWT token解析失败")
274
311
  return None
275
312
 
276
313
  # 从payload中提取用户信息
@@ -326,20 +363,23 @@ class AuthService:
326
363
  cred_types = [cred.get("type") for cred in all_credentials]
327
364
  cred_type_count = {cred_type: cred_types.count(cred_type) for cred_type in set(cred_types)}
328
365
 
329
- logger.info(
330
- f"用户认证成功: user_id={iam_user_id}, username={username}, 凭证数量={total_credentials}, 凭证类型分布={cred_type_count}")
331
- logger.debug(f"JWT payload: {payload}")
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}")
332
370
 
333
371
  # 将用户信息添加到请求状态中
334
372
  request.state.user = user_info
335
373
  return user_info
336
374
 
337
375
  except HTTPException as e:
338
- logger.error(f"获取当前用户信息失败: {str(e)}")
376
+ if self.auth_config.enable_sdk_logging:
377
+ logger.error(f"获取当前用户信息失败: {str(e)}")
339
378
  # 重新抛出HTTP异常(403权限不足)
340
379
  return None
341
380
  except Exception as e:
342
- logger.error(f"获取当前用户信息失败: {str(e)}")
381
+ if self.auth_config.enable_sdk_logging:
382
+ logger.error(f"获取当前用户信息失败: {str(e)}")
343
383
  return None
344
384
 
345
385
  async def require_auth(self, request: Request) -> Dict:
@@ -371,13 +411,16 @@ class AuthService:
371
411
  try:
372
412
  # 不验证签名,只解析payload(因为token已经通过verify_token验证过)
373
413
  decoded_payload = jwt.decode(token, options={"verify_signature": False})
374
- logger.debug(f"JWT token解析成功: {decoded_payload}")
414
+ if self.auth_config.enable_sdk_logging:
415
+ logger.debug(f"JWT token解析成功: {decoded_payload}")
375
416
  return decoded_payload
376
417
  except jwt.InvalidTokenError as e:
377
- logger.error(f"JWT token解析失败: {str(e)}")
418
+ if self.auth_config.enable_sdk_logging:
419
+ logger.error(f"JWT token解析失败: {str(e)}")
378
420
  return None
379
421
  except Exception as e:
380
- logger.error(f"JWT token解析异常: {str(e)}")
422
+ if self.auth_config.enable_sdk_logging:
423
+ logger.error(f"JWT token解析异常: {str(e)}")
381
424
  return None
382
425
 
383
426
 
@@ -394,7 +437,8 @@ def setup_auth_middleware(auth_config: AuthConfig) -> None:
394
437
  """
395
438
  global auth_service
396
439
  auth_service = AuthService(auth_config)
397
- logger.info(f"认证中间件已配置,白名单路径数量: {len(auth_config.get_whitelist_paths())}")
440
+ if auth_config.enable_sdk_logging:
441
+ logger.info(f"认证中间件已配置,白名单路径数量: {len(auth_config.get_whitelist_paths())}")
398
442
 
399
443
 
400
444
  # 便捷的依赖函数
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skyplatform-iam
3
- Version: 1.1.0
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/
@@ -0,0 +1,8 @@
1
+ skyplatform_iam/__init__.py,sha256=6ffSGMRItbBPnxgQMaoeB1oGLfXuMK3HHrspuiqyO8s,5591
2
+ skyplatform_iam/config.py,sha256=x3GBikMkC8-0og0Vhs6h5_nTcBXeEd8euDgVDd7IsK4,4607
3
+ skyplatform_iam/connect_agenterra_iam.py,sha256=t03IdWCwBP1oOF1WBTSZETB4MUKQ7zTmfvc6zXD83fg,42050
4
+ skyplatform_iam/exceptions.py,sha256=Rt55QIzVK1F_kn6yzKQKKakD6PZDFdPLCGaCphKKms8,2166
5
+ skyplatform_iam/middleware.py,sha256=jXX8_UciUhOkbK418anqtJoclNhvTQeFnguwPjv5K9E,17863
6
+ skyplatform_iam-1.2.1.dist-info/METADATA,sha256=HLkBft7Qp6w9NFpsHtkVJWFXLcHNZDJ_7_AaXYprZ6E,12658
7
+ skyplatform_iam-1.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ skyplatform_iam-1.2.1.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- skyplatform_iam/__init__.py,sha256=JfCqvHdzjiwkEVhgzlB45pKKs0VkxKg8yrMMQ4NpWpY,4664
2
- skyplatform_iam/config.py,sha256=s4tctVpguKZv4O1Fhf7_Fo7zELNX6KYviMjkE1WPbQM,3715
3
- skyplatform_iam/connect_agenterra_iam.py,sha256=9SnZkYmZ1VewzIz6a3-uOdOLzHrhPyJPIofZHgImJCM,39785
4
- skyplatform_iam/exceptions.py,sha256=Rt55QIzVK1F_kn6yzKQKKakD6PZDFdPLCGaCphKKms8,2166
5
- skyplatform_iam/middleware.py,sha256=XNJxvjw3O55TW-ff_uORK-C9Wy4BTAfkcnNjy1SQkx0,15721
6
- skyplatform_iam-1.1.0.dist-info/METADATA,sha256=K3G-22HfmCKyzHOK_zvrpGDzez_gpEvrqon7nsvG4wg,12658
7
- skyplatform_iam-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
- skyplatform_iam-1.1.0.dist-info/RECORD,,