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