持续创作,加速成长!这是我参与「掘金日新计划 · 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的验证和进行权限校验,知识可能有点难懂,特别是装饰器那里,套娃又套娃...
好了,今天先到这里了,我们下期见哈~
- 项目地址