74 lines
2.4 KiB
Python
74 lines
2.4 KiB
Python
# core/deps.py
|
|
# 认证与权限依赖:从请求头解析 JWT -> 加载用户 -> 角色级 RBAC 校验
|
|
|
|
from fastapi import Depends, HTTPException, status
|
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
import jwt
|
|
from sqlalchemy.orm import Session
|
|
|
|
from core.security import decode_access_token
|
|
from database import get_db
|
|
from model.user import User
|
|
|
|
# 自动从 Authorization: Bearer <token> 中提取 token
|
|
_bearer_scheme = HTTPBearer(auto_error=False)
|
|
|
|
|
|
def get_current_user(
|
|
credentials: HTTPAuthorizationCredentials = Depends(_bearer_scheme),
|
|
db: Session = Depends(get_db),
|
|
) -> User:
|
|
"""
|
|
解析 JWT 并返回当前登录用户(model.user.User 对象)
|
|
未登录 / token 无效 / 用户不存在 均抛 401
|
|
"""
|
|
if credentials is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="未登录,请先获取 token",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
try:
|
|
payload = decode_access_token(credentials.credentials)
|
|
except jwt.ExpiredSignatureError:
|
|
raise HTTPException(status_code=401, detail="token 已过期,请重新登录")
|
|
except jwt.InvalidTokenError:
|
|
raise HTTPException(status_code=401, detail="token 无效")
|
|
|
|
user_id = int(payload["sub"])
|
|
user = (
|
|
db.query(User)
|
|
.filter(User.user_id == user_id, User.is_deleted == 0)
|
|
.first()
|
|
)
|
|
if user is None:
|
|
raise HTTPException(status_code=401, detail="用户不存在或已被禁用")
|
|
return user
|
|
|
|
|
|
def require_roles(*roles: str):
|
|
"""
|
|
角色级 RBAC 依赖工厂。
|
|
用法:user: User = Depends(require_roles("admin", "teacher"))
|
|
"""
|
|
def checker(user: User = Depends(get_current_user)) -> User:
|
|
if user.role not in roles:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail=f"权限不足,需要角色:{' / '.join(roles)},当前角色:{user.role}",
|
|
)
|
|
return user
|
|
return checker
|
|
|
|
|
|
def get_teacher_class_ids(db: Session, teacher_id: int) -> list[int]:
|
|
"""获取某教师所带班级 ID 列表(用于资源归属校验)"""
|
|
from model.teachers import Teacher
|
|
|
|
rows = (
|
|
db.query(Teacher.class_id)
|
|
.filter(Teacher.teacher_id == teacher_id, Teacher.is_deleted == 0)
|
|
.all()
|
|
)
|
|
return [r[0] for r in rows]
|