概述
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