Python操作 JWT(python-jose包)、哈希(passlib包)、用户验证完整流程

news2024/11/16 12:07:59

一、JWT简介

JWT是什么?

  • JWT 即JSON 网络令牌(JSON Web Tokens)。

  • JWT(JSON Web Token) 是一种用于在身份提供者和服务提供者之间传递身份验证和授权数据的开放标准。JWT是一个JSON对象,其中包含了被签名的声明。这些声明可以是身份验证的声明、授权的声明等。JWT可以使用数字签名进行签名,以确保它不被篡改。

  • JWT 是一种将 JSON 对象编码为没有空格,且难以理解的长字符串的标准。JWT 的内容如下所示:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
    
  • JWT 字符串没有加密,任何人都能用它恢复原始信息。
    jwt官网及在线解码

  • 但 JWT 使用了签名机制。接受令牌时,可以用签名校验令牌。

  • 例如:使用 JWT 创建有效期为一周的令牌。第二天,用户持令牌再次访问时,仍为登录状态。令牌于一周后过期,届时,用户身份验证就会失败。只有再次登录,才能获得新的令牌。如果用户(或第三方)篡改令牌的过期时间,因为签名不匹配会导致身份验证失败。

JWT由三部分组成

JWT的格式为:xxx.xxx.xxx

  1. Header: 这部分包含了JWT的类型和签名所使用的算法。
  2. Payload: 也可以叫claims,这部分包含了JWT的声明,例如身份验证的声明和授权的声明。
  3. Signature: 这部分是使用算法和密钥对前两部分进行签名得到的。这部分用于验证JWT的发送者是否可信任。

在这里插入图片描述

Bearer JWT 和 JWT 不是同一个意思

  • JWT (JSON Web Token) 是一种用于在双方之间传递身份验证信息的标准。它是一个 JSON 对象,包含了被签名的声明。这些声明可以是身份验证的信息,比如用户名和密码,也可以是其他与此有关的信息,比如用户的权限。

  • Bearer JWT 是 JWT 的一种使用方式。在这种方式中,JWT 被用作认证授权的令牌,并通过 HTTP 请求的 Authorization 头部进行发送。这个头部的值是 Bearer空格 加上 JWT。

  • 所以 JWT 是一种身份验证和授权的标准, Bearer JWT 则是 JWT在HTTP请求中的一种传递方式。

进行JWT验证主要有以下几步

  1. 验证JWT签名:使用JWT的签名算法和密钥对JWT的签名部分进行验证,以确保JWT的发送者是可信任的。
  2. 验证JWT的有效期:使用JWT中的exp(expiration)和nbf(not before)声明来验证JWT是否在有效期内。
  3. 验证JWT的权限:使用JWT中的scope声明来验证用户是否有请求资源的权限。
  4. 验证JWT的其他声明:使用JWT中的其他声明来验证用户是否符合其他条件。

openssl rand -hex 32 什么意思?

openssl rand -hex 32是一个命令行命令,它使用 OpenSSL 库生成 32 个字符的十六进制随机字符串。

  • ‘openssl’ 是一种常用的工具,用于实现各种安全协议和算法,包括密钥生成、证书管理等。
  • ‘rand’ 是 OpenSSL 库的一个子命令,它用于生成随机数。
  • ‘-hex’ 指示 rand 子命令使用十六进制输出结果。
  • ‘32’ 是随机字符串的长度。

所以这个命令就是生成32位十六进制随机字符串。这种随机字符串常用来做盐值,可以用来加密密码,来防止密码被破解。

例如:

$ openssl rand -hex 32
> 6b48014c061a82bb3c6fd4812777552cf41a79c38149e31516b818a73d50ee51
  • 这里主要用于JWT的Signature,以确保JWT的发送者是可信任的。即创建用于 JWT 令牌签名的随机密钥。

安装 python-jose

  • python-jose包用于在Python中 生成和校验 JWT 令牌

  • python-jose官网

  • python-jose需要安装配套的加密后端,例如下面我们使用的后端是pyca/cryptography

