Delete unibo_auth2_get_AuthCode_RefreshToken_sucsses.py
Browse files
unibo_auth2_get_AuthCode_RefreshToken_sucsses.py
DELETED
@@ -1,391 +0,0 @@
|
|
1 |
-
import concurrent.futures
|
2 |
-
import json
|
3 |
-
import os
|
4 |
-
import re
|
5 |
-
import threading
|
6 |
-
from datetime import datetime
|
7 |
-
|
8 |
-
import httpx
|
9 |
-
from bs4 import BeautifulSoup
|
10 |
-
from urllib.parse import urljoin, urlencode
|
11 |
-
from loguru import logger
|
12 |
-
|
13 |
-
# logger.stop()
|
14 |
-
# 定义线程安全计数器和锁
|
15 |
-
success_counter = 0
|
16 |
-
error_counter = 0
|
17 |
-
# 定义文件名 filename = user-当前月份-日期.txt
|
18 |
-
current_date = datetime.now().strftime("%m-%d")
|
19 |
-
# filename = f'user-{current_date}.txt'
|
20 |
-
counter_lock = threading.Lock()
|
21 |
-
success_file_lock = threading.Lock()
|
22 |
-
error_file_lock = threading.Lock()
|
23 |
-
|
24 |
-
|
25 |
-
class OAuth2Authenticator:
|
26 |
-
def __init__(self, username, password):
|
27 |
-
self.username = username
|
28 |
-
self.password = password
|
29 |
-
self.client_id = '9e5f94bc-e8a4-4e73-b8be-63364c29d753'
|
30 |
-
self.session = httpx.Client(timeout=30.0, follow_redirects=True, verify=False)
|
31 |
-
self.base_urls = {
|
32 |
-
'microsoft': 'https://login.microsoftonline.com',
|
33 |
-
'idp': 'https://idp.unibo.it'
|
34 |
-
}
|
35 |
-
self.current_state = {} # 用于存储流程中的临时数据
|
36 |
-
|
37 |
-
def _extract_input_value(self, html, name):
|
38 |
-
"""从HTML中提取指定名称的input值"""
|
39 |
-
soup = BeautifulSoup(html, 'html.parser')
|
40 |
-
element = soup.find('input', {'name': name})
|
41 |
-
return element['value'] if element else None
|
42 |
-
|
43 |
-
def _make_request(self, method, url, data=None, **kwargs):
|
44 |
-
"""封装请求方法,统一处理异常"""
|
45 |
-
try:
|
46 |
-
# 设置默认的请求头
|
47 |
-
self.session.headers = {
|
48 |
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Thunderbird/137.0',
|
49 |
-
# 'Accept': 'application/json, text/javascript, */*; q=0.01',
|
50 |
-
# 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
51 |
-
# 'X-Requested-With': 'XMLHttpRequest'
|
52 |
-
}
|
53 |
-
|
54 |
-
response = self.session.request(method, url, data=data, **kwargs)
|
55 |
-
url = response.url
|
56 |
-
print(f"请求的URL: {url}")
|
57 |
-
# response.raise_for_status()
|
58 |
-
return response
|
59 |
-
except httpx.ConnectError as e:
|
60 |
-
# print(f"连接失败的目标URL: {e.request.url}")
|
61 |
-
# print(f"错误详情: {e}")
|
62 |
-
raise ValueError(e.request.url)
|
63 |
-
except Exception as e:
|
64 |
-
print(f"其他错误: {e}")
|
65 |
-
raise
|
66 |
-
|
67 |
-
def _build_auth_url(self):
|
68 |
-
"""构建初始认证URL"""
|
69 |
-
params = {
|
70 |
-
'response_type': 'code',
|
71 |
-
'client_id': self.client_id,
|
72 |
-
'redirect_uri': 'https://localhost',
|
73 |
-
'scope': 'https://outlook.office.com/EWS.AccessAsUser.All https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send offline_access',
|
74 |
-
'login_hint': self.username
|
75 |
-
}
|
76 |
-
return f"{self.base_urls['microsoft']}/common/oauth2/v2.0/authorize?{urlencode(params)}"
|
77 |
-
|
78 |
-
def step1_get_initial_login_page(self):
|
79 |
-
"""第一步: 获取初始登录页面并提取SAML参数"""
|
80 |
-
logger.info("执行第一步: 获取初始登录页面")
|
81 |
-
auth_url = self._build_auth_url()
|
82 |
-
response = self._make_request('GET', auth_url)
|
83 |
-
|
84 |
-
# self.current_state['relay_state'] = self._extract_input_value(response.text, 'RelayState')
|
85 |
-
# self.current_state['saml_request'] = self._extract_input_value(response.text, 'SAMLRequest')
|
86 |
-
|
87 |
-
# if not all([self.current_state['relay_state'], self.current_state['saml_request']]):
|
88 |
-
# raise ValueError("无法从登录页面提取必要的SAML参数")
|
89 |
-
config_match = re.search(r'\$Config=({.*?//])', response.text, re.DOTALL)
|
90 |
-
if config_match:
|
91 |
-
config_value = config_match.group(1).replace('//]', '').strip()
|
92 |
-
self.current_state['config'] = json.loads(config_value[:-1])
|
93 |
-
else:
|
94 |
-
raise ValueError("无法从响应中提取配置信息")
|
95 |
-
return response
|
96 |
-
|
97 |
-
def step2_submit_saml_request(self):
|
98 |
-
"""第二步: 提交SAML请求到IDP"""
|
99 |
-
logger.info("执行第二步: 提交SAML请求到IDP")
|
100 |
-
|
101 |
-
data = {
|
102 |
-
"UserName": self.username,
|
103 |
-
"Password": self.password,
|
104 |
-
"AuthMethod": "FormsAuthentication"
|
105 |
-
}
|
106 |
-
idp_sso_url = self.current_state['config'].get('bsso').get('failureRedirectUrl')
|
107 |
-
logger.info(f"IDP SSO URL: {idp_sso_url}")
|
108 |
-
response = self._make_request('POST', idp_sso_url, data=data)
|
109 |
-
# 提取配置信息
|
110 |
-
logger.info(f"LAST URL: {response.url}")
|
111 |
-
|
112 |
-
self.current_state['wa'] = self._extract_input_value(response.text, 'wa')
|
113 |
-
self.current_state['wresult'] = self._extract_input_value(response.text, 'wresult')
|
114 |
-
self.current_state['wctx'] = self._extract_input_value(response.text, 'wctx')
|
115 |
-
# if not self.current_state['csrf_token']:
|
116 |
-
# raise ValueError("无法从响应中提取CSRF令牌")
|
117 |
-
|
118 |
-
return response
|
119 |
-
|
120 |
-
def step3_submit_loginsrf_response(self):
|
121 |
-
"""第三步: 提交登录响应"""
|
122 |
-
logger.info("执行第三步: 提交登录响应")
|
123 |
-
|
124 |
-
response = self._make_request('POST',
|
125 |
-
urljoin(self.base_urls['microsoft'], '/login.srf'),
|
126 |
-
data={
|
127 |
-
"wa": self.current_state['wa'],
|
128 |
-
"wresult": self.current_state['wresult'],
|
129 |
-
"wctx": self.current_state['wctx']
|
130 |
-
}
|
131 |
-
)
|
132 |
-
config_match = re.search(r'\$Config=({.*?//])', response.text, re.DOTALL)
|
133 |
-
if config_match:
|
134 |
-
config_value = config_match.group(1).replace('//]', '').strip()
|
135 |
-
self.current_state['config'] = json.loads(config_value[:-1])
|
136 |
-
else:
|
137 |
-
raise ValueError("无法从响应中提取配置信息")
|
138 |
-
return response
|
139 |
-
|
140 |
-
def step6_submit_saml_response(self):
|
141 |
-
"""第六步: 处理授权同意"""
|
142 |
-
logger.info("执行第六步: 处理授权同意")
|
143 |
-
if 'config' not in self.current_state:
|
144 |
-
raise ValueError("缺少配置信息")
|
145 |
-
|
146 |
-
config = self.current_state['config']
|
147 |
-
response = self._make_request('POST',
|
148 |
-
urljoin(self.base_urls['microsoft'], '/appverify'),
|
149 |
-
data={
|
150 |
-
"ContinueAuth": True,
|
151 |
-
"ctx": config.get("sCtx"),
|
152 |
-
"hpgrequestid": config.get("sessionId"),
|
153 |
-
"flowToken": config.get("sFT"),
|
154 |
-
"iscsrfspeedbump": True,
|
155 |
-
"canary": config.get("canary"),
|
156 |
-
"i19": 492026
|
157 |
-
}
|
158 |
-
)
|
159 |
-
|
160 |
-
# 提取配置信息
|
161 |
-
config_match = re.search(r'\$Config=({.*?//])', response.text, re.DOTALL)
|
162 |
-
if config_match:
|
163 |
-
config_value = config_match.group(1).replace('//]', '').strip()
|
164 |
-
self.current_state['config'] = json.loads(config_value[:-1])
|
165 |
-
else:
|
166 |
-
raise ValueError("无法从响应中提取配置信息")
|
167 |
-
|
168 |
-
return response
|
169 |
-
|
170 |
-
def step7_handle_consent(self):
|
171 |
-
"""第七步: 处理授权同意"""
|
172 |
-
logger.info("执行第七步: 处理授权同意")
|
173 |
-
if 'config' not in self.current_state:
|
174 |
-
raise ValueError("缺少配置信息")
|
175 |
-
|
176 |
-
config = self.current_state['config']
|
177 |
-
response = self._make_request('POST',
|
178 |
-
urljoin(self.base_urls['microsoft'], '/common/Consent/Set'),
|
179 |
-
data={
|
180 |
-
"acceptConsent": True,
|
181 |
-
"ctx": config.get("sCtx"),
|
182 |
-
"hpgrequestid": config.get("sessionId"),
|
183 |
-
"flowToken": config.get("sFT"),
|
184 |
-
"canary": config.get("canary"),
|
185 |
-
"i19": 958761
|
186 |
-
}
|
187 |
-
)
|
188 |
-
|
189 |
-
if 'localhost/?code=' in response.url:
|
190 |
-
self.current_state['auth_code'] = self._extract_auth_code(response.url)
|
191 |
-
return response
|
192 |
-
|
193 |
-
raise ValueError("未能成功获取授权码")
|
194 |
-
|
195 |
-
def _extract_auth_code(self, url):
|
196 |
-
"""从URL中提取授权码"""
|
197 |
-
logger.info(f'提取授权码: {url}')
|
198 |
-
url = str(url)
|
199 |
-
match = re.search(r'code=([^&]+)', url)
|
200 |
-
return match.group(1) if match else None
|
201 |
-
|
202 |
-
def get_refresh_token(self, code):
|
203 |
-
"""获取刷新令牌"""
|
204 |
-
url = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
|
205 |
-
data = {
|
206 |
-
'client_id': self.client_id,
|
207 |
-
'grant_type': 'authorization_code',
|
208 |
-
'redirect_uri': 'https://localhost',
|
209 |
-
'code': code
|
210 |
-
}
|
211 |
-
response = self._make_request('POST', url, data=data)
|
212 |
-
response_data = response.json()
|
213 |
-
if 'error' in response_data:
|
214 |
-
raise ValueError(f"获取刷新令牌失败: {response_data['error_description']}")
|
215 |
-
refresh_token = response_data.get('refresh_token')
|
216 |
-
access_token = response_data.get('access_token')
|
217 |
-
return refresh_token, access_token
|
218 |
-
|
219 |
-
def execute_flow(self):
|
220 |
-
"""执行完整的OAuth2认证流程"""
|
221 |
-
try:
|
222 |
-
# 更新每一步的last_url
|
223 |
-
response = self.step1_get_initial_login_page()
|
224 |
-
self.current_state['last_url'] = response.url
|
225 |
-
response = self.step2_submit_saml_request()
|
226 |
-
self.current_state['last_url'] = response.url
|
227 |
-
response = self.step3_submit_loginsrf_response()
|
228 |
-
self.current_state['last_url'] = response.url
|
229 |
-
response = self.step6_submit_saml_response()
|
230 |
-
self.current_state['last_url'] = response.url
|
231 |
-
|
232 |
-
response = self.step7_handle_consent()
|
233 |
-
self.current_state['last_url'] = response.url
|
234 |
-
|
235 |
-
if 'auth_code' in self.current_state:
|
236 |
-
return self.current_state['auth_code']
|
237 |
-
|
238 |
-
raise Exception("认证流程未完成")
|
239 |
-
|
240 |
-
except Exception as e:
|
241 |
-
code = self._extract_auth_code(e)
|
242 |
-
if code:
|
243 |
-
# logger.info(f"提取到的授权码: {code}")
|
244 |
-
return code
|
245 |
-
else:
|
246 |
-
logger.info(f"认证流程出错: {e}")
|
247 |
-
logger.info("未能提取到授权码")
|
248 |
-
raise
|
249 |
-
|
250 |
-
|
251 |
-
def handle_success(username, original_line_new):
|
252 |
-
"""处理成功账号,线程安全地写入文件并更新计数器"""
|
253 |
-
global success_counter
|
254 |
-
try:
|
255 |
-
with success_file_lock:
|
256 |
-
with open(success_file, 'a', encoding='utf-8') as file:
|
257 |
-
file.write(original_line_new + '\n')
|
258 |
-
|
259 |
-
with counter_lock:
|
260 |
-
success_counter += 1
|
261 |
-
current_count = success_counter
|
262 |
-
|
263 |
-
logger.info(f"{username} 已写入成功文件,当前成功数: {success_counter},当前失败数: {error_counter}")
|
264 |
-
except Exception as e:
|
265 |
-
logger.error(f"写入成功账号 {username} 时出错: {str(e)}")
|
266 |
-
|
267 |
-
|
268 |
-
def handle_failure(username, original_line):
|
269 |
-
"""处理失败账号,线程安全地写入文件并更新计数器"""
|
270 |
-
global error_counter
|
271 |
-
|
272 |
-
try:
|
273 |
-
with error_file_lock:
|
274 |
-
with open(error_file, 'a', encoding='utf-8') as file:
|
275 |
-
file.write(original_line + '\n')
|
276 |
-
|
277 |
-
with counter_lock:
|
278 |
-
error_counter += 1
|
279 |
-
current_count = error_counter
|
280 |
-
|
281 |
-
logger.info(f"{username} 已写入失败文件,当前成功数: {success_counter},当前失败数: {error_counter}")
|
282 |
-
except Exception as e:
|
283 |
-
logger.error(f"写入失败账号 {username} 时出错: {str(e)}")
|
284 |
-
|
285 |
-
|
286 |
-
def read_user_credentials(filepath='user.txt'):
|
287 |
-
"""从user.txt文件中读取用户名和密码"""
|
288 |
-
credentials = []
|
289 |
-
try:
|
290 |
-
with open(filepath, 'r', encoding='utf-8') as file:
|
291 |
-
for line in file:
|
292 |
-
parts = line.strip().split('---')
|
293 |
-
if len(parts) >= 3: # 至少需要时间、用户名和密码
|
294 |
-
timestamp = parts[0]
|
295 |
-
username = parts[1]
|
296 |
-
password = parts[2]
|
297 |
-
email = parts[3] if len(parts) > 3 else ""
|
298 |
-
credentials.append((username, password, email, line.strip()))
|
299 |
-
logger.info(f"成功从{filepath}读取了{len(credentials)}个账号信息")
|
300 |
-
return credentials
|
301 |
-
except Exception as e:
|
302 |
-
logger.error(f"读取凭据文件时出错: {str(e)}")
|
303 |
-
return []
|
304 |
-
|
305 |
-
|
306 |
-
def ensure_files_exist():
|
307 |
-
"""确保输出文件存在"""
|
308 |
-
|
309 |
-
files = [success_file, error_file]
|
310 |
-
for file in files:
|
311 |
-
try:
|
312 |
-
if not os.path.exists(file):
|
313 |
-
with open(file, 'w', encoding='utf-8') as f:
|
314 |
-
pass
|
315 |
-
logger.info(f"创建文件 {file}")
|
316 |
-
except Exception as e:
|
317 |
-
logger.error(f"创建文件 {file} 时出错: {str(e)}")
|
318 |
-
return False
|
319 |
-
return True
|
320 |
-
def main_threaded(username, password, original_line):
|
321 |
-
authenticator = OAuth2Authenticator(
|
322 |
-
username=username,
|
323 |
-
password=password, # 实际使用时应从安全来源获取密码
|
324 |
-
)
|
325 |
-
|
326 |
-
try:
|
327 |
-
auth_code = authenticator.execute_flow()
|
328 |
-
refresh_token, access_token = authenticator.get_refresh_token(auth_code)
|
329 |
-
print(f"成功获取授权码:\n{auth_code}\n{refresh_token}\n{access_token}\n")
|
330 |
-
# 报错
|
331 |
-
original_line_new = f'{original_line}---{refresh_token}'
|
332 |
-
handle_success(username, original_line_new)
|
333 |
-
except Exception as e:
|
334 |
-
print(f"认证失败: {e}")
|
335 |
-
handle_failure(username, original_line)
|
336 |
-
|
337 |
-
# 使用示例
|
338 |
-
if __name__ == "__main__":
|
339 |
-
|
340 |
-
# authenticator = OAuth2Authenticator(
|
341 |
-
# username='[email protected]',
|
342 |
-
# password='W17M&HQK^x1q7h.', # 实际使用时应从安全来源获取密码
|
343 |
-
# )
|
344 |
-
#
|
345 |
-
# try:
|
346 |
-
# auth_code = authenticator.execute_flow()
|
347 |
-
# refresh_token, access_token = authenticator.get_refresh_token(auth_code)
|
348 |
-
# print(f"成功获取授权码:\n{auth_code}\n{refresh_token}\n{access_token}\n")
|
349 |
-
# except Exception as e:
|
350 |
-
# print(f"认证失败: {e}")
|
351 |
-
filename = 'user-4-21-success.txt'
|
352 |
-
success_file = filename.replace('success.txt', 'refresh-success.txt')
|
353 |
-
error_file = filename.replace('error.txt', 'refresh-error.txt')
|
354 |
-
filepath = filename
|
355 |
-
credentials = read_user_credentials(filepath)
|
356 |
-
if not credentials:
|
357 |
-
logger.error("没有读取到有效凭据,退出程序")
|
358 |
-
exit(1)
|
359 |
-
|
360 |
-
# 确保输出文件存在
|
361 |
-
if not ensure_files_exist():
|
362 |
-
logger.error("创建输出文件失败,退出程序")
|
363 |
-
exit(1)
|
364 |
-
|
365 |
-
# 设置最大线程数
|
366 |
-
max_workers = 1 # 可以根据需要调整线程数量
|
367 |
-
|
368 |
-
logger.info(f"开始多线程处理账号,最大并发数: {max_workers}")
|
369 |
-
|
370 |
-
# 使用线程池处理账号
|
371 |
-
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
372 |
-
# 提交所有任务
|
373 |
-
futures = []
|
374 |
-
for username, password, email, original_line in credentials:
|
375 |
-
# 提交任务到线程池
|
376 |
-
future = executor.submit(
|
377 |
-
main_threaded,
|
378 |
-
username,
|
379 |
-
password,
|
380 |
-
original_line
|
381 |
-
)
|
382 |
-
futures.append(future)
|
383 |
-
|
384 |
-
# 等待所有任务完成
|
385 |
-
for future in concurrent.futures.as_completed(futures):
|
386 |
-
try:
|
387 |
-
future.result() # 获取结果,但我们不需要处理
|
388 |
-
except Exception as e:
|
389 |
-
logger.error(f"执行任务时发生异常: {str(e)}")
|
390 |
-
|
391 |
-
logger.info(f"处理完成,共成功 {success_counter} 个账号,失败 {error_counter} 个账号。")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|