Files
live-hub-py/web/backend/deps.py
T
yml2213 6fc453017c feat(auth): 后台登录改为滑动会话
- JWT 记录 iat,exp=min(now+24h, iat+7d)
- 已认证请求自动续签 httpOnly Cookie
- 新增 ACCESS_TOKEN_IDLE_HOURS / ACCESS_TOKEN_MAX_HOURS
2026-10-25 14:07:44 +08:00

92 lines
3.1 KiB
Python

"""FastAPI 依赖注入"""
from fastapi import Depends, HTTPException, Request, WebSocket, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError
from sqlalchemy.orm import Session
from .database import SessionLocal, get_db
from .models import User
from .permissions import get_user_permissions
from .security import decode_access_token, is_session_expired
from .session import SESSION_COOKIE_NAME
# auto_error=False: 允许 token 为空(后续从 cookie 读取)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login", auto_error=False)
def get_current_user(
request: Request,
token: str | None = Depends(oauth2_scheme),
db: Session = Depends(get_db),
) -> User:
credentials_exc = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的认证凭据",
headers={"WWW-Authenticate": "Bearer"},
)
# 优先从 Authorization Bearer header 读取,回退到 httpOnly cookie
from_cookie = False
if not token:
token = request.cookies.get(SESSION_COOKIE_NAME)
from_cookie = True
if not token:
raise credentials_exc
try:
payload = decode_access_token(token)
if payload is None or is_session_expired(payload):
raise credentials_exc
user_id_raw = payload.get("sub")
if user_id_raw is None:
raise credentials_exc
user_id = int(user_id_raw)
except JWTError:
raise credentials_exc
user = db.query(User).filter(User.id == int(user_id)).first()
if user is None or not user.is_active:
raise credentials_exc
# 中间件据此只在「已通过认证的请求」上续签 Cookie;Bearer 客户端不写 Cookie。
request.state.current_user = user
if from_cookie:
request.state.auth_cookie_token = token
return user
def require_permission(permission: str):
"""权限检查依赖工厂。用法: Depends(require_permission('user:create'))"""
def checker(current_user: User = Depends(get_current_user)) -> User:
if not current_user.is_active:
raise HTTPException(status_code=403, detail="账号已禁用")
perms = get_user_permissions(current_user)
if permission not in perms:
raise HTTPException(status_code=403, detail=f"无权限: {permission}")
return current_user
return checker
def authenticate_websocket(websocket: WebSocket) -> User | None:
"""WebSocket 认证:从 cookie 或 query param token 中验证用户身份。
Returns:
User 如果认证成功,None 如果失败。
"""
token = websocket.cookies.get(SESSION_COOKIE_NAME) or websocket.query_params.get(
"token"
)
if not token:
return None
payload = decode_access_token(token)
if not payload or is_session_expired(payload) or not payload.get("sub"):
return None
db = SessionLocal()
try:
user = db.query(User).filter(User.id == int(payload["sub"])).first()
if user and user.is_active:
return user
return None
finally:
db.close()