$ pip3 install "python-jose[cryptography]"
  • 除了python-jose,相似的还有PyJWT,但还是推荐Python-jose ,因为支持 PyJWT 的所有功能,还支持与其它工具集成时可能会用到的一些其它功能。

二、密码哈希

哈希是指把特定内容(本例中为密码)转换为乱码形式的字节序列(其实就是字符串)。

每次传入完全相同的内容时(比如,完全相同的密码),返回的都是完全相同的乱码。

但这个乱码无法转换回传入的密码。

为什么使用密码哈希

原因很简单,假如数据库被盗,窃贼无法获取用户的明文密码,得到的只是哈希值。

这样一来,窃贼就无法在其它应用中使用窃取的密码,要知道,很多用户在所有系统中都使用相同的密码,风险超大)。

bcrypt算法是什么?

  • bcrypt是一种密码哈希算法。它通过对用户的密码进行加密来保护它们。

  • bcrypt使用一种称为"慢哈希"的技术来提高安全性。在这种技术中,算法会在计算哈希值时进行大量迭代,从而使得暴力破解变得更加困难。

  • bcrypt 也支持一种称为 “盐” 的概念。盐是一串随机字符串,它被附加到密码上,然后一起计算哈希值。这样即使两个用户使用了相同的密码,它们的哈希值也会不同。

  • bcrypt 是被广泛接受的密码哈希算法,因为它能够高效地防止暴力破解,并且它的实现方式可以防止在多种并行计算环境中的高速破解。

  • 在Python中可以使用bcrypt库来使用bcrypt算法。

    import bcrypt
    
    password = b"supersecretpassword"
    # Hash a password for the first time, with a randomly-generated salt
    hashed = bcrypt.hashpw(password, bcrypt.gensalt())
    
    # Check that an unencrypted password matches one that has
    # previously been hashed
    if bcrypt.checkpw(password, hashed):
        print("It Matches!")
    else:
        print("It Does not Match :(")
    

安装 passlib

  • Passlib 是处理密码哈希的 Python 包。

  • 它支持很多安全哈希算法及配套工具。

  • 本教程推荐的算法是 Bcrypt。因此,请先安装附带 Bcrypt 的 PassLib:

    $ pip3 install passlib[bcrypt]
    
  • passlib 甚至可以读取 Django、Flask 的安全插件等工具创建的密码。

    例如,把 Django 应用的数据共享给 FastAPI 应用的数据库。或利用同一个数据库,可以逐步把应用从 Django 迁移到 FastAPI。

    并且,用户可以同时从 Django 应用或 FastAPI 应用登录。

"""
passlib.context.CryptContext 类可以用来配置和管理密码哈希算法。
这个实例的意思是:使用 "bcrypt" 算法对密码进行哈希,并使用 "auto" 模式对过时的算法进行处理。
"bcrypt" 是一种常用的密码哈希算法,它可以防止密码被暴力破解。
"auto" 模式表示自动处理过时的算法,例如在验证时优先使用最新的算法,如果失败则尝试使用旧的算法。
这个配置可以确保密码安全性,并且对过时算法进行兼容性处理。
Passlib 是一个强大的密码管理库,提供了一组密码哈希,验证和生成密码的工具,并且支持多种常用的密码哈希算法。
"""

from passlib.context import CryptContext

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

def verify_password(plain_password, hashed_password):
    """验证输入的密码的hash密码与数据库中记录的hash密码是否是一样的
    :param plain_password: 用户输入的密码
    :param hashed_password: hash密码
    :return:
    """
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
    """把密码转为hash密码
    """
    return pwd_context.hash(password)

if __name__ == '__main__':
    password_hash = get_password_hash("Abc123.")
    print(password_hash)  # 注意:每次运行打印的值都是不同的

    print(verify_password("Abc123.", password_hash))  # True
    print(verify_password("abc123.", password_hash))  # False

