feat(auth): 支持多渠道登录态管理并新增webview重定向功能
- 修改get_current_user函数同时支持Authorization header和cookie获取token - 在logout接口中添加JSONResponse并清除webview cookie登录态 - 新增webview模块实现URL token到cookie的转换和安全重定向功能 - 实现域名白名单校验防止开放重定向漏洞 - 在main.py中注册webview路由
This commit is contained in:
80
routes/webview.py
Normal file
80
routes/webview.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Webview 登录态传递:URL token → cookie → 302 重定向。
|
||||
|
||||
流程:
|
||||
1. 小程序端构造 webview URL:/api/v1/webview/redirect?token=xxx&target=xxx
|
||||
2. web-view 打开该地址,后端验证 token 有效 → 设置 HttpOnly cookie → 302 重定向到 target
|
||||
3. H5 页面内所有同域请求自动携带 cookie,get_current_user 同时支持 header 和 cookie
|
||||
|
||||
H5 端零改动,权限校验由 H5 自己的接口完成。
|
||||
"""
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
|
||||
from .. import config, db
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/v1/webview", tags=["webview"])
|
||||
|
||||
# 允许跳转的目标域名白名单(防止开放重定向漏洞)
|
||||
ALLOWED_REDIRECT_HOSTS = {"m.muguaclub.cn", "muguaclub.cn"}
|
||||
|
||||
|
||||
def _is_safe_target(target: str) -> bool:
|
||||
"""校验 target 是否在允许的域名范围内,防止开放重定向漏洞。"""
|
||||
if not target:
|
||||
return False
|
||||
# 相对路径(/xxx 开头)一律允许,同站跳转
|
||||
if target.startswith("/") and not target.startswith("//"):
|
||||
return True
|
||||
parsed = urlparse(target)
|
||||
if not parsed.scheme or not parsed.netloc:
|
||||
return False
|
||||
if parsed.scheme not in ("http", "https"):
|
||||
return False
|
||||
host = parsed.netloc.lower().split(":")[0]
|
||||
return host in ALLOWED_REDIRECT_HOSTS or any(
|
||||
host.endswith("." + h) for h in ALLOWED_REDIRECT_HOSTS
|
||||
)
|
||||
|
||||
|
||||
@router.get("/redirect")
|
||||
def redirect_with_cookie(
|
||||
request: Request,
|
||||
token: str = Query(..., description="会话 token"),
|
||||
target: str = Query(..., description="目标跳转地址"),
|
||||
):
|
||||
"""验证 token,设置登录态 cookie,然后 302 重定向到 target。
|
||||
|
||||
这个接口由 web-view 直接打开,不需要 Authorization header,
|
||||
token 通过 URL query 传入,换 cookie 后重定向,URL 中不再暴露 token。
|
||||
"""
|
||||
if not token or len(token) > 128:
|
||||
raise HTTPException(status_code=400, detail="无效的 token")
|
||||
if not _is_safe_target(target):
|
||||
raise HTTPException(status_code=400, detail="无效的跳转地址")
|
||||
|
||||
# 验证 token 有效
|
||||
session = db.fetch_one(
|
||||
"SELECT user_id FROM auth_sessions WHERE token=%s AND expires_at>NOW()",
|
||||
(token,),
|
||||
)
|
||||
if not session:
|
||||
raise HTTPException(status_code=401, detail="token 无效或过期")
|
||||
|
||||
# 设置 HttpOnly cookie + 302 重定向
|
||||
response = RedirectResponse(url=target, status_code=302)
|
||||
response.set_cookie(
|
||||
key="auth_token",
|
||||
value=token,
|
||||
max_age=int(config.JWT_TTL_SECONDS),
|
||||
httponly=True,
|
||||
secure=request.url.scheme == "https",
|
||||
samesite="lax",
|
||||
path="/",
|
||||
)
|
||||
return response
|
||||
Reference in New Issue
Block a user