fastapi依賴與權限管理

概述

fastapi對權限的控制,目前來看有兩種,一種是全局權限控制,通過中間件。另一種是通過依賴實現精準權限控制。
個人感覺依賴控制權限有更好的使用環境。
這裏以權限控制爲例。

依賴的高級用法

官方介紹的依賴的高級用法是通過類的__call__實現傳遞參數的高級用法,舉個例子:

class PermissionChecker:
    """
    權限管理的類型,把閉包改爲類實現
    """

    def __init__(self, permissions: Optional[List[str]]):
        """
        傳遞需要驗證是否具有的權限的列表。
        :param permissions:
        """
        self.permissions = permissions

    async def __call__(self, user: User = Depends(user_has_login)) -> User:
        """
        生成的依賴函數
        """
        if user.is_superuser:
            return user
        if not self.permissions:
            return user
        for need_permission in self.permissions:
            db = AdminDatabase().database#通過單例實現的全局數據庫調用類
            # 用戶->group,group和permission多對多,由auth_group_permission表記錄多對多關係字段
            query = select([auth_group_permission]).where(auth_group_permission.c.group_id == user.group_id).where(
                auth_group_permission.c.codename == need_permission)#這個主要是數據庫查詢是否有權限的過程,由於我使用的異步數據庫查詢,所以這裏沒有直接用sqlalchemy的默認查詢方式
            res = await db.fetch_all(query)
            if not res:
                raise HTTPException(status_code=403, detail="沒有權限")
        return user

講道理,通過類的實現更直觀,不過,當時沒看高級依賴寫法, 所以我在寫代碼過程中使用的是閉包的實現方法,代碼如下:

def func_user_has_permissions(need_permissions: List[str] = None) -> Callable:
    """
    生成權限認證的依賴
    """

    async def user_has_permission(user: User = Depends(user_has_login)) -> User:
        """
        是否有某權限
        """
        if user.is_superuser:
            return user
        if not need_permissions:
            return user
        for need_permission in need_permissions:
            db = AdminDatabase().database
            query = select([auth_group_permission]).where(auth_group_permission.c.group_id == user.group_id).where(
                auth_group_permission.c.codename == need_permission)
            res = await db.fetch_all(query)
            if not res:
                raise HTTPException(status_code=403, detail="沒有權限")
        return user

    return user_has_permission

實現的功能與上方是一致的。兩者並沒有優劣之分,不過感覺下方實現更pythoner一點???

權限架構

權限最開始考慮的是user對group爲多對多,group對permission也是多對多,但是後來發現這樣操作會增加數據庫查詢的次數(1次變3次),且實在是有點複雜,遂改爲user對group爲多對1,這樣只需要查詢一次就ok了。爲此犧牲一點用戶體驗是值得的。(除非你要寫user-group-permission三聯表的多對多表,否則不建議我最開始的實現方式)

from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String, Boolean, Integer, ForeignKey, Table, DateTime, DECIMAL, UniqueConstraint, func, text
from datetime import datetime

Base = declarative_base()

class Group(Base):
    __tablename__ = 'fastapi_auth_group'
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(150), unique=True, index=True, comment="組名")
    def __str__(self):
        return self.name
class User(Base):
    __tablename__ = 'fastapi_auth_user'
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String(150), unique=True, index=True, comment="用戶名",nullable=False)
    password = Column(String(128), comment="密碼",nullable=False)
    nick_name = Column(String(64), comment="暱稱", nullable=False,unique=True)
    qq = Column(String(16), comment="qq")
    email = Column(String(64), nullable=True, unique=True,comment="郵箱")
    phone_number = Column(String(18), nullable=True, unique=True, comment="手機號")
    register_ip = Column(String(32), comment="註冊ip")
    register_time = Column(DateTime, comment="註冊時間", server_default=func.now())
    status = Column(Integer, comment="狀態", server_default='0')
    is_superuser = Column(Boolean, server_default=text('false'), comment="是否爲超級管理員")
    is_active = Column(Boolean, server_default=text('true'), comment="是否刻可登錄")
    is_delete=Column(Boolean, server_default=text('false'), comment="是否刪除")
    group_id=Column(Integer, ForeignKey("fastapi_auth_group.id"))
    def __str__(self):
        return self.username

auth_group_permission = Table(  # 多對多的第三方表,居然還要自己生成。。
    'fastapi_auth_group_permission',
    Base.metadata,
    Column("id",Integer,primary_key=True,index=True),
    Column("group_id", Integer, ForeignKey("fastapi_auth_group.id")),
    Column("codename", String(100), ForeignKey("fastapi_auth_permission.codename")),
    UniqueConstraint('group_id', 'codename', name='idx_group_id_permission_id'),
)


class Permission(Base):
    __tablename__ = 'fastapi_auth_permission'
    name = Column(String(128), unique=True, index=True, comment="權限名稱")  # 權限名稱
    codename = Column(String(100), comment="權限字段",primary_key=True)  # 權限字段,也是我們平判斷權限輸入的字段
    groups = relationship("Group", backref="permissions", secondary=auth_group_permission)

    def __str__(self):
        return self.name

示例

所有的權限字段需要自己手動創建,之後會考慮學習django從表自動生成一些增刪改查的權限
上面已經有了表,現在講一下如何在實際操作的時候實現精準的權限控制:
以用戶修改密碼爲例:

# func_user_has_permissions爲一個生成depends函數的閉包,通過對應的權限列表生成權限管理
async def modify_password(new_password: ModifyPassword,
                          user: User = Depends(func_user_has_permissions(['user_modify_password']))):
    """
    用戶修改密碼
    :param current_user:
    :return:
    """
    #這裏其實需要先判斷舊密碼是否正確,各位可以自行增加
    hash_password = get_password_hash(new_password.new_password)#hash化密碼,
    #坑爹的sqlalchemy生成的數據庫操作指令
    query = User.__table__.update().values({"password": hash_password}).where(User.id == user.id)
    res = await AdminDatabase().database.execute(query)
    return {"code": 200, "message": "success"}

結尾

有任何問題可以到qq羣溝通:1067030489

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章