feat: 增强认证 Cookie 设置,支持协议检测和跳过注释行
This commit is contained in:
@@ -154,6 +154,10 @@ def _parse_allowed_ips(ip_string: str) -> list:
|
|||||||
ip_entry = ip_entry.strip() # 去除空格
|
ip_entry = ip_entry.strip() # 去除空格
|
||||||
if not ip_entry:
|
if not ip_entry:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# 跳过注释行(以#开头)
|
||||||
|
if ip_entry.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
# 检查通配符格式(包含*)
|
# 检查通配符格式(包含*)
|
||||||
if "*" in ip_entry:
|
if "*" in ip_entry:
|
||||||
|
|||||||
@@ -24,17 +24,22 @@ def _is_secure_environment() -> bool:
|
|||||||
bool: 如果应该使用 secure cookie 则返回 True
|
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
|
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
|
return False
|
||||||
|
|
||||||
# 检查是否是生产环境
|
# 检查是否是生产环境
|
||||||
env = os.environ.get("WEBUI_MODE", "").lower()
|
env = os.environ.get("WEBUI_MODE", "").lower()
|
||||||
if env in ("production", "prod"):
|
if env in ("production", "prod"):
|
||||||
|
logger.info(f"WEBUI_MODE 设置为 {env},启用 secure cookie")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# 默认:开发环境不启用(因为通常是 HTTP)
|
# 默认:开发环境不启用(因为通常是 HTTP)
|
||||||
|
logger.debug(f"未设置特殊环境变量 (WEBUI_SECURE_COOKIE={secure_cookie_env}, WEBUI_MODE={env}),禁用 secure cookie")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -77,17 +82,35 @@ def get_current_token(
|
|||||||
return 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
|
设置认证 Cookie
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
response: FastAPI Response 对象
|
response: FastAPI Response 对象
|
||||||
token: 要设置的 token
|
token: 要设置的 token
|
||||||
|
request: FastAPI Request 对象(可选,用于检测协议)
|
||||||
"""
|
"""
|
||||||
# 根据环境决定安全设置
|
# 根据环境和实际请求协议决定安全设置
|
||||||
is_secure = _is_secure_environment()
|
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
|
# 设置 Cookie
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
key=COOKIE_NAME,
|
key=COOKIE_NAME,
|
||||||
@@ -95,7 +118,7 @@ def set_auth_cookie(response: Response, token: str) -> None:
|
|||||||
max_age=COOKIE_MAX_AGE,
|
max_age=COOKIE_MAX_AGE,
|
||||||
httponly=True, # 防止 JS 读取,阻止 XSS 窃取
|
httponly=True, # 防止 JS 读取,阻止 XSS 窃取
|
||||||
samesite="lax", # 使用 lax 以兼容更多场景(开发和生产)
|
samesite="lax", # 使用 lax 以兼容更多场景(开发和生产)
|
||||||
secure=is_secure, # 生产环境强制 HTTPS
|
secure=is_secure, # 根据实际协议决定
|
||||||
path="/", # 确保 Cookie 在所有路径下可用
|
path="/", # 确保 Cookie 在所有路径下可用
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -137,8 +137,8 @@ async def verify_token(
|
|||||||
if is_valid:
|
if is_valid:
|
||||||
# 认证成功,重置失败计数
|
# 认证成功,重置失败计数
|
||||||
rate_limiter.reset_failures(request)
|
rate_limiter.reset_failures(request)
|
||||||
# 设置 HttpOnly Cookie
|
# 设置 HttpOnly Cookie(传入 request 以检测协议)
|
||||||
set_auth_cookie(response, request_body.token)
|
set_auth_cookie(response, request_body.token, request)
|
||||||
# 同时返回首次配置状态,避免额外请求
|
# 同时返回首次配置状态,避免额外请求
|
||||||
is_first_setup = token_manager.is_first_setup()
|
is_first_setup = token_manager.is_first_setup()
|
||||||
return TokenVerifyResponse(valid=True, message="Token 验证成功", is_first_setup=is_first_setup)
|
return TokenVerifyResponse(valid=True, message="Token 验证成功", is_first_setup=is_first_setup)
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ WEBUI_ALLOWED_IPS=127.0.0.1 # IP白名单(逗号分隔,支持精确IP、
|
|||||||
WEBUI_TRUSTED_PROXIES= # 信任的代理IP列表(逗号分隔),只有来自这些IP的X-Forwarded-For才被信任
|
WEBUI_TRUSTED_PROXIES= # 信任的代理IP列表(逗号分隔),只有来自这些IP的X-Forwarded-For才被信任
|
||||||
# 示例: 127.0.0.1,192.168.1.1,172.17.0.1
|
# 示例: 127.0.0.1,192.168.1.1,172.17.0.1
|
||||||
WEBUI_TRUST_XFF=false # 是否启用X-Forwarded-For代理解析(默认false)
|
WEBUI_TRUST_XFF=false # 是否启用X-Forwarded-For代理解析(默认false)
|
||||||
# 启用后,仍要求直连IP在TRUSTED_PROXIES中才会信任XFF头
|
# 启用后,仍要求直连IP在TRUSTED_PROXIES中才会信任XFF头
|
||||||
|
WEBUI_SECURE_COOKIE=false # 是否启用安全Cookie(仅通过HTTPS传输,默认false)
|
||||||
Reference in New Issue
Block a user