三、Python实现用户验证的完整流程(用于生产环境)

from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Union
from passlib.context import CryptContext
from pydantic import BaseModel

# 要获取如下所示的字符串,请运行:openssl rand -hex 32
# SECRET_KEY用于JWT令牌签名的随机密钥
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
# 指定 JWT 令牌签名算法的变量 ALGORITHM
ALGORITHM = "HS256"
# 设置令牌过期时间的变量,这里是30分钟
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# 假数据,假如这是数据库中的用户表的全量数据
fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        # 下面的hash password对应的明文密码为Abc123.
        "hashed_password": "$2b$12$oC25Sks9Kg8WU8N3ddoeNugEYYQDQU9Cph7aLvP9VL.uNykdgCQQG",
        "disabled": False,
    }
}

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


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


class UserInDB(User):
    hashed_password: str


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


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """验证输入的密码的hash密码与数据库中记录的hash密码是否是一样的
    :param plain_password: 用户输入的密码
    :param hashed_password: hash密码
    :return:
    """
    return pwd_context.verify(plain_password, hashed_password)


def get_user(db: dict, username: str) -> UserInDB:
    """模拟在数据库中查找用户,找到之后初始化UserInDB类并返回实例"""
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def authenticate_user(fake_db: dict, username: str, password: str) -> Union[bool, UserInDB]:
    """
    先验证$username用户是否在数据库中存在,存在则继续验证用户输入的明文密码与数据库中记录的hash密码是否匹配
    如果都没问题就返回<class '__main__.UserInDB'>
    :param fake_db:
    :param username:
    :param password:
    :return:
    """
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):  # user是UserInDB类的实例,所以可以点属性
        return False
    return user


def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None) -> str:
    """创建带exp字段的JWT字符串"""
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta  # 这里是utc时间,不是东八区时间
        # print(expire)  # 2023-01-18 08:14:02.453944
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})  # datetime.datetime(2023, 1, 18, 8, 14, 02, 453944)

    # SECRET_KEY对声明集进行签名的密钥
    # jwt.encode()对声明集进行编码并返回 JWT 字符串。
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


def get_current_user(token: str) -> Union[UserInDB, None]:
    """
    解密JWT,即验证JWT字符串的SIGNATURE签名并返回claims(也称PAYLOAD)的信息
    :param token:
    :return:
    """
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        # print(payload)  # {'sub': 'johndoe', 'exp': 1674033230}
        username: str = payload.get("sub")
        if username is None:
            print("username 不存在")
        token_data = TokenData(username=username)
        user = get_user(fake_users_db, username=token_data.username)
        return user
    except JWTError as e:
        print(e)  # Signature verification failed.


if __name__ == '__main__':
    # form_data是模拟Request body的参数
    form_data = {
        "username": "johndoe",
        "password": "Abc123."
    }

    # 创建token过期时间
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    # print(access_token_expires)  # <class 'datetime.timedelta'> 0:30:00

    # 验证Request body传入的用户是否在数据库中存在
    # 如果存在则比对password与数据库中记录的hash password是否匹配
    # 最终,没问题则返回该用户的UserInDB类的实例对象user;有问题则返回False
    user = authenticate_user(fake_users_db, form_data.get("username"), form_data.get("password"))
    print(user)

    # 创建PAYLOAD带exp字段、sub字段的JWT字符串
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    print(access_token)  # 解码用https://jwt.io/

    # 根据JWT获取当前用户
    current_user = get_current_user(access_token)
    print(current_user)

当然,我们还可以为token添加权限。

划重点,sub 键在整个应用中应该只有一个唯一的标识符,而且应该是字符串。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/170815.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑开机出现绿屏错误无法启动怎么办?

电脑开机出现绿屏错误无法启动怎么办&#xff1f;有用户电脑开机的时候&#xff0c;突然出现了屏幕变成绿色的情况&#xff0c;而且上面有很多的错误代码。然后卡在页面上一直无法进入到桌面&#xff0c;重启电脑后依然无效。那么如何去解决这个问题呢&#xff1f;来看看具体的…

