diff --git a/src/webui/anti_crawler.py b/src/webui/anti_crawler.py index 997d854b..7daf27f3 100644 --- a/src/webui/anti_crawler.py +++ b/src/webui/anti_crawler.py @@ -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: diff --git a/src/webui/auth.py b/src/webui/auth.py index deecbf1e..cfedc13b 100644 --- a/src/webui/auth.py +++ b/src/webui/auth.py @@ -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 在所有路径下可用 ) diff --git a/src/webui/routes.py b/src/webui/routes.py index 7f9909c1..8f85490c 100644 --- a/src/webui/routes.py +++ b/src/webui/routes.py @@ -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) diff --git a/template/template.env b/template/template.env index 1d46dcf5..932f9c82 100644 --- a/template/template.env +++ b/template/template.env @@ -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头 \ No newline at end of file + # 启用后,仍要求直连IP在TRUSTED_PROXIES中才会信任XFF头 +WEBUI_SECURE_COOKIE=false # 是否启用安全Cookie(仅通过HTTPS传输,默认false) \ No newline at end of file