zaixian_cas_login.py脚本的原始脚本内容是:
#!/usr/bin/python3
# coding=utf-8
import io
import sys
import time
import requests
import json
import re
import base64
from urllib.parse import urlparse, urljoin, quote
import urllib3
import gzip
import zlib
import brotli
import chardet
from typing import Optional, Tuple, Dict
# 禁用SSL警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
SUC_RES = {
'resCode': 200,
'resTime': 0,
'keyword': 'SUCCESS',
'message': []
}
FAIL_RES = {
'resCode': 500,
'resTime': 0,
'keyword': 'FAILED',
'message': []
}
# 封装解码
class HttpResponseProcessor:
def __init__(self, url: str, headers: Optional[Dict] = None):
"""
初始化响应处理器
:param url: 请求的URL
:param headers: 请求头,默认为None
"""
self.url = url
self.headers = headers or {}
self.response = None
self.raw_content = None
self.text_content = None
self.encoding = None
self.status_code = None
def fetch_response(self):
"""
发送HTTP请求并获取响应
:return: None
"""
try:
self.response = requests.get(
url=self.url,
headers=self.headers,
allow_redirects=False, # 禁用自动重定向
stream=True, # 流模式获取原始响应
verify=False
)
self.status_code = self.response.status_code
self.raw_content = self.response.content
except Exception as e:
raise Exception(f"请求失败: {str(e)}")
def print_response_headers(self):
"""
打印响应头信息
:return: None
"""
if not self.response:
raise Exception("尚未获取响应,请先调用 fetch_response()")
def decode_content(self) -> str:
"""
尝试解码内容为文本
:return: 解码后的文本内容
"""
if not self.raw_content:
raise Exception("尚未获取原始内容,请先调用 fetch_response()")
try:
# 检测内容编码
result = chardet.detect(self.raw_content)
encoding_detected = result.get('encoding') if result else None
# 尝试解码
if encoding_detected:
try:
self.text_content = self.raw_content.decode(encoding_detected)
self.encoding = encoding_detected
return self.text_content
except UnicodeDecodeError:
# 如果检测到的编码解码失败,则尝试其他编码
pass
# 尝试常见编码
for encoding in ['utf-8', 'gbk', 'gb2312', 'latin1']:
try:
self.text_content = self.raw_content.decode(encoding)
self.encoding = encoding
break
except:
continue
else:
# 如果都无法解码,则使用替换错误字符的方式解码
try:
self.text_content = self.raw_content.decode('utf-8', errors='replace')
self.encoding = 'utf-8'
except:
self.text_content = "无法解码内容"
self.encoding = None
return self.text_content
except Exception as e:
# 将内容保存到文件以便分析
with open('response.bin', 'wb') as f:
f.write(self.raw_content)
raise Exception(f"内容解码失败: {str(e)}")
def process_response(self) -> Tuple[int, Optional[str], Optional[str]]:
"""
完整处理响应的便捷方法
:return: (status_code, text_content, encoding)
"""
self.fetch_response()
self.print_response_headers()
text = self.decode_content()
return self.status_code, text, self.encoding
def print_err_result(e):
FAIL_RES['error'] = e
exit(1)
def make_request(url, params=None, data=None, method='get', session=None):
try:
start = time.time()
req_func = session.get if session else requests.get
if method.lower() == 'post':
req_func = session.post if session else requests.post
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'application/json,text/plain,text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'X-Requested-With': 'XMLHttpRequest',
'accept-encoding': 'gzip, deflate, br,zstd'
}
response = req_func(
url,
params=params,
data=data,
verify=False,
headers=headers
)
res_time = (time.time() - start) * 1000
if response.status_code in [200, 302]:
SUC_RES['resTime'] = int(res_time)
SUC_RES['message'].append(f"请求 {url} 成功")
return response
else:
FAIL_RES['error'] = f"请求失败,状态码: {response.status_code}, 响应内容: {response.text}, 头信息:{session.headers if session else None}"
FAIL_RES['message'].append(f"请求 {url} 失败")
return None
except Exception as e:
print_err_result(f"请求过程中发生错误: {str(e)}")
return None
def cas_login(username, password) -> Tuple[Optional[str], Optional[dict]]:
# 使用会话保持cookies
session = requests.Session()
token = None
try:
# 第一步:获取lt令牌
params1 = {
'service': 'https://www.fifedu.com/iplat/ssoservice',
'get-lt': 'true',
'n': str(int(time.time() * 1000)),
'callback': 'jsonpcallback',
'_': str(int(time.time() * 1000))
}
url1 = "https://cycore.fifedu.com/cas-server/login"
response1 = make_request(url1, params=params1, session=session)
if not response1:
return None, {}
# 1. 检查响应是否以jsonpcallback开头
if not response1.text.startswith('jsonpcallback'):
raise ValueError("响应格式不符合预期,不是JSONP格式")
# 2. 提取括号内的JSON部分
json_str = response1.text[len('jsonpcallback('):-2] # 去掉首尾的jsonpcallback(和);
# 3. 将字符串解析为字典
try:
data = json.loads(json_str)
except json.JSONDecodeError:
raise ValueError("JSON解析失败,响应内容: " + response1.text)
# 4. 提取所需的值
lt = data.get('lt', '')
execution = data.get('execution', '')
if not lt or not execution:
raise ValueError("响应中缺少lt或execution字段")
# 第二步:提交登录表单
# 注意:密码是base64编码的,但这里我们直接使用传入的密码(原始代码中密码是base64编码的,所以这里我们直接使用)
# 实际上,在登录请求中,密码应该是明文还是编码?根据观察,原始代码中密码是base64编码的,但登录表单提交的是原始密码还是编码后的?
# 由于我们传入的password已经是base64编码(从get_credentials中获取的),但实际登录接口可能需要明文,所以这里需要先解码?
# 但是,在原始代码中,密码是直接以base64字符串形式传入的,而登录接口是否要求base64编码?需要根据实际接口要求。
# 由于我们不清楚,所以先按照原始代码的方式,直接传入base64字符串作为密码。
data2 = {
'service': 'https://cycore.fifedu.com/iplat/ssoservice',
'callback': 'logincallback',
'isajax': 'true',
'isframe': 'true',
'_eventId': 'submit',
'serviceURL': 'null',
'lt': lt,
'type': 'pwd',
'execution': execution,
'username': username,
'password': password,
'_': str(int(time.time() * 1000))
}
url2 = "https://cycore.fifedu.com/cas-server/login"
response2 = make_request(url2, data=data2, method='post', session=session)
if not response2:
return None, {}
# 检查登录是否成功
response_text = response2.text.strip()
if response_text.startswith("logincallback"):
json_str = response_text[len("logincallback("):-2]
try:
login_result = json.loads(json_str)
except json.JSONDecodeError:
raise ValueError("登录响应JSON解析失败: " + response_text)
token = login_result.get("token", "")
ticket = login_result.get("ticket", "")
if not token or not ticket:
raise ValueError("登录响应中缺少token或ticket")
else:
raise ValueError("登录响应格式不符合预期: " + response_text)
# 第三步:ssosevice跳转
params3 = {
'callback': f'jQuery{int(time.time()*1000000)}_{int(time.time()*1000)}',
'action': 'login',
'_': str(int(time.time() * 1000))
}
# 更新请求头,注意:这里我们不再手动设置Cookie,而是由session自动管理
session.headers.update({
'Referer': 'https://www.fifedu.com/iplat/fifLogin/scuai/index.html?service=https://assess.fifedu.com/testcenter/home/teacher_index',
'Accept': '*/*',
'Accept-encoding': 'gzip, deflate, br, zstd',
'Accept-Language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'pragma': 'no-cache',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
})
url3 = "https://www.fifedu.com/iplat/ssoservice"
response3 = session.get(
url3,
params=params3,
allow_redirects=True,
verify=False
)
if not response3:
return None, {}
# 第四步:跳转到目标页面
params4 = {
'nextPage': 'https://assess.fifedu.com/testcenter/home/teacher_index',
}
url4 = "https://www.fifedu.com/iplat/ssoservice"
# 注意:这里我们不再手动设置Cookie头,而是由session自动管理
session.headers.update({
'Referer': 'https://www.fifedu.com/iplat/fifLogin/scuai/index.html?service=https://assess.fifedu.com/testcenter/home/teacher_index',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-encoding': 'gzip, deflate, br, zstd',
'Accept-Language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'pragma': 'no-cache',
'priority': 'u=0, i',
'Upgrade-Insecure-Requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',
})
response4 = session.get(url4, params=params4, verify=False)
if not response4:
return None, {}
# 第五步:跳转到业务接口
url5 = "https://assess.fifedu.com/testcenter/home/getUser"
session.headers.update({
'Referer': 'https://assess.fifedu.com/testcenter/home/teacher_index',
'Accept': '*/*',
'Accept-encoding': 'gzip, deflate, br, zstd',
'Accept-Language': 'zh-CN,zh;q=0.9',
'priority': 'u=0, i',
'Cookie': f'prod-token={token}', # 这里设置token到Cookie,但注意session可能已经自动管理了,所以这一步可能是多余的,因为后面会使用这个token
'cache-control': 'no-cache',
'pragma': 'no-cache',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
})
response5 = session.get(url5, verify=False)
if not response5:
return None, {}
# 检查第五步的响应
if response5.status_code != 200:
raise ValueError(f"获取用户信息失败,状态码: {response5.status_code}")
# 获取session中的cookies
sess = session.cookies.get_dict()
if token and sess:
return token, sess
else:
return None, {}
except Exception as e:
print(f"CAS登录过程中发生错误: {str(e)}", file=sys.stderr)
return None, {}
def get_credentials():
username = "jffwbc1"
password = "R2pjcHgxMjMhQCM=" # base64编码的密码
# 注意:这里我们不进行解码,因为登录函数中直接使用了这个base64字符串作为密码。
# 但是,根据实际接口,可能需要明文密码,那么就需要先解码:
# password = base64.b64decode(password).decode('utf-8')
# 但是,原始代码中直接使用base64字符串作为密码,所以我们先保持原样。
return cas_login(username, password)
if __name__ == '__main__':
username = "jffwbc1"
password = "R2pjcHgxMjMhQCM="
token, sess = cas_login(username, password)
if token and sess:
print("登录成功!")
print(f"Token: {token}")
print(f"Session Cookies: {sess}")
else:
print("登录失败!")
zaixian.py脚本的原始脚本内容是:
#!/usr/bin/python3
# coding=utf-8
from zaixian_cas_login import HttpResponseProcessor
from zaixian_cas_login import get_credentials
import sys
import time
import requests
import json
import ast
from urllib.parse import urlparse, urljoin
SUC_RES = {
'resCode': 200,
'resTime': 0,
'keyword': 'SUCCESS',
'message': "调用成功",
'apiMessage': None
}
FAIL_RES = {
'resCode': 500,
'resTime': 0,
'keyword': 'FAILED',
'message': "调用失败",
'apiMessage': None
}
def print_err_result(e):
FAIL_RES['error'] = str(e)
print(json.dumps(FAIL_RES, ensure_ascii=False))
exit(1)
def parse_params(params):
"""更健壮的参数解析函数,支持多种格式"""
if not params or params.lower() == 'null':
return {}
# 尝试直接解析为标准JSON
try:
return json.loads(params)
except json.JSONDecodeError:
pass
# 尝试解析为Python字面量(支持单引号)
try:
return ast.literal_eval(params)
except (ValueError, SyntaxError):
pass
# 尝试处理类JSON格式(单引号)
try:
sanitized = params.replace("'", '"')
return json.loads(sanitized)
except json.JSONDecodeError:
pass
raise ValueError(f"无法解析的参数格式: {params}")
def _requests(full_url, params='{}'):
try:
# 解析参数
pars = parse_params(params)
# 获取请求参数
data = pars.get('data', {})
method = pars.get('method', 'GET').upper()
expected_message = pars.get('expectedMessage', None)
# 添加协议前缀
if not full_url.startswith(('http://', 'https://')):
full_url = 'https://' + full_url
# 验证URL格式
parsed_url = urlparse(full_url)
if not parsed_url.netloc:
raise ValueError("无效的URL格式,缺少域名部分")
# 确保路径正确
if not parsed_url.path.startswith('/'):
full_url = urljoin(full_url, '/')
isSuccess = True
start = time.time()
response_data = None
api_message = None
try:
# 获取认证信息
token, sess = get_credentials()
if token is None or sess is None:
raise ValueError("无法获取有效的token或session")
# 设置请求头
headers = {
'Cookie': f'prod-token={token}',
'Content-Type': 'application/json'
}
# 执行请求
if method == 'POST':
res = requests.post(url=full_url, json=data, headers=headers, verify=False)
else:
res = requests.get(url=full_url, params=data, headers=headers, verify=False)
# 处理响应
processor = HttpResponseProcessor(full_url, headers=res.headers)
processor.response = res
processor.raw_content = res.content
processor.status_code = res.status_code
try:
# 解码内容
text_content = processor.decode_content()
# 尝试解析JSON
try:
response_data = json.loads(text_content)
api_message = response_data.get('message', None)
except json.JSONDecodeError:
response_data = {'raw_response': text_content}
except Exception as e:
raise e
except requests.exceptions.SSLError as e:
raise Exception('SSL证书验证失败') from e
except Exception as e:
raise Exception('调用出现异常') from e
# 计算耗时
res_time = (time.time() - start) * 1000
# 获取状态码
try:
statusCode = response_data.get('statusCode', res.status_code)
except AttributeError:
statusCode = res.status_code
# 判断请求结果
if res.status_code != 200:
isSuccess = False
FAIL_RES['message'] = f"HTTP状态码错误: {res.status_code}"
FAIL_RES['apiMessage'] = api_message or getattr(response_data, 'resInfo', '')
elif statusCode != 200:
isSuccess = False
try:
res_info = response_data.get('responseBody', '') or response_data.get('resInfo', '')
FAIL_RES['message'] = f"业务状态码错误: {statusCode} - {res_info}"
FAIL_RES['apiMessage'] = api_message
except (ValueError, AttributeError):
FAIL_RES['message'] = '解析响应内容失败'
# 处理预期消息验证
if expected_message is not None and api_message != expected_message:
isSuccess = False
FAIL_RES['message'] = f"消息验证失败: 预期 '{expected_message}', 实际 '{api_message}'"
FAIL_RES['apiMessage'] = api_message
# 输出结果
if isSuccess:
SUC_RES['resTime'] = int(res_time)
SUC_RES['apiMessage'] = api_message
print(json.dumps(SUC_RES, ensure_ascii=False))
else:
FAIL_RES['resTime'] = int(res_time)
print(json.dumps(FAIL_RES, ensure_ascii=False))
except Exception as e:
print_err_result(e)
if __name__ == '__main__':
args = sys.argv[1:]
if len(args) < 1:
print_err_result('''
参数不足
用法: ./http_requests.py 完整URL [JSON参数]
示例: ./http_requests.py "api.example.com/endpoint" '{"data":{"key":"value"}, "method":"POST"}'
注意: JSON参数需要使用单引号包裹,内部使用双引号
''')
full_url = args[0]
params = args[1] if len(args) > 1 else '{}'
_requests(full_url, params)
最新发布