Files
LoveACE-EndF/loveace/router/endpoint/aac/utils/aac_ticket.py
Sibuxiangx bbc86b8330 ⚒️ 重大重构 LoveACE V2
引入了 mongodb
对数据库进行了一定程度的数据加密
性能改善
代码简化
统一错误模型和响应
使用 apifox 作为文档
2025-11-20 20:44:25 +08:00

168 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from urllib.parse import unquote
from fastapi import Depends
from httpx import Headers
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from loveace.config.manager import config_manager
from loveace.database.aac.ticket import AACTicket
from loveace.database.creator import get_db_session
from loveace.router.dependencies.auth import ProtectRouterErrorToCode
from loveace.router.endpoint.aac.model.base import AACConfig
from loveace.service.remote.aufe import AUFEConnection
from loveace.service.remote.aufe.depends import get_aufe_conn
from loveace.utils.rsa import RSAUtils
rsa = RSAUtils.get_or_create_rsa_utils(AACConfig.RSA_PRIVATE_KEY_PATH)
def _extract_and_encrypt_token(location: str, logger) -> str | None:
"""从重定向URL中提取并加密系统令牌"""
try:
sys_token = location.split("ticket=")[-1]
# URL编码转为正常字符串
sys_token = unquote(sys_token)
if not sys_token:
logger.error("系统令牌为空")
return None
logger.info(f"获取到系统令牌: {sys_token[:10]}...")
# 加密系统令牌
encrypted_token = rsa.encrypt(sys_token)
return encrypted_token
except Exception as e:
logger.error(f"解析/加密系统令牌失败: {str(e)}")
return None
async def get_system_token(conn: AUFEConnection) -> str:
next_location = AACConfig.LOGIN_SERVICE_URL
max_redirects = 10 # 防止无限重定向
redirect_count = 0
try:
while redirect_count < max_redirects:
response = await conn.client.get(
next_location, follow_redirects=False, timeout=conn.timeout
)
# 如果是重定向,继续跟踪
if response.status_code in (301, 302, 303, 307, 308):
next_location = response.headers.get("Location")
if not next_location:
conn.logger.error("重定向响应中缺少 Location 头")
return ""
conn.logger.debug(f"重定向到: {next_location}")
redirect_count += 1
if "register?ticket=" in next_location:
conn.logger.info(f"重定向到爱安财注册页面: {next_location}")
encrypted_token = _extract_and_encrypt_token(
next_location, conn.logger
)
return encrypted_token if encrypted_token else ""
else:
break
if redirect_count >= max_redirects:
conn.logger.error(f"重定向次数过多 ({max_redirects})")
return ""
conn.logger.error("未能获取系统令牌")
return ""
except Exception as e:
conn.logger.error(f"获取系统令牌异常: {str(e)}")
return ""
async def get_aac_header(
conn: AUFEConnection = Depends(get_aufe_conn),
db: AsyncSession = Depends(get_db_session),
) -> Headers:
"""
获取AAC Ticket的依赖项。
如果用户没有登录AUFE或UAAP或者AAC Ticket不存在且无法获取新的Ticket则会抛出HTTP异常。
否则返回有效的AAC Ticket字符串。
"""
# 检查AAC Ticket是否存在
async with db as session:
result = await session.execute(
select(AACTicket).where(AACTicket.userid == conn.userid)
)
aac_ticket = result.scalars().first()
if not aac_ticket:
aac_ticket = await _get_or_fetch_ticket(conn, db, is_new=True)
else:
aac_ticket_token = aac_ticket.aac_token
try:
# 解密以验证Ticket有效性
decrypted_ticket = rsa.decrypt(aac_ticket_token)
if not decrypted_ticket:
raise ValueError("解密后的Ticket为空")
aac_ticket = decrypted_ticket
except Exception as e:
conn.logger.error(
f"用户 {conn.userid} 的 AAC Ticket 无效,正在获取新的 Ticket: {str(e)}"
)
aac_ticket = await _get_or_fetch_ticket(conn, db, is_new=False)
else:
conn.logger.info(f"用户 {conn.userid} 使用现有的 AAC Ticket")
return Headers(
{
**config_manager.get_settings().aufe.default_headers,
"ticket": aac_ticket,
"sdp-app-session": conn.twf_id,
}
)
async def _get_or_fetch_ticket(
conn: AUFEConnection, db: AsyncSession, is_new: bool
) -> str:
"""获取或重新获取AAC Ticket并保存到数据库返回解密后的ticket"""
action_type = "获取" if is_new else "重新获取"
conn.logger.info(
f"用户 {conn.userid} 的 AAC Ticket {'不存在' if is_new else '无效'},正在{action_type}新的 Ticket"
)
encrypted_token = await get_system_token(conn)
if not encrypted_token:
conn.logger.error(f"用户 {conn.userid} {action_type} AAC Ticket 失败")
raise ProtectRouterErrorToCode().remote_service_error.to_http_exception(
conn.logger.trace_id,
message="获取 AAC Ticket 失败,请检查 AUFE/UAAP 登录状态",
)
# 解密token
try:
decrypted_token = rsa.decrypt(encrypted_token)
if not decrypted_token:
raise ValueError("解密后的Ticket为空")
except Exception as e:
conn.logger.error(f"用户 {conn.userid} 解密 AAC Ticket 失败: {str(e)}")
raise ProtectRouterErrorToCode().remote_service_error.to_http_exception(
conn.logger.trace_id,
message="解密 AAC Ticket 失败",
)
# 保存加密后的token到数据库
async with db as session:
if is_new:
session.add(AACTicket(userid=conn.userid, aac_token=encrypted_token))
else:
result = await session.execute(
select(AACTicket).where(AACTicket.userid == conn.userid)
)
existing_ticket = result.scalars().first()
if existing_ticket:
existing_ticket.aac_token = encrypted_token
await session.commit()
conn.logger.success(f"用户 {conn.userid} 成功{action_type}并保存新的 AAC Ticket")
# 返回解密后的token
return decrypted_token