Java---Spring---SpringCache

SpringCache入门学习SpringCache介绍SpringCatch常用注解SpringCatch使用1.导入maven坐标2.配置application.yml3.在启动类上加入EnableCaching注解&#xff0c;开启缓存注解功能4.在controller的方法上加入Cacheable,CacheEvict等注解&#xff0c;进行缓存操作缓存穿透定义解决…

【Nginx】入门看这一篇就够啦,nginx 简介、安装、工作原理、工作方式、详解配置文件

目录 1、nginx 简介 2、nginx的工作原理 3、nginx 工作方式 4、nginx 安装 命令行安装 卸载命令 从源码构建 查看版本 测试启动 5、详解nginx配置文件 第一部分&#xff1a;全局块 第二部分&#xff1a;events块 第三部分&#xff1a;http 6、hosts 文件简介 1、…

解析Activity启动-窗口篇

解析Activity启动-窗口篇 在 解析Activity启动 前两篇文章中&#xff0c;我们分别专注于 堆栈 和 生命周期角度大致的过了一遍启动流程&#xff0c;而本篇会着重窗口的创建和显示流程&#xff0c;继续梳理Activity的启动流程 顺着前两篇文章的分析流程&#xff0c;我们知道和 …

DBCO高分子PEG_DBCO-PEG-Lipoic COOH_二苯并环辛炔-聚乙二醇-硫辛酸

DBCO-PEG-Lipoic acid“点击化学"一般由叠氮化物&#xff08;azide&#xff09;和炔烃&#xff08;alkyne&#xff09;作用形共价键&#xff0c;具有高效稳定&#xff0c;高特异性等优点。反应不受PH影响&#xff0c;能在常温条件下的水中进行,甚至能在活细胞中进行。DBCO…

第十三届蓝桥杯省赛 JAVA A组 - 矩形拼接

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4da;专栏地址&#xff1a;蓝桥杯题解集合 &#x1f4dd;原题地址&#xff1a;付账问题 &#x1f4e3;专栏定位&#xff1a;为想参加蓝桥别的小伙伴整理常考算法题解&#xff0c;祝大家…

Python学习中的六个技巧小结

1. 引言 “Beautiful is better than ugly.” 上述为著名的The Zen of Python的第一句话&#xff0c;也是有追求的python开发人员的信条之一。 所以我们的问题来了&#xff1a; 如何编写漂亮的Python代码? 本文重点通过九个示例向大家展示Python中的六个小技巧&#xff0c;以帮…

java后端-servlet超详细入门

java后端介绍今天我正式开始了一个新话题&#xff0c;那就是 Web。目前我主要会介绍后端。作为后端的老大哥 java&#xff0c;也有很多后端框架&#xff0c;比如大家耳熟能详的 spring 等。今天来带大家入门 servlet&#xff0c;不管是学生&#xff0c;刚毕业或是已经工作自学编…

【倍增】魔力小球

今天最后一篇&#xff0c;该睡了&#xff0c;怕猝死QwQ学校OJ上的一道模板题&#xff0c;去年不会做&#xff0c;今年还是不会做嘻嘻&#xff0c;还好最后调出来了&#xff0c;错的原因竟然是题目有歧义这个小球i的i是他喵的小球编号&#xff0c;不是id&#xff01;出题人是懂出…

Win11的两个实用技巧系列之电脑system占用高的解决办法

Win11 system占用cpu过高是什么原因? Win11电脑system占用高的解决办法Win11 system占用cpu过高是什么原因&#xff1f;Win11系统遇到system占用cpu很高&#xff0c;该怎么解决呢&#xff1f;下面我们就来看看Win11电脑system占用高的解决办法System占用cpu过高导致电脑卡顿&a…

2023年中职网络安全技能竞赛网页渗透(注入版)

