当前位置: 首页 > news >正文

fastapi双token机制登录实现

fastapi双token机制登录实现

一、整体架构

二、代码实现

from datetime import datetime, timedelta, timezone import uuid from redis import asyncio from fastapi import HTTPException, Depends,FastAPI,Response,Request from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from jose import jwt, JWTError from pydantic import BaseModel from tortoise.contrib.fastapi import register_tortoise from app.config.model_conf.settings import TORTOISE_ORM from app.models.users import Users api_login = FastAPI() class user_login(BaseModel): username: str password: str class user_login_success(BaseModel): token: str token_type: str="Bearer" ACCESS_TOKEN_EXPIRE_MINUTES = 15 REFRESH_TOKEN_EXPIRE_DAYS = 7 SECRET_KEY = "CHANGE_ME_TO_32RANDOM_STRING" ALGORITHM = "HS256" REDIS_URL = "redis://192.168.88.14:6379/0" BLK_PREFIX = "black:jti:" REF_PREFIX = "refresh:" def create_access_token(username: str, jti: str) -> str: expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) return jwt.encode({"username": username, "jti": jti, "exp": expire}, SECRET_KEY, algorithm=ALGORITHM) # redis初始化 async def create_redis(): redis = await asyncio.from_url( REDIS_URL, decode_responses=True, max_connections=100, ) return redis @api_login.post("/login",response_model=user_login_success) async def login(user:user_login,response: Response,redis=Depends(create_redis)): user_info = Users.get(uername=user.username,password=user.password) if not user_info: raise HTTPException(status_code=401,detail="用户名或密码错误") # 生成access_token jti = str(uuid.uuid4()) access_token = create_access_token(user.username,jti) # 生成refresh_token,存储redis和cookie refresh_token = str(uuid.uuid4()) await redis.setex(REF_PREFIX+refresh_token,REFRESH_TOKEN_EXPIRE_DAYS*60*60*24,user.username) response.set_cookie( key="refresh_token", value=refresh_token, max_age=REFRESH_TOKEN_EXPIRE_DAYS*60*60*24, httponly=True, samesite="lax", secure=False, # 生产 True ) return {"token": access_token} # 解码token,验证token是否合法 def decode_token(token: str): try: return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) except JWTError: return None # 获取当前登录用户信息 http_bearer = HTTPBearer(auto_error=True) async def get_current_user( cred: HTTPAuthorizationCredentials = Depends(http_bearer), redis=Depends(create_redis) ): # 解码token username_jti = decode_token(cred.credentials) print(username_jti) if not username_jti: raise HTTPException(status_code=401,detail="token无效") if not username_jti.get("username") or not username_jti.get("jti"): raise HTTPException(status_code=401,detail="token无效") # 验证用户是否已经退出 if await redis.exists(BLK_PREFIX+username_jti.get("jti")): raise HTTPException(status_code=401,detail="token已失效") return username_jti.get("username") # 重新签发access_token和refresh_token @api_login.post("/refresh") async def refresh(request: Request,response: Response,redis=Depends(create_redis),current_user: str = Depends(get_current_user)): # refresh_token存在,access_token失效,重新生成token # 判断并生成refresh_token refresh_token=request.cookies.get("refresh_token") if not refresh_token: raise HTTPException(status_code=401,detail="缺少 refresh_token") if not await redis.exists(REF_PREFIX+refresh_token): raise HTTPException(status_code=401,detail="refresh_token不存在") new_token = str(uuid.uuid4()) await redis.delete(REF_PREFIX+refresh_token) await redis.setex(REF_PREFIX+new_token,REFRESH_TOKEN_EXPIRE_DAYS*60*60*24,current_user) response.set_cookie( key="refresh_token", value=new_token, max_age=REFRESH_TOKEN_EXPIRE_DAYS*60*60*24, httponly=True, samesite="lax", secure=False, ) # 生成access_token new_jti = str(uuid.uuid4()) access_token = create_access_token(current_user,new_jti) return {"access_token":access_token} # 注销,依赖当前登录的用户,拉黑+删除token,redis @api_login.post("/logout") async def logout(request: Request,redis=Depends(create_redis), username_jti: str = Depends(get_current_user), cred: HTTPAuthorizationCredentials = Depends(http_bearer), ): # 退出,请求头获取token,拉黑当前请求的token header_token = decode_token(cred.credentials) await redis.setex(BLK_PREFIX+header_token.get("jti"),ACCESS_TOKEN_EXPIRE_MINUTES*60,1) # 从cookie中获取refresh_token,删除reids的refresh_token refresh_token = request.cookies.get("refresh_token") if refresh_token: await redis.delete(REF_PREFIX+refresh_token) else: raise HTTPException(status_code=401,detail="缺少refresh_token") return {"msg": "注销成功"} class user_register(BaseModel): msg: str @api_login.post("/register",response_model=user_register) async def register(user:user_login): # 1. 用户名重复检查 if await Users.filter(username=user.username).exists(): raise HTTPException(status_code=409, detail="用户已存在") if await Users.create(username=user.username, password=user.password): return {"msg": "注册成功,跳转到登录界面"} return {"msg": "注册失败"} # 数据库参数配置 register_tortoise( api_login, config=TORTOISE_ORM, ) if __name__ == "__main__": import uvicorn uvicorn.run(api_login, host="127.0.0.1", port=8000)
http://www.cnnetsun.cn/news/7765.html

