Flask 学习-26.JWT(JSON Web Token)生成Token

2022-09-06 14:17:26 浏览数 (1)

前言

JSON Web Token(JWT)是一个非常轻巧的规范。jwt广泛应用在系统的用户认证方面,特别是现在前后端分离项目。 python 中 pyjwt 是一个独立的包,flask 的插件集成了该功能可以使用 flask-jwt-extended 插件来实现。

环境准备

环境准备,需用到的包

代码语言:javascript复制
flask
flask-restful
flask-jwt-extended
passlib
flask-sqlalchemy

flask-jwt-extended官网https://flask-jwt-extended.readthedocs.io/en/latest/

认证方案Json Web Token(JWT)

jwt 的生成 token 格式如下,即:由 . 连接的三段字符串组成, 分别是header、payload、Signature

代码语言:javascript复制
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2MTk0MzE5NCwianRpIjoiNGIwOWNmMWItNzYzZS00NWQyLWI2N2QtNDY0ZWQxODVkYmIxIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3Q1IiwibmJmIjoxNjYxOTQzMTk0LCJleHAiOjE2NjE5NDY3OTR9.EgBPLNfZ34fLGngjLj5HhjHowUN5UsZXvzfqQs_MkMk

HEADER 部分,固定包含算法和 token 类型,该部分数据需要转换成json串并用base64转码

  • alg代表要使用的 算法 HMAC-SHA256 简写HS256
  • typ表明该token的类别 此处必须为 大写的 JWT
代码语言:javascript复制
{
    "alg": "HS256",
    "typ": "JWT"
}

PAYLOAD 部分, 在cookie和session中会将用户id或名字写入到其中,在token中会将其写在payload中。格式为字典-此部分分为公有声明和私有声明 公有声明:JWT提供了内置关键字用于描述常见的问题 此部分均为可选项,用户根据自己需求 按需添加key,常见公共声明如下:

  • iss  【issuer】发布者的url地址
  • sub 【subject】该JWT所面向的用户,用于处理特定应用,不是常用的字段
  • aud 【audience】接受者的url地址
  • exp 【expiration】 该jwt销毁的时间; unix时间戳
  • nbf  【not before】 该jwt的使用时间不能早于该时间; unix时间戳
  • iat   【issued at】 该jwt的发布时间; unix 时间戳
  • jti    【JWT ID】 该jwt的唯一ID编号
代码语言:javascript复制
{
'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外没有任何存储的东西,都是计算。所以不会占用数据库资源。

Users表设计

User 表的内容

代码语言:javascript复制
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 视图

校验用户账户和密码正确后,生成token

代码语言:javascript复制
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()

jwt 初始化

在启动之前还需在create_app() 工厂函数先初始化jwt

代码语言:javascript复制
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文件添加相关配置

代码语言:javascript复制
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

验证登录接口

启动服务,验证接口

代码语言:javascript复制
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"
}

接口返回

代码语言:javascript复制
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"
}

需要token验证的接口

需要token验证的接口,加上装饰器@jwt_required()

代码语言:javascript复制
class UserInfo(Resource):

    @jwt_required()
    def get(self):
        """根据token 解析用户username"""
        current_user = get_jwt_identity()
        return {
            "user": current_user
        }

# 注册
api.add_resource(UserInfo, '/api/v1/userinfo')

测试验证接口,不带token请求,返回401 UNAUTHORIZED

代码语言:javascript复制
GET http://127.0.0.1:5000/api/v1/userinfo HTTP/1.1
User-Agent: Fiddler
Host: 127.0.0.1:5000
Content-Type: application/json
Content-Length: 0

HTTP/1.1 401 UNAUTHORIZED
Server: Werkzeug/2.2.2 Python/3.8.5
Date: Wed, 31 Aug 2022 14:49:09 GMT
Content-Type: application/json
Content-Length: 44
Connection: close

{
  "msg": "Missing Authorization Header"
}

需在请求头部加上token认证,格式

代码语言:javascript复制
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJ.....token

带上token重新请求

代码语言:javascript复制
GET http://127.0.0.1:5000/api/v1/userinfo HTTP/1.1
User-Agent: Fiddler
Host: 127.0.0.1:5000
Content-Type: application/json
Content-Length: 0
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2MTk1NzA2NiwianRpIjoiNmY4NWRlNGEtZThhNS00ZGY2LWJiMjktMmM4NWQyMWE3ZjU3IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3Q1IiwibmJmIjoxNjYxOTU3MDY2LCJleHAiOjE2NjE5NjA2NjZ9.GKsz2nJUziXLWfYrzidX7Fopw5tlycT0lZBKlvnpt8s

HTTP/1.1 200 OK
Server: Werkzeug/2.2.2 Python/3.8.5
Date: Wed, 31 Aug 2022 14:50:54 GMT
Content-Type: application/json
Content-Length: 24
Connection: close

{
    "user": "test5"
}

get_jwt_identity()方法可以从token中解析出用户username,这样就可以方便判断当前登录用户是哪个了

2022年第 12期《python接口web自动化 测试开发》课程,9月17号开学!

本期上课时间:2022年9月17号 - 2022年12月17号,周六周日上午9:00-11:00

报名费:报名费3000一人(周期3个月)

联系微信/QQ:283340479

0 人点赞