python-jose 实现fastapi登录验证
JWT 和 Session 的区别:
JWT:JWT是一种无状态的认证机制。由于JWT令牌包含了用户的身份信息以及相关的元数据,服务端不需要存储任何用户状态信息,只需要验证JWT令牌的真实性和有效性即可。这使得JWT非常适合于构建无状态的分布式系统,因为JWT令牌可以在不同的服务之间轻松共享。
Session:Session则是一种有状态的认证机制。服务端需要存储用户的认证状态信息,如Session对象,以便在后续的请求中通过Session ID来识别用户身份和权限。这种机制在单一服务的架构中较为常见,但在分布式系统中需要实现多机数据共享,以确保Session信息的一致性和可用性。
目前前后端分离用JWT比较多,这篇文章介绍的是fastapi框架中的登录验证实现。
1、生成 token
直接上代码,token 通过exp参数设置过期时长,选择 HS256、RS256等加密算法。
from datetime import datetime,timedelta
from jose import jwt
from typing import Optional
import logging,os
# 加密秘钥
SECRET_KEY = os.getenv('SECRET_KEY')
hours = os.getenv('hours')
def creat_access_token(user_id: str,
username:str,
hour:Optional[int] = int(hours))->str:
# 设置过期时间
expire = datetime.utcnow()+timedelta(hours=hour)
# exp 必传参数
to_encode = {"exp":expire,"user_id":user_id,"username":username}
# 生成token,algorithm使用 HS256 加密算法
access_token = jwt.encode(to_encode,SECRET_KEY,algorithm = 'HS256')
return access_token
2、验证token
这里用到了Headers,在FastAPI中,Headers是一个特殊的类型,用于处理HTTP请求头(Headers)。Headers允许你接收、访问和修改HTTP请求中的头部信息。token:str=Header(...) 括号里 ... 表示在header里 token 是必传字段。提取到token后,使用jwt.decode()方法解析验证。
from jose.exceptions import ExpiredSignatureError,JWTError,JWSError,JWKError
from jose import jwt
from fastapi import Header
# 签名秘钥
SECRET_KEY = os.getenv('SECRET_KEY')
def check_access_token(token:str=Header(...)):
try:
payload = jwt.decode(token,SECRET_KEY,algorithms = ['HS256'])
print('token验证成功!')
return payload
except ExpiredSignatureError:
print('token过期')
except JWTError:
print('token验证失败')
在fastapi中实现JWT认证登录。步骤1和2的加密、解密了解清楚了,接下来就比较简单了。先把上面creat_access_token、check_access_token封装到一个security.py文件,然后在业务端引用。
3、API中实现生成token
from fastapi import APIRouter,Depends
from models.cms.user import User
from sqlalchemy.orm import Session
from schemas.cms.user import login
import logging
from plugin.pulgin_sqlalchamy import db
from libs.security import creat_access_token
log = logging.getLogger('uvicorn')
user = APIRouter()
def get_db():
try:
yield db
finally:
db.close()
@user.post('/login')
async def login(data: login, db: Session =Depends(get_db)):
user = db.query(User).filter_by(username = data.username,password = data.password).first()
# 若存在账号密码,创建token
if user:
user_id = user.id
token = creat_access_token(user_id, data.username)
return token
else:
return '账号密码不正确'
4、API中验证token
验证token,用到了Depends 。Depends 是一个非常重要的特性,它允许你以依赖注入的方式向你的路径操作函数(path operation functions)中传递依赖项。这种方式让你的代码更加模块化,更加易于管理和测试。使用 Depends,你可以将复杂的逻辑(如认证、验证、数据库连接等)封装到独立的函数中,然后在需要时通过 Depends 注入到路径操作函数中。这一步使用 Depends 来验证 headers中的 token,在上面 security.py 文件中有定义获取headers中的token参数。
from fastapi import APIRouter,Depends
from models.cms.user import User
from sqlalchemy.orm import Session
from schemas.cms.user import login
import logging
from plugin.pulgin_sqlalchamy import db
from libs.security import creat_access_token,check_access_token
@user.post('/dev')
async def dev(data:dict,access_token:str=Depends(check_access_token) ,db: Session = Depends(get_db)):
user = db.query(User).filter_by(username = data['username'],password = data['password']).first()
return user