相关文章:

  • YOLOv8-Ultralytics 系列文章目录
  • 自动化运维工程师之ansible启动rpcbind和nfs服务
  • 数字供应链系统哪个好?2025 供应链系统推荐排名来了,八大供应链系统
  • M.I.B.终极指南:解锁汽车娱乐系统的隐藏功能
  • 把 ABAP CDS 讲清楚:从 ABAP 7.40 SP05 的语义建模,到 SP08 的函数、参数化与扩展视图
  • 终极PHP兼容性检查工具:轻松应对版本迁移挑战
  • Kamailio usrloc 细节测试
  • 探索STM32单片机仿真温湿度采集控制系统
  • MediaPipe实时多模态感知:从单点检测到全身协同追踪的技术革命
  • SMDJ33A单向 TVS瞬态抑制二极管 :33V电压000W 浪涌,中压电路防护核心
  • MCP 2025量子编程认证重大升级(新增内容全曝光)
  • Bottles:让Windows软件在Linux上轻松运行的智能解决方案
  • 日志框架问答整理(吊打面试官)
  • 从零到安全工程师:2025年必备技能树详解(附实战学习蓝图)
  • Komikku:免费开源的Android漫画阅读器终极指南
  • 长耗时接口异步改造总结
  • 解码人类智慧密码——贾子五定律(Kucius Five Laws):贾子认知、历史、战略、军事、文明五定律
  • 启点创新智慧景区小程序系统,景区智能化售票系统,景区购票管理系统
  • 3种快速安装readr数据读取工具的方法:从入门到精通
  • 对比实测:传统vs自动化VMware安装,效率提升300%
  • 跨平台字体革命:PingFangSC字体包的终极解决方案
  • 14 类圣诞核心 SVG 交互方案拆解(附案例 + 资源)
  • 7个技巧轻松搞定Node.js版本升级:从16.x到20.x的无痛迁移指南
  • MCP SC-400配置避坑手册(一线专家亲授10大常见错误)
  • Ghost没落、同行消失,深度却靠国产系统翻盘?关键点不止一个!
  • 5分钟掌握PROPKA:蛋白质pKa预测的终极入门指南
  • dotNetFx40_Full_x86_x64:解决Windows开发环境配置难题的终极方案
  • 终极解决方案:如何快速解除Cursor试用限制
  • PMail个人邮件服务器:3步搭建私有邮箱的完整指南
  • 阿里自研Wan2.2-T2V-A14B如何实现720P高清视频生成?