feat: 增强认证 Cookie 设置,支持协议检测和跳过注释行

pull/1451/head
墨梓柒 2025-12-16 13:04:37 +08:00
parent 4482be7142
commit a5880522e7
No known key found for this signature in database
GPG Key ID: 4A65B9DBA35F7635
4 changed files with 37 additions and 9 deletions

View File

@ -154,6 +154,10 @@ def _parse_allowed_ips(ip_string: str) -> list:
ip_entry = ip_entry.strip() # 去除空格
if not ip_entry:
continue
# 跳过注释行(以#开头)
if ip_entry.startswith("#"):
continue
# 检查通配符格式(包含*
if "*" in ip_entry:

View File

@ -24,17 +24,22 @@ def _is_secure_environment() -> bool:
bool: 如果应该使用 secure cookie 则返回 True
"""
# 检查环境变量
if os.environ.get("WEBUI_SECURE_COOKIE", "").lower() in ("true", "1", "yes"):
secure_cookie_env = os.environ.get("WEBUI_SECURE_COOKIE", "")
if secure_cookie_env.lower() in ("true", "1", "yes"):
logger.info(f"WEBUI_SECURE_COOKIE 设置为 {secure_cookie_env},启用 secure cookie")
return True
if os.environ.get("WEBUI_SECURE_COOKIE", "").lower() in ("false", "0", "no"):
if secure_cookie_env.lower() in ("false", "0", "no"):
logger.info(f"WEBUI_SECURE_COOKIE 设置为 {secure_cookie_env},禁用 secure cookie")
return False
# 检查是否是生产环境
env = os.environ.get("WEBUI_MODE", "").lower()
if env in ("production", "prod"):
logger.info(f"WEBUI_MODE 设置为 {env},启用 secure cookie")
return True
# 默认:开发环境不启用(因为通常是 HTTP
logger.debug(f"未设置特殊环境变量 (WEBUI_SECURE_COOKIE={secure_cookie_env}, WEBUI_MODE={env}),禁用 secure cookie")
return False
@ -77,17 +82,35 @@ def get_current_token(
return token
def set_auth_cookie(response: Response, token: str) -> None:
def set_auth_cookie(response: Response, token: str, request: Optional[Request] = None) -> None:
"""
设置认证 Cookie
Args:
response: FastAPI Response 对象
token: 要设置的 token
request: FastAPI Request 对象可选用于检测协议
"""
# 根据环境决定安全设置
# 根据环境和实际请求协议决定安全设置
is_secure = _is_secure_environment()
# 如果提供了 request检测实际使用的协议
if request:
# 检查 X-Forwarded-Proto header代理/负载均衡器)
forwarded_proto = request.headers.get("x-forwarded-proto", "").lower()
if forwarded_proto:
is_https = forwarded_proto == "https"
logger.debug(f"检测到 X-Forwarded-Proto: {forwarded_proto}, is_https={is_https}")
else:
# 检查 request.url.scheme
is_https = request.url.scheme == "https"
logger.debug(f"检测到 scheme: {request.url.scheme}, is_https={is_https}")
# 如果是 HTTP 连接,强制禁用 secure 标志
if not is_https and is_secure:
logger.warning("检测到 HTTP 连接但配置要求 secure cookie强制禁用 secure 以允许 cookie 工作")
is_secure = False
# 设置 Cookie
response.set_cookie(
key=COOKIE_NAME,
@ -95,7 +118,7 @@ def set_auth_cookie(response: Response, token: str) -> None:
max_age=COOKIE_MAX_AGE,
httponly=True, # 防止 JS 读取,阻止 XSS 窃取
samesite="lax", # 使用 lax 以兼容更多场景(开发和生产)
secure=is_secure, # 生产环境强制 HTTPS
secure=is_secure, # 根据实际协议决定
path="/", # 确保 Cookie 在所有路径下可用
)

View File

@ -137,8 +137,8 @@ async def verify_token(
if is_valid:
# 认证成功,重置失败计数
rate_limiter.reset_failures(request)
# 设置 HttpOnly Cookie
set_auth_cookie(response, request_body.token)
# 设置 HttpOnly Cookie(传入 request 以检测协议)
set_auth_cookie(response, request_body.token, request)
# 同时返回首次配置状态,避免额外请求
is_first_setup = token_manager.is_first_setup()
return TokenVerifyResponse(valid=True, message="Token 验证成功", is_first_setup=is_first_setup)

View File

@ -15,4 +15,5 @@ WEBUI_ALLOWED_IPS=127.0.0.1 # IP白名单逗号分隔支持精确IP、
WEBUI_TRUSTED_PROXIES= # 信任的代理IP列表逗号分隔只有来自这些IP的X-Forwarded-For才被信任
# 示例: 127.0.0.1,192.168.1.1,172.17.0.1
WEBUI_TRUST_XFF=false # 是否启用X-Forwarded-For代理解析默认false
# 启用后仍要求直连IP在TRUSTED_PROXIES中才会信任XFF头
# 启用后仍要求直连IP在TRUSTED_PROXIES中才会信任XFF头
WEBUI_SECURE_COOKIE=false # 是否启用安全Cookie仅通过HTTPS传输默认false