Files
H3ConuMS-v2/backend/app/middleware/permission_middleware.py
T

105 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""权限检查中间件(数据库驱动 + Redis 缓存)"""
import json
import logging
from fastapi import HTTPException, Depends, Header
from sqlalchemy.orm import Session
from sqlalchemy import text
logger = logging.getLogger(__name__)
import redis
from app.core.database import get_db
from app.core.security import verify_token
from app.core.config import settings
_redis_client = None
def get_redis() -> redis.Redis:
global _redis_client
if _redis_client is None:
_redis_client = redis.from_url(settings.REDIS_URL, decode_responses=True)
return _redis_client
def get_role_permissions(role: str, db: Session) -> list:
"""从数据库加载角色权限,结果缓存到 RedisTTL 5分钟)"""
if role == 'admin':
return ['*']
r = get_redis()
cache_key = f"permissions:role:{role}"
cached = r.get(cache_key)
if cached:
return json.loads(cached)
rows = db.execute(
text("""
SELECT p.code FROM permissions p
JOIN role_permissions rp ON rp.permission_id = p.id
WHERE rp.role = :role
"""),
{"role": role}
).fetchall()
perms = [row[0] for row in rows]
r.setex(cache_key, 300, json.dumps(perms))
return perms
def invalidate_role_cache(role: str) -> None:
"""修改角色权限后清除缓存"""
get_redis().delete(f"permissions:role:{role}")
def require_permission(permission: str):
"""FastAPI Depends 工厂,检查 Bearer token 中的角色是否拥有指定权限"""
def dependency(
authorization: str = Header(None, alias="Authorization"),
db: Session = Depends(get_db),
) -> dict:
if not authorization:
logger.warning("auth rejected: 缺少 Authorization 头 (permission=%s)", permission)
raise HTTPException(status_code=401, detail="未授权")
if not authorization.startswith("Bearer "):
logger.warning("auth rejected: Authorization 格式错误 (permission=%s): %.50s", permission, authorization)
raise HTTPException(status_code=401, detail="未授权")
token = authorization[7:]
payload = verify_token(token)
if not payload:
logger.warning("auth rejected: token 验证失败 (permission=%s)", permission)
raise HTTPException(status_code=401, detail="令牌无效或已过期")
role = payload.get('role', 'user')
perms = get_role_permissions(role, db)
if '*' not in perms and permission not in perms:
raise HTTPException(status_code=403, detail="权限不足")
# 附加用户的区域/学校分配信息,供数据范围过滤使用
user_id = payload.get('sub')
if user_id and role in ('area_admin', 'school_admin'):
from app.models.user import User
user = db.query(User).filter(User.id == int(user_id)).first()
if user:
payload['assigned_area'] = user.assigned_area
payload['assigned_school'] = user.assigned_school
return payload
return dependency
def get_current_user(
authorization: str = Header(None, alias="Authorization"),
db: Session = Depends(get_db),
) -> dict:
"""仅验证登录状态,不检查具体权限"""
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="未授权")
token = authorization[7:]
payload = verify_token(token)
if not payload:
raise HTTPException(status_code=401, detail="令牌无效或已过期")
return payload