数据工厂系列(7)引入Depends实现token验证和权限校验

286 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

大家好~我是小方,欢迎大家关注笋货测试笔记体完记得俾个like

上篇回顾

上篇我们已经完成了登录功能的开发,有些小伙伴会问,token里面记载的是什么样的数据呢?有了token,要怎么进行token校验和权限校验呢?

伪代码

我们先来看一下token里面存放的是什么数据?生成token之前先来打印一下user_data数据

登录接口请求一下,没错,其实就是UserDto模型

再来看看我们的接口,其实本质上就是一个函数套了个装饰器,从而实现路由与函数的绑定

假如一些接口需要权限校验或者token验证,那么肯定是先进行权限校验或者token验证,如果通过了再执行函数实际的逻辑,权限校验或者token验证算是函数运行前的一个前置动作,那么我们是不是可以引入装饰器,从而实现函数运行前进行校验和验证?

我们先来一段伪代码

#用户登录态校验装饰器
def auth(role):
    @wraps(role)
    def wrapper(func):
        def inner(*args, **kwargs):
            try:
                token = args[0]
                user_data = UserToken.parse_token(token)
                print("token校验好了,可以访问")
                if user_data['role'] < role:
                    raise Exception('权限不足,不能访问')
                print("权限验证通过,可以访问")
                return func(*args, **kwargs)
            except Exception as e:
                raise Exception(str(e))
        return inner
    return wrapper

接着编写一个伪函数,套上这个装饰器

@auth(Permission.ADMIN)
def user_list(token):
    return "这是列表接口数据"

再把之前登录接口生成的token,传入user_list函数中执行,我们可以装饰器先进行了token验证,再进行权限校验,最后返回列表接口数据。 那如果我输入一个fake的token,会怎么样? 直接抛出异常 那如果权限不足呢? token验证通过了,解析得到了用户数据,但是用户的权限是2,用户列表接口的权限是3,比3小,确实无法访问用户列表接口,抛出异常 那在fastapi里是如何实现的呢?

引入Depends

我们翻一下fastapi的官方文档,有一节讲到如何使用Depends,翻译过来就是依赖注入,这里不就是我们想要的功能么!!! 我们先来看看官网的demo

接收到新的请求时,FastAPI 执行如下操作:

  • commons: dict = Depends(common_parameters)声明了一个依赖关系,表示接口参数请求依赖于common_parameters函数,当接口被调用时,回调给common_parameters函数进行处理
  • common_parameters函数处理后返回一个dict
  • 接着将这个dict赋值给commons,这就是一个依赖注入的过程

这里只能传给Depends一个参数且该参数必须是可调用对象,比如函数

功能拆解

按照官网的demo,其实我们可以从请求中获取token(请求头),解析token,判断token是否过期了,是否解析失败了,如果解析成功,从用户数据获取用户的权限,判断用户权限是否满足操作设置的权限,最后返回用户信息~

先在exception_utils.py加上这两个异常类

# 用户登录态
class AuthException(HTTPException):
    def __init__(self, detail: Any = None) -> None:
        super().__init__(status_code=200, detail=detail)

# 用户权限
class PermissionException(HTTPException):
    def __init__(self, detail: Any = None) -> None:
        super().__init__(status_code=200, detail=detail)

auth_utils.py加上这段代码

def auth(role: int = Permission.MEMBERS, token: str = Header(..., description="登录的token")):
    if not token:
        raise AuthException("token不能为空")
    try:
        user_info = UserToken.parse_token(token)
    except Exception as e:
        raise AuthException(str(e))
    if user_info.get('role', 0) < role:
        raise PermissionException('权限不足, 请联系管理员')
    return user_info

user/user.py编写一个接口测试一下

可以看到role变成了一个路径参数,这可不是我们想要的结果

上面有说到过Depends接收的是一个函数(即是否可调用对象)可调用对象其实就是含有__call__这个方法,Python中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。那我们这里直接编写一个类,然后内部实现__call__,接着role字段通过__init__方法进行传值不就好了么?这样就可以避免role变成了一个路径参数

最终改造如下:

class Auth(object):

    def __init__(self, role: int = Permission.MEMBERS):
        self.role = role

    def __call__(self, token: str = Header(..., description="登录的token")):
        if not token:
            raise AuthException("token不能为空")
        try:
            user_info = UserToken.parse_token(token)
        except Exception as e:
            raise AuthException(str(e))
        if user_info.get('role', 0) < self.role:
            raise PermissionException('权限不足, 请联系管理员')
        return user_info

用户列表接口也改一下

Auth类初始化时,将role赋值给self.role,先判断请求头中的token是否为空,再进行解析token,解析失败了,抛出AuthException异常,如果用户权限小于设置的权限,抛出PermissionException异常

这里的自定义异常我们也要处理一下,返回专属的code

app/__init__.py加入以下代码

# 自定义权限异常
@fun.exception_handler(PermissionException)
async def unexpected_exception_error(request: Request, exc: PermissionException):
    res = ResponseDto(code=403, msg=HTTP_MSG_MAP.get(exc.status_code, exc.detail))
    return JSONResponse(content=res.dict())

# 自定义用户登录态异常
@fun.exception_handler(AuthException)
async def unexpected_exception_error(request: Request, exc: AuthException):
    res = ResponseDto(code=401, msg=HTTP_MSG_MAP.get(exc.status_code, exc.detail))
    return JSONResponse(content=res.dict())

最后测试一下吧~

token解析失败

权限不足

总结

今天主要介绍了使用Depends实现token的验证和进行权限校验,知识可能有点难懂,特别是装饰器那里,套娃又套娃... 好了,今天先到这里了,我们下期见哈~