Fastapi框架-冷饭再炒-基础知识补充篇(3)-安全机制总汇

2,125 阅读14分钟

在上一章节(2)中的其实有补充一些基础的官网的关于安全的机制,但是到后续的奖金JWT的认证的时候,感觉估计新手肯定会蒙圈,看的云里雾里的。包括我的第一次的看的时候,都不知道是说什么,只是硬头皮的去啃文档!哈哈

不过自己撸一次后,也感觉无非那玩意!没啥!

这里为了避免分散专题的,我这里索性也再把2的那些再补充到这里吧!比较再去修改的2,貌似又需要审核!!哈哈 怕被审核的大佬给打④~~~

PS:部分和(2)的内容的有重复,原谅我!希望你能原谅我!

1 Fastapi 安全授权(补充关于oauth2关于基础)

可能相对的新手来说的,对oauth2授权可能还是有点迷糊,迷糊不怕,怕的是你不去理解,不去实践和体验。当不理解的时候要学会搜索和思考,我觉得再难的问题也终究有的新的发现和新的思路。

建议这块各位可以直接的百度一下,别人估计描述的比较专业。我这里描述可能比较撇脚!

1.1 .什么是OAuth2?

.什么是OAuth2

专业的说话就是:

OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容。

1.2 .OAuth2应用场景?

随处可见的使用微信登入授权,使用QQ登入授权,使用支付宝账号登入授权。挺多的!

1.3 .OAuth2授权几个模式。

  • 授权码模式
  • 隐式授权模式
  • 密码模式(Fastapi官网的示例就是这个模式)
  • 客户端凭证模式

对于上面的几种模式的,如何区分呢?

  1. 授权码模式 首先授权码模式其实是我的常见,也是最常用的。现在的的使用微信授权登入到第三方应用,就是这种的授权模式。它是一直比较完整和严格的一种授权模式,需要通过Code和相关的app_secret才能换取我们的token.
  • 需通过code再获取token
  • 支持refresh token

关于这个模式最好的见证即使看我们的微信授权官网的文档.

微信授权:developers.weixin.qq.com/doc/oplatfo…

微信授权的时序图:

image.png

以下的文字抄录自微信的官方文档:

微信 OAuth2.0 授权登录目前支持 authorization_code 模式,适用于拥有 server 端的应用授权。该模式整体流程为:

1. 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;

2. 通过code参数加上AppID和AppSecret等,通过API换取access_token;

3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

有兴趣的还是看完微信的授权说明吧!感觉你看完之后,其实就能明白这种授权模式你也可以实现。

  1. 隐示授权

相比权码模式少了code环节,回调url直接携带token,这种模式的使用场景是基于浏览器的应用。这种模式的安全性不是很高,token容易因为被拦截。且不支持refresh token。

  1. 密码模式 说真的官网就是这种模式,而且用户名称而密码而且在我们的操作文档那上面还是明文的传递,有点坑爹啊!虽然这个模式支持refresh token,但是从安全性上来说肯定也不推荐滴! 只是官网的示例如此,想必是出于方便吧!

  2. 客户端凭证模式

这个模式可以直接根据client_id和密钥即可获取token,无需用户参与,它比较合适用于消费api的类型的后端服务。

PS:关于refresh token的主要是为了在我们的token已经失效或过期,且在正常的范围内的话,可以使用它来换取新的token.

2 fastapi OAuth2-密码授权模式

从官网的文档可以看到,他们的处理安全的是再讲完了依赖之后,就继续说明关于了安全的问题, 仔细分析官网示例的,他的实现也确实是依赖于我们的上节说的依赖注入的实现。

如果对于依赖注入不熟悉的,建议可以查阅前面的内容,当然也可以直达官网去翻看!

PS:因为我们的OAuth2的用户名和密码是在表单里提交的,所以需要安装支持表单的库的:

pip install python-multipart

2.2 通过OAuth2PasswordBearer获取token

import uvicorn
from fastapi import  FastAPI,Depends
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()

# 定义一个依赖注入的对象--这个依赖注入的对象返回的一个token
# tokenUrl="token" 是请求获取token的地址,格式是/token
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/read_token/")
async def read_token(token: str = Depends(oauth2_scheme)):
    return {"token": token}

if __name__ == "__main__":
    uvicorn.run('main:app', host="127.0.0.1", port=8000,reload=True)

执行文档接口的操作(提示没认证):

image.png

2.3 闪亮的“授权”按钮点击且输入

官方翻译过来是一个闪亮的按钮!!!!我觉得不够闪亮 哈哈!

image.png

按官网的说法,确实是我们输入什么都没用滴!!!

image.png

原因是:你的头部里面没有添加这个OAuth2需要的信息,按官网的说是需要再头部文件里加上:

Authorization:Bearer (需要空格哟)xxxxxx的请求头

2.4 改用POSTMAN提交请求头信息

没输入的情况:

image.png

有输入的情况:

image.png

注意:接口返回Token

从上面的接口可以看到,我们的Authorization:Bearer后面 就是我们的token的的值。

image.png

所以上面的演示的请求,其实是说明了,如果我们的接口没有按规范的协议在请求头传入认证的信息的话,则是不给通过滴!我们的OAuth2PasswordBearer会自己的验证用户的输入信息。

2.5 加工TOKEN的示例

import uvicorn
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()


#虚拟解码token
def fake_decode_token(token):
    # 经过加工处理的TOEKEN
    return "token 我是加工过的%s " + token

# 定义第1层的依赖--
# 定义一个依赖注入的对象--这个依赖注入的对象返回的一个token
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


# 定义第二层的依赖
# 再定义一个依赖用于处理我们的前端传入的Authorization:Bearer后面的值信息
async def get_current_user(token: str = Depends(oauth2_scheme)):
    user = fake_decode_token(token)
    return user


@app.get("/read_token/")
async def read_token(token: str = Depends(get_current_user)):
    return {"token": token}


if __name__ == "__main__":
    uvicorn.run('main:app', host="127.0.0.1", port=8000, reload=True)

上面的依赖流程:

  • get_current_user,依赖于oauth2_scheme

  • oauth2_scheme 主要是接收token

  • get_current_user,接收到依赖于oauth2_scheme的token后调用fake_decode_token进行处理

重新的访问接口:

image.png

官网的示例原本是定义个模型进行在fake_decode_token进行注入,我没按它的来!

class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None

#虚拟解码token
def fake_decode_token(token):
    return User(
        username=token + "fakedecoded", email="john@example.com", full_name="John Doe"
    )

但是其实就是一个原理!只是对用户的信息的注入再返回而已!

2.6 基于OAuth2完整的Token签发和验证流程:

翻了那么久,我竟然我没看到完全的登入的流程和签发token的流程!后知后觉才知道原来! 官网的示例比较奇葩!先讲怎么获取Token再讲什么签发的!所以下面的就是关于才是真正的基于登入的用户名和密码的Token的签发流程.

所以完整一个简要的示例如下:

2.6.1 示例代码:

import secrets

import uvicorn
from fastapi import FastAPI, Depends
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None


class UserInDB(User):
    hashed_password: str


def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def fake_decode_token(token):
    # This doesn't provide any security at all
    # Check the next version
    user = get_user(fake_users_db, token)
    return user


async def get_current_user(token: str = Depends(oauth2_scheme)):
    user = fake_decode_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="TOKEN 认证失败!TOKEN非法!",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return user


async def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="当前用户不可用!")
    return current_user


# 模拟当前数据库存在的用户数据
fake_users_db = {
    "xiaozhongtongxue": {
        "username": "xiaozhongtongxue",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "xiaozhongtongxue=xiaozhongtongxue",
        "disabled": False,
    },
    "xiaozhongtongxue2": {
        "username": "alice",
        "full_name": "Alice Wonderson",
        "email": "alice@example.com",
        "hashed_password": "xiaozhongtongxue",
        "disabled": True,
    },
}


# 处理密码的加密的函数
def fake_hash_password(password: str):
    return "xiaozhongtongxue=" + password


