JSON Web Token(JWT)是一个非常轻巧的规范。jwt广泛应用在系统的用户认证方面,特别是现在前后端分离项目。
python 中 pyjwt 是一个独立的包,flask 的插件集成了该功能可以使用 flask-jwt-extended 插件来实现。
环境准备,需用到的包
flask flask-restful flask-jwt-extended passlib flask-sqlalchemy
https://blog.csdn.net/key_world/article/details/109634148
https://github.com/dickens88/flask-jwt-demo
jwt 的生成 token 格式如下,即:由 . 连接的三段字符串组成, 分别是header、payload、Signature
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2MTk0MzE5NCwianRpIjoiNGIwOWNmMWItNzYzZS00NWQyLWI2N2QtNDY0ZWQxODVkYmIxIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3Q1IiwibmJmIjoxNjYxOTQzMTk0LCJleHAiOjE2NjE5NDY3OTR9.EgBPLNfZ34fLGngjLj5HhjHowUN5UsZXvzfqQs_MkMk
HEADER 部分,固定包含算法和 token 类型,该部分数据需要转换成json串并用base64转码
{ "alg": "HS256", "typ": "JWT" }
PAYLOAD 部分, 在cookie和session中会将用户id或名字写入到其中,在token中会将其写在payload中。格式为字典-此部分分为公有声明和私有声明
公有声明: JWT提供了内置关键字用于描述常见的问题
此部分均为可选项,用户根据自己需求 按需添加key,常见公共声明如下:
{ 'exp': time.time()+300s, "iat": 1516239022 ... }
Signature 签名
签名规则如下:
根据header中的alg确定具体算法,以下用HS256为例:
HS256(自定义的key,base64后的header + b’.‘ + base64后的payload,digestmod=‘SHA256’)
解释:用自定义的key,对base64后的header + b’.’ + base64后的payload进行hmac计算。
JWT整个过程中除了一个自定义的加密key外没有任何存储的东西,都是计算。所以不会占用数据库资源。
User 表的内容
class Users(db.Model): __tablename__ = 'user' # 数据库表名 id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(50), unique=True, nullable=False) password = db.Column(db.String(128), nullable=False) is_active = db.Column(db.Boolean, default=1) email = db.Column(db.String(64), nullable=True) def hash_password(self, password): """密码加密""" self.password = sha256_crypt.encrypt(password) def verify_password(self, password): """校验密码""" return sha256_crypt.verify(password, self.password)
校验用户账户和密码正确后,生成token
from apps import create_app, db from flask import url_for, request, jsonify from flask_restful import reqparse, abort, Api, Resource from flask_jwt_extended import ( create_access_token, create_refresh_token, jwt_required, get_jwt_identity, get_jwt ) from apps.models import Users app = create_app() api = Api(app) class Login(Resource): def post(self): args = reqparse.RequestParser() \ .add_argument('username', type=str, location='json', required=True, help="用户名不能为空") \ .add_argument("password", type=str, location='json', required=True, help="密码不能为空") \ .parse_args() print(f"args: {args}") user = Users.query.filter_by(username=args.get('username')).first() if not user: return {"code": 222, "msg": f"用户名或密码不正确"} else: if not user.is_active: return {"code": 333, "msg": f"{user.username} not active"} else: # 验证密码 if user.verify_password(args.get('password')): access_token = create_access_token(identity=user.username) return jsonify({ "code": "0", "message": "success", "data": { "access_token": access_token, "userid": user.id } }) else: return {"code": 222, "msg": f"用户名或密码不正确"} # 注册 api.add_resource(Login, '/api/v1/login') if __name__ == '__main__': app.run()
在启动之前还需在create_app() 工厂函数先初始化jwt
from flask import Flask import os from flask_sqlalchemy import SQLAlchemy from config import config_env from flask_migrate import Migrate from flask_jwt_extended import JWTManager db = SQLAlchemy() # 数据库 jwt = JWTManager() # jwt 生成token def create_app(test_config=None): # create and configure the app app = Flask(__name__, instance_relative_config=True) ...... # db 数据库初始化 db.init_app(app) # jwt 初始化 jwt.init_app(app) # ...... return app
config.py文件添加相关配置
class DevelopmentConfig(Config): """开发环境""" DEBUG = True # ..... # jwt 相关配置 JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY') or 'jwt-xxx' JWT_COOKIE_CSRF_PROTECT = True JWT_CSRF_CHECK_FORM = True JWT_ACCESS_TOKEN_EXPIRES = os.environ.get('JWT_ACCESS_TOKEN_EXPIRES') or 3600 PROPAGATE_EXCEPTIONS = True
启动服务,验证接口
POST http://127.0.0.1:5000/api/v1/login HTTP/1.1 User-Agent: Fiddler Host: 127.0.0.1:5000 Content-Type: application/json Content-Length: 56 { "username": "test5", "password": "123456" }
接口返回
HTTP/1.1 200 OK Server: Werkzeug/2.2.2 Python/3.8.5 Date: Wed, 31 Aug 2022 11:07:58 GMT Content-Type: application/json Content-Length: 368 Connection: close { "code": "0", "data": { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2MTk0NDA3NywianRpIjoiOTdmNDg4ZTEtYTEzMi00NTQxLWJiMmItYjBiZDU5MDZkOTM4IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3Q1IiwibmJmIjoxNjYxOTQ0MDc3LCJleHAiOjE2NjE5NDc2Nzd9.Y7tJVGLKJx7V1-A2c_qr14S7EicLp2zwOqfGrnFTNlY", "userid": 5 }, "message": "success" }