竞赛任务书内容 (一)拓扑图 网页渗透 任务环境说明: 服务器场景:Server2121 服务器场景操作系统:未知(封闭靶机) 用户名:未知 密码:未知 1.访问服务器网站目录1,根据页面信息完成条件,将获取到的flag提交; 2.访问服务器网站目录2,根据页面信息完成条件,将获…

学生写字台灯用什么牌子的好?高品质学生台灯品牌推荐

学生写字台灯&#xff0c;很明显就是为学生而设计的&#xff0c;针对学生长时间学习&#xff0c;用眼强度大的特点&#xff0c;这种学生台灯在设计上对灯光的亮度、样式、护眼技术都是很有讲究的&#xff0c;为的就是保护学生眼睛&#xff0c;在一定程度上缓解眼部疲劳的作用。…

进程概念理解

既然要了解计算机的进程&#xff0c;那么就需要先了解一下计算机的底层结构 目录 冯洛伊曼体系结构 操作系统 系统调用接口 进程 PCB task_struct 内容 操作系统如何组织进程 冯洛伊曼体系结构 想了解计算机的底层结构&#xff0c;那么必定绕不开冯洛伊曼体系结构&…

19/365 java 多线程

1. 基础概念 程序&#xff1a;指令集和数据的集合。&#xff08;静态&#xff09; 进程&#xff1a;对程序的一次执行。&#xff08;动态&#xff09; 对同一个程序&#xff0c;执行两次&#xff0c;那就是两个进程。 进程是系统资源分配的基本单位 线程&#xff1a;一个进程…

gma 1.1.2 (2023.01.14) 更新日志(重大更新:开始支持空间绘图)

重大更新&#xff1a;从本版本开始&#xff0c; gma 逐步 支持空间绘图功能&#xff08;依赖 matplotlib&#xff09;&#xff01; 获取 gma 1.1.2 1、百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1eT2rJRiUDJuJkWeLJNL-cw?pwdb07n 提取码&#xff1a;b…

基于SIMULINK的动力电池CAN通信仿真教程

在真实的整车开发过程中&#xff0c;整车厂一般会先设计出整车网络架构&#xff0c;并依据此架构及ECU之间的功能交互设计网络总线数据库&#xff08;Database&#xff09;, 作为重要的技术文档&#xff0c;可以根据需 要全部或部分地公开给各个ECU供应商。也存在一些特殊情况&…

【渗透测试】web端姿势-前端利用

目录 前端 存在问题 关于密码重置 jwt攻击 jwt介绍 工具使用 学习来源 前端 存在问题 任意用户注册 未授权访问&#xff0c;直接访问对应链接&#xff0c;可得到系统权限 可爆破用户名 爆破用户名&#xff0c;密码 用户名注入 万能密码 用户名Xss 修改返回包信息&#…

(三)计算机组成原理——总线

文章目录&#xff08;三&#xff09;计算机组成原理——总线总线的基本概念单总线双总线面向CPU以存储器为中心总线的分类片内总线系统总线数据总线地址总线控制总线通信总线总线特性及性能指标总线特性机械特性电气特性功能特性时间特性性能指标总线标准总线结构单总线多总线双…

3.1 python高阶应用

文章目录闭包装饰器设计模式单例模式工厂模式多线程进程、线程和并行执行多线程编程网络编程服务端开发客户端开发正则表达式基础匹配元字符匹配递归闭包 def account_create(inital_account 0) :def atm(num:int,deposite:bool True) :# 声明inital_account是外部声明的init…

【Ajax】服务器的基本概念

一、客户端与服务器上网的目的通过互联网的形式来获取和消费资源。2. 服务器上网过程中&#xff0c;负责存放和对外提供资源的电脑&#xff0c;叫做服务器。3. 客户端上网过程中&#xff0c;负责获取和消费资源的电脑&#xff0c;叫做客户端。二、URL地址URL地址的概念URL&…