@app.post("/oauth/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user_dict = fake_users_db.get(form_data.username)
    print("字典内的用户信息", user_dict)
    if not user_dict:
        raise HTTPException(status_code=400, detail="不存在这个用户信息,再字典里找不到!")
    user = UserInDB(**user_dict)
    print("用户输出的密码:", form_data.password)
    print("用户字典里的密码输出的密码:", user.hashed_password)
    hashed_password = fake_hash_password(form_data.password)
    print("hashed_password之后的密码", hashed_password)
    if not hashed_password == user.hashed_password:
        raise HTTPException(status_code=400, detail="用户的密码和fake_hash_password生产的不匹配!")
    return {"access_token": user.username, "token_type": "bearer"}


# 验证用户Token的流程get_current_active_user
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    # 如果验证Token通过的话,则返回用户当前的信息
    return current_user


if __name__ == "__main__":
    uvicorn.run('main:app', host="127.0.0.1", port=8000, reload=True)

2.6.1 获取token需要注意以下识相点:

必须使用表单的形式提交: JSON格式提交无用: image.png

表单的形式提交: image.png 文档也只提供表单格式的提交 image.png

2.6.2 正常的签发token的请求:

image.png

2.6.3 对签发token的验证:

错误的TOKEN:

image.png

image.png

正确的TOKEN:

image.png

使用操作文档进行登入:

image.png

发现它默认的请的地址是:

/token HTTP/1.1" 404 Not Found

所以加一个多地址的支持,然后一样的输入用户名和密码之后

image.png

然后进行验证我们的需要携带Token的接口:

image.png

此时退出以下操作文档的认证看看:手动的放Token进行失效

image.png

然后再访问一下需要携带Token的接口:

image.png

整个流程初步的完成!

3 使用(哈希)密码和 JWT Bearer 令牌的 OAuth2

至于这里的说的JWT是啥,百度吧!啥都有!我这里就不重复那么多了!

这里补充说关于JWT一些特点吧

  • 体积小,因而传输速度快
  • payload 里面可以包含用户基本验证消息,但是不建议把密码也写入啊,因为是可以反查出来的!
  • 存在访问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload 支持为你的应用而定制化。
  • 支持跨域验证,通常我们的可以把它应用于单点登录的需求中。

感兴趣的可以看我公众号的关于JWT在flask上的使用说明:

感兴趣的可以看我公众号的关于JWT在flask上的使用说明,Flask + Ant Vue 实战 (10)-JWT鉴权中间件

3.1 使用前置环境

官网说:既然我们已经有了所有的安全流程,就让我们来使用 JWT 令牌和安全哈希密码让应用程序真正地安全吧!!!意思就是我们基于上面的基础上再加点料呗!

翻看官网的示例的,其实分析起来如果是顺着它的依赖的思路去的话,还是可以顺藤摸瓜,理出点头绪出来。不过我感觉官网的讲的是比较的细了。

按我的以往的使用的jwt的话,是直接的封装成了一个类的,一个是用于生成我们的JTW,一个是用于验证JWT。既然是官网推荐的示例,该理解的还是先去理解一下。

1 . 安装官网推荐生成JWT的库

pip install python-jose[cryptography]
pip install cryptography(如果上面一起装不了就单独安装)

Python-jose,因为它提供了 PyJWT 的所有功能,以及之后与其他工具进行集成时你可能需要的一些其他功能。

2 . 安装官网的密码加盐的依赖库

pip install passlib[bcrypt]
pip install bcrypt(如果上面一起装不了就单独安装)

官网关于哈希密码的说明:

「哈希」的意思是:将某些内容(在本例中为密码)转换为看起来像乱码的字节序列(只是一个字>符串)。

每次你传入完全相同的内容(完全相同的密码)时,你都会得到完全相同的乱码。

但是你不能从乱码转换回密码。

passlib它支持许多安全哈希算法以及配合算法使用的实用程序。 官网推荐的算法是 「Bcrypt」

3.2 JWT的官网示例摸瓜顺藤系列

官网的说的步骤是:

3.2.1 创建一个工具函数以哈希来自用户的密码

from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def get_password_hash(password):
    return pwd_context.hash(password)

3.2.2 创建另一个工具函数,用于校验接收的密码是否与存储的哈希值匹配

from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

关于哈希密码的理解示例:

from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def get_password_hash(password):
    return pwd_context.hash(password)

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

print("获取加盐后的密码1:",get_password_hash('xiaozhong'))
print("获取加盐后的密码2:",get_password_hash('xiaozhong'))
print("明文密码和哈希密码之后的验证---------》")
print("获取加盐后的密码1:",verify_password('xiaozhong',get_password_hash('xiaozhong')))
print("获取加盐后的密码2:",verify_password('xiaozhong',get_password_hash('xiaozhong')))

上面的示例的输出的结果:

获取加盐后的密码1: $2b$12$VuU.tAakvuYnQM/smhU0/efASK5E/yMakuD89aVdkUn0CVHMiMpd2
获取加盐后的密码2: $2b$12$saNNZ9Mp6qt/7JM2qyqWmeQ3vSTu.TB1/XPdNe3M/qwm3EcdcLvlK
明文密码和哈希密码之后的验证---------》
获取加盐后的密码1True
获取加盐后的密码2True

:PS 从上面上面看到我们的一个铭文可以对于多个不同的哈希的值!

3.2.3 创建一个工具函数用于认证并返回用户

def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user

上面的工具函数主要是用于当用户输入了用户名和密码之后对当前的用户进行密码的校验处理。

3.2.4 官网完整示例解说:

from datetime import datetime, timedelta
from typing import Optional

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt

from passlib.context import CryptContext

from pydantic import BaseModel

# to get a string like this run:
# openssl rand -hex 32
# JWT的签名的密钥
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
# 签名的算法
ALGORITHM = "HS256"
# 有效期
ACCESS_TOKEN_EXPIRE_MINUTES = 30

fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    }
}


class TokenData(BaseModel):
    username: Optional[str] = None


class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None


class UserInDB(User):
    hashed_password: str


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

app = FastAPI()



# 对名称的密码和哈希的密码进行校验对比
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


# 对用户的密码进行哈希处理
def get_password_hash(password):
    return pwd_context.hash(password)


def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def authenticate_user(fake_db, username: str, password: str):
    # 从当前的用户字典里面判断是否存在输入的用户的姓名,如果存在,就返回这个用户的实体,
    user = get_user(fake_db, username)
    # 如果没有这个用户信息
    if not user:
        return False
    # 如果存在用户信息,开始使用verify_password进行用户的密码和加盐后的密码的校验
    if not verify_password(password, user.hashed_password):
        return False
    # 验证通过就返回用户信息
    return user


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    # 计算时间差
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    # 根据密钥生成
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


async def get_current_user(token: str = Depends(oauth2_scheme)):

    # 提取我们的授权的的一直依赖的
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        # 验证通过从提取token
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        # 从payload里面提出出来之前的写入的用户信息
        username: str = payload.get("sub")
        if username is None:
            # 如果用户不存在,则抛出异常
            raise credentials_exception
        # 提取出我们的值
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    # 判断是否在数据字典中是否存在
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        # 如果用户不存在,则抛出异常
        raise credentials_exception
    # 存在则返回的我们的用户信息
    return user


async def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


# 定义接口的返回的Token输出的模型
class Token(BaseModel):
    access_token: str
    token_type: str


@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    '''
    登入获取用户授权,我们的token凭证的签发入口API
    :param form_data:
    :return:

    第1步:首先是经过OAuth2PasswordRequestForm的依赖注入的校验
    
    第2步: 通过校验后,我们的开始对我们的输入的用户的名字和密码从fake_users_db进行查询出来并进行校验
    
    第3步: 通过authenticate_user校验用户输入信息

       - 从当前的用户字典里面判断是否存在输入的用户的姓名,如果存在,就返回这个用户的实体,
       - 如果没有这个用户信息,表示验证不通过
       - 如果存在用户信息,开始使用verify_password进行用户的密码和加盐后的密码的校验, 验证通过就返回用户信息

     第4步: 返回验证信息之后,我们的开始生成我们的JWT

       - 生成JWT的过期的时间 access_token_expires,以minutes为单位
       - 给我们的JTW写入需要携带的数据信息

    '''
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    # 定义JWT有效期
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    # 写入JWT的用户信息,后续的可以从里面提取出来
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    # 需要写道签名值返回的access_token,如果通过
    return current_user


@app.get("/users/me/items/")
async def read_own_items(current_user: User = Depends(get_current_active_user)):
    return [{"item_id": "Foo", "owner": current_user.username}]


import uvicorn
if __name__ == '__main__':
    # 等于通过 uvicorn 命令行 uvicorn 脚本名:app对象 启动服务:uvicorn xxx:app --reload
    uvicorn.run('frun2:app', host="127.0.0.1", port=8000, debug=True, reload=True)

访问文档:

流程其实和上面的一样,登入成功后,表示就是签发成功我的TOKEN了,接下来,需要Token的API接口会自动的带上这个TOKEN滴咯!

如果用户姓名和密码:

image.png 查看接口 image.png

请求接口:

image.png

3.2.5 总结

感觉这个示例有点绕,但是其实和我以前使用PYjwt一个鸟样! 哈哈 按我的以往使用Flask的习惯的话,是直接的封装成为一个中间件来使用,如示例代码:

mport jwt
import datetime


class Auth():
    # 签名秘钥
    secret = 'super-man$&123das%qzq'
    # iss: 该JWT的签发者,是否使用是可选的;
    iss = 'xiaozhongtongxue'
    aud = 'www.xiaozhong.com'

    # 1000天有效期
    @classmethod
    def create_token_by_data(cls, sub='', data={}, scopes=['open'], exp_time=60 * 60 * 24 * 5):
        """
        生成对应的JWT的token值
        :param sub:    参数名称
        :param data:      参与签名的参数信息
        :param secret:   是否要求进行空检测,True必须检测
        :param exp_time:  token过期时间,按秒来计算
        :return:        返回处理后的参数
        """
        if not data:
            return False, {'token': '', 'meg': '需要签名信息不能为空'}

        payload = {
            "iss": cls.iss,  # iss: 该JWT的签发者,是否使用是可选的;
            "exp": datetime.datetime.utcnow() + datetime.timedelta(days=0, seconds=exp_time),  # 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;
            "iat": datetime.datetime.utcnow(),  # 在什么时候签发的(UNIX时间),是否使用是可选的;
            "aud": cls.aud,  # 接收该JWT的一方,是否使用是可选的;#  如果在生成token的时候使用了aud参数,那么校验的时候也需要添加此参数
            "sub": sub,  # sub: 该JWT所面向的用户,是否使用是可选的;
            "scopes": scopes,  # 用户授权的作用域,使用逗号(,)分隔
            "data": data
        }
        # 不参与进行签名计算
        if not sub:
            payload.pop('sub')
        # token生成处理
        token = jwt.encode(payload, cls.secret, algorithm='HS256')
        # 返回授权token
        return True, str(token, 'utf-8')

    @classmethod
    def verify_bearer_token(cls, ischecck_sub=False, sub_in='', token=''):
        #  如果在生成token的时候使用了aud参数,那么校验的时候也需要添加此参数
        try:
            payload = jwt.decode(token, cls.secret, audience=cls.aud, algorithms=['HS256'])
            if ischecck_sub and sub_in != '':
                sub = payload['sub']
                if sub != sub_in:
                    return False, "无效的Token"

            if payload and ('data' in payload):
                # 验证通过返回对应的参与签名的字段信息
                return True, payload['data']
            else:
                raise jwt.InvalidTokenError

        except jwt.ExpiredSignatureError:
            return False, "Token过期"

        except jwt.InvalidTokenError:
            return False, "无效的Token"
        except:
            return False, "无效的Token"

    @classmethod
    def verify_bearer_token_state(cls, ischecck_sub=False,sub_in='', token=''):
        #  如果在生成token的时候使用了aud参数,那么校验的时候也需要添加此参数
        try:
            payload = jwt.decode(token, cls.secret, audience=cls.aud, algorithms=['HS256'])
            if ischecck_sub and sub_in != '':
                sub = payload['sub']
                if sub != sub_in:
                    return False, 1, "无效的Token"

            if payload and ('data' in payload):
                # 验证通过返回对应的参与签名的字段信息
                return True, 0, payload['data']
            else:
                raise jwt.InvalidTokenError

        except jwt.ExpiredSignatureError:
            return False, 2, "Token过期"

        except jwt.InvalidTokenError:
            return False, 1, "无效的Token"
        except:
            return False, 1, "无效的Token"

END

小钟同学 | 文 【原创】【欢迎一起学习交流】| QQ:308711822