FastAPI JWT 登录认证实战:Access Token、Refresh Token 与权限保护
在前后端分离项目中用户登录后如何保持身份状态是后端开发必须解决的问题。传统 Web 项目经常使用 Session用户登录成功后服务器保存会话数据并向浏览器返回 Session ID。但在移动端、多服务部署和前后端分离场景中JWTJSON Web Token是一种更常见的身份认证方案。本文将使用 FastAPI 实现一套基础的 JWT 登录认证机制内容包括用户密码验证Access Token 的生成与解析Refresh Token 的设计接口身份保护Token 过期与刷新退出登录与 Token 撤销实际项目中的安全注意事项。一、JWT 是什么JWT 是一种可以在客户端和服务器之间传递声明信息的 Token 格式。一个 JWT 通常由三部分组成Header.Payload.Signature例如eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiIxMDAxIiwiZXhwIjoxNzIwMDAwMDAwfQ . xxxxxxxxxxxxxxxx三部分分别表示Header使用的签名算法和 Token 类型Payload用户 ID、过期时间、Token 类型等声明Signature服务器根据密钥生成的签名。JWT 的 Payload 只是经过 Base64URL 编码并不是加密内容。因此不应该在其中保存密码、身份证号、聊天内容等敏感信息。JWT 签名的主要作用是防止内容被篡改而不是隐藏内容。二、JWT 登录认证的基本流程一次完整的 JWT 登录过程如下用户提交账号和密码 ↓ 服务器验证用户信息 ↓ 生成 Access Token 和 Refresh Token ↓ 客户端保存 Token ↓ 请求接口时携带 Access Token ↓ 服务器验证签名和有效期 ↓ 允许或拒绝访问客户端一般通过请求头携带 TokenAuthorization: Bearer access_tokenAccess Token 过期后客户端可以使用 Refresh Token 获取新的 Access Token而不需要用户立即重新输入密码。三、安装项目依赖安装 FastAPI、JWT 和密码哈希相关依赖pip install fastapi uvicorn pyjwt bcrypt项目可以先使用一个文件演示jwt_demo/ └── main.py启动命令uvicorn main:app --reload四、不要明文保存用户密码用户密码不能直接保存到数据库中应该保存密码经过哈希计算后的结果。可以使用bcrypt处理密码import bcrypt def hash_password(password: str) - str: password_bytes password.encode(utf-8) hashed bcrypt.hashpw( password_bytes, bcrypt.gensalt(), ) return hashed.decode(utf-8) def verify_password( plain_password: str, hashed_password: str, ) - bool: return bcrypt.checkpw( plain_password.encode(utf-8), hashed_password.encode(utf-8), )注册用户时保存哈希结果password_hash hash_password( example_password )用户登录时通过verify_password()判断输入密码是否正确。密码哈希与普通加密不同。系统不需要还原用户的原始密码只需要验证用户本次输入是否与之前保存的密码一致。五、配置 JWT 密钥JWT 签名密钥不能直接写死在代码仓库中应该通过环境变量读取import os JWT_SECRET os.environ[JWT_SECRET] JWT_ALGORITHM HS256启动服务前配置环境变量export JWT_SECRET请替换成足够长的随机字符串 uvicorn main:app --reload生产环境中的密钥应该具有足够的随机性不提交到 Git 仓库不直接输出到日志定期进行安全检查通过密钥管理服务或安全配置系统保存。如果密钥泄露攻击者就可能伪造合法 Token。六、生成 Access TokenAccess Token 用于访问需要登录的接口其有效时间通常较短。from datetime import ( datetime, timedelta, timezone, ) from uuid import uuid4 import jwt ACCESS_TOKEN_EXPIRE_MINUTES 30 def create_access_token(user_id: int) - str: now datetime.now(timezone.utc) payload { sub: str(user_id), type: access, iat: now, exp: now timedelta( minutesACCESS_TOKEN_EXPIRE_MINUTES ), jti: str(uuid4()), } return jwt.encode( payload, JWT_SECRET, algorithmJWT_ALGORITHM, )这里使用了几个常见字段字段含义subToken 对应的用户typeToken 类型iatToken 签发时间expToken 过期时间jtiToken 的唯一编号建议使用 UTC 时间处理 Token 有效期减少不同服务器时区产生的问题。七、生成 Refresh TokenRefresh Token 只用于申请新的 Access Token其有效期通常更长REFRESH_TOKEN_EXPIRE_DAYS 7 def create_refresh_token(user_id: int) - str: now datetime.now(timezone.utc) payload { sub: str(user_id), type: refresh, iat: now, exp: now timedelta( daysREFRESH_TOKEN_EXPIRE_DAYS ), jti: str(uuid4()), } return jwt.encode( payload, JWT_SECRET, algorithmJWT_ALGORITHM, )虽然两种 Token 都使用 JWT 格式但必须通过type字段进行区分。如果后端没有检查 Token 类型攻击者可能把有效期较长的 Refresh Token 当成 Access Token 使用从而绕过原本的有效期设计。八、实现用户登录接口首先定义请求和响应模型from pydantic import BaseModel class LoginRequest(BaseModel): username: str password: str class TokenResponse(BaseModel): access_token: str refresh_token: str token_type: str bearer为了简化示例使用字典模拟数据库fake_users { demo: { id: 1001, username: demo, password_hash: hash_password( 123456 ), is_active: True, } }实现登录接口from fastapi import FastAPI, HTTPException app FastAPI() app.post( /auth/login, response_modelTokenResponse, ) def login(request: LoginRequest): user fake_users.get(request.username) if not user: raise HTTPException( status_code401, detail用户名或密码错误, ) if not verify_password( request.password, user[password_hash], ): raise HTTPException( status_code401, detail用户名或密码错误, ) if not user[is_active]: raise HTTPException( status_code403, detail用户已被禁用, ) return TokenResponse( access_tokencreate_access_token( user[id] ), refresh_tokencreate_refresh_token( user[id] ), )无论用户名不存在还是密码错误接口都返回相同提示可以避免向外部暴露账号是否存在。九、解析和验证 TokenFastAPI 提供了OAuth2PasswordBearer可以从请求头中提取 Bearer Tokenfrom fastapi.security import OAuth2PasswordBearer oauth2_scheme OAuth2PasswordBearer( tokenUrl/auth/login )然后实现当前用户解析逻辑from fastapi import Depends, HTTPException from jwt import ( ExpiredSignatureError, InvalidTokenError, ) def get_current_user_id( token: str Depends(oauth2_scheme), ) - int: try: payload jwt.decode( token, JWT_SECRET, algorithms[JWT_ALGORITHM], ) if payload.get(type) ! access: raise HTTPException( status_code401, detailToken 类型错误, ) user_id payload.get(sub) if not user_id: raise HTTPException( status_code401, detailToken 缺少用户信息, ) return int(user_id) except ExpiredSignatureError: raise HTTPException( status_code401, detailToken 已过期, ) except ( InvalidTokenError, TypeError, ValueError, ): raise HTTPException( status_code401, detail无效的 Token, )jwt.decode()会验证签名和过期时间。验证失败时接口应该返回401 Unauthorized。十、保护需要登录的接口有了get_current_user_id()就可以通过依赖注入保护接口app.get(/users/me) def get_current_user( user_id: int Depends( get_current_user_id ), ): return { user_id: user_id, message: 身份验证成功, }客户端请求时必须携带 Access Tokencurl \ -H Authorization: Bearer access_token \ http://localhost:8000/users/me如果 Token 不存在、签名错误或已经过期服务器会拒绝访问。十一、实现 Token 刷新接口定义刷新请求模型class RefreshRequest(BaseModel): refresh_token: str刷新接口需要确认传入的是 Refresh Tokenapp.post(/auth/refresh) def refresh_access_token( request: RefreshRequest, ): try: payload jwt.decode( request.refresh_token, JWT_SECRET, algorithms[JWT_ALGORITHM], ) if payload.get(type) ! refresh: raise HTTPException( status_code401, detailToken 类型错误, ) user_id payload.get(sub) if not user_id: raise HTTPException( status_code401, detailToken 缺少用户信息, ) return { access_token: create_access_token( int(user_id) ), token_type: bearer, } except ExpiredSignatureError: raise HTTPException( status_code401, detailRefresh Token 已过期, ) except ( InvalidTokenError, TypeError, ValueError, ): raise HTTPException( status_code401, detail无效的 Refresh Token, )实际项目中刷新时还应该检查用户是否仍然存在用户是否已被禁用Refresh Token 是否被撤销用户密码是否已经修改当前设备是否仍然可信。不能只验证 JWT 签名后就无条件签发新 Token。十二、退出登录为什么不能只删除前端 TokenJWT 的特点之一是服务器可以不保存会话状态。这也意味着只要 Token 没有过期服务器通常就会认为它有效。用户在前端点击退出登录只是删除了当前设备保存的 Token并不能让已经泄露的 Token 立即失效。如果业务要求退出后立即失效可以使用 Redis 保存 Token 黑名单。退出时将 Access Token 的jti写入 Redis并设置与 Token 剩余有效期相同的过期时间jwt:blacklist:jti 1验证 Token 时检查jti payload.get(jti) if redis_client.exists( fjwt:blacklist:{jti} ): raise HTTPException( status_code401, detailToken 已失效, )当 Token 自然过期后对应的黑名单记录也可以自动删除。另一种方案是保存用户的 Token 版本号。修改密码、退出所有设备或封禁账号时提高版本号让之前签发的 Token 全部失效。十三、同言翻译场景中的身份认证设计对于具有个人账号、历史会话和跨设备使用需求的应用身份认证不仅关系到接口能否访问也关系到用户数据是否会被错误读取。以同言翻译为例用户可能需要查看自己的翻译记录、管理术语配置或在不同设备间同步会话。后端接口需要通过 Access Token 确认请求者身份并在查询数据时同时校验资源归属关系。下面这种写法只根据会话 ID 查询数据存在越权风险app.get(/sessions/{session_id}) def get_session(session_id: int): return query_session(session_id)即使接口要求登录用户仍然可能通过修改session_id访问其他人的会话。更安全的方式是同时使用当前用户 ID 查询app.get(/sessions/{session_id}) def get_session( session_id: int, user_id: int Depends( get_current_user_id ), ): session query_user_session( user_iduser_id, session_idsession_id, ) if not session: raise HTTPException( status_code404, detail会话不存在, ) return session对于同言翻译这类涉及语音、文本和会话内容的应用仅验证“用户是否登录”是不够的还必须验证“当前用户是否有权访问这份数据”。此外可以为敏感操作增加更严格的安全措施例如修改密码前重新验证身份导出历史记录时进行二次确认Refresh Token 按设备分别管理异常登录后使旧 Token 失效对重要接口记录安全审计日志不在 JWT 中保存翻译原文或会话内容。十四、Access Token 应该保存在哪里不同客户端需要采用不同的 Token 保存策略。浏览器应用常见方式包括内存变量HttpOnly CookiesessionStoragelocalStorage。将 Token 保存到localStorage实现简单但如果页面存在 XSS 漏洞恶意脚本可能读取 Token。使用HttpOnly Cookie可以阻止 JavaScript 直接读取 Cookie但需要额外处理 CSRF、防跨站请求和 Cookie 安全属性。如果使用 Cookie通常应该合理配置HttpOnly Secure SameSite没有一种方案适合所有项目需要结合前端架构、跨域方式和安全要求进行选择。移动端应用移动端应使用系统提供的安全存储能力不建议把 Token 直接保存在普通配置文件或明文数据库中。十五、生产环境中的安全建议1. Access Token 不要设置得过长Access Token 有效期越长泄露后的风险持续时间越长。可以使用短期 Access Token 长期 Refresh Token在用户体验和安全性之间取得平衡。2. Refresh Token 应支持撤销Refresh Token 的有效期较长一旦泄露攻击者可能不断申请新的 Access Token。生产环境中可以在数据库或 Redis 中保存 Refresh Token 的状态并记录Token 唯一编号所属用户登录设备签发时间过期时间是否已经撤销。3. 使用 HTTPS如果使用明文 HTTPToken 可能在传输过程中被截获。生产环境必须使用 HTTPSWebSocket 则应该使用wss://。4. 不要在日志中记录完整 Token排查问题时可以记录 Token 的jti、用户 ID或部分摘要但不应该输出完整 Token。5. 为登录接口增加限流攻击者可能持续尝试不同密码因此登录接口应该增加IP 限流账号维度限流连续失败次数限制验证码或其他人机验证异常登录告警。6. 修改密码后撤销旧 Token如果用户修改密码但旧 Token 仍然可以继续使用那么已经泄露的 Token 不会自动失效。可以通过 Token 版本号、黑名单或会话记录实现统一撤销。十六、JWT 常见误区误区一JWT 中的数据是加密的JWT Payload 通常只是编码任何获得 Token 的人都可以解析其中内容。误区二使用 JWT 就完全不需要服务器状态如果需要退出登录、设备管理、Token 撤销和风险控制服务器仍然可能需要保存部分状态。误区三Refresh Token 可以访问业务接口Refresh Token 只能用于刷新身份凭证。业务接口必须检查 Token 类型只接受 Access Token。误区四只要签名正确就代表用户可以访问所有数据签名正确只能说明 Token 是服务器签发的。具体资源是否属于当前用户仍然需要在业务层进行权限校验。误区五JWT 可以替代所有权限系统JWT 负责传递身份信息但角色权限、资源权限、数据归属和操作范围仍然需要单独设计。十七、总结使用 FastAPI 实现 JWT 登录认证核心流程包括对用户密码进行安全哈希登录成功后生成 Access Token 和 Refresh Token客户端通过 Bearer Token 访问接口服务器验证签名、类型和过期时间Access Token 过期后使用 Refresh Token 更新对重要接口进行资源归属和权限检查通过黑名单或 Token 版本实现主动撤销。JWT 能够让前后端分离项目更方便地传递身份信息但它并不是“生成一个字符串”这么简单。真正可靠的认证系统还需要综合考虑密钥管理、Token 存储、退出登录、设备管理、越权访问、接口限流和安全审计。身份认证解决的是“你是谁”权限校验解决的是“你能做什么”。只有同时做好这两部分才能真正保护用户数据和系统接口。

相关新闻

VLA 已经能输出动作,为什么机器人仍需要多时间尺度闭环

VLA 已经能输出动作,为什么机器人仍需要多时间尺度闭环

VLA 已经能输出动作,为什么机器人仍需要多时间尺度闭环 TL;DR 场景:端到端 VLA 可以共享视觉、语言与动作表示、减少人工设计的中间接口,但不能取消传感器时钟、机械动力学、关节限位、网络故障、控制稳定性和事故责任。结论:可…

2026/7/29 16:41:03 阅读更多 →
HarmonyOS应用开发实战:猫猫大作战-PushKit 的接收与处理

HarmonyOS应用开发实战:猫猫大作战-PushKit 的接收与处理

前言 PushKit 是 HarmonyOS 的远程推送服务,支持服务端向用户设备发送推送消息。对于游戏应用,远程推送可用于活动通知(“周末双倍积分!”)、好友邀请(“你的好友超过了你的得分!”&#xff09…

2026/7/29 16:40:03 阅读更多 →
为什么90%的AI路径系统在雨天/晚高峰崩盘?资深LBS架构师首度公开时空约束建模的6个隐性变量

为什么90%的AI路径系统在雨天/晚高峰崩盘?资深LBS架构师首度公开时空约束建模的6个隐性变量

更多请点击: https://codechina.net 第一章:AI 配送路线优化 在城市物流日益复杂的背景下,AI 驱动的配送路线优化正成为提升时效性与降低碳排放的关键技术。传统基于规则或简单启发式算法的路径规划难以应对实时交通变化、动态订单涌入及多约…

2026/7/29 16:40:03 阅读更多 →

最新新闻

Unlock Music:5分钟快速解锁加密音乐的终极免费方案

Unlock Music:5分钟快速解锁加密音乐的终极免费方案

Unlock Music:5分钟快速解锁加密音乐的终极免费方案 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库: 1. https://github.com/unlock-music/unlock-music ;2. https://git.unlock-music.dev/um/web 项目地址: https:/…

2026/7/29 16:50:07 阅读更多 →
别把大模型 API Key 写进 APK:移动 AI 应用接口防盗刷实践

别把大模型 API Key 写进 APK:移动 AI 应用接口防盗刷实践

别把大模型 API Key 写进 APK:移动 AI 应用接口防盗刷实践 移动 AI 应用要防止接口被盗刷,第一原则不是把长期 API Key 换一种藏法,而是让不可信的客户端不再持有长期主密钥。更可落地的做法是:客户端只申请短期、受范围约束的访…

2026/7/29 16:50:07 阅读更多 →
LinkSwift:解锁九大网盘下载自由的专业级解决方案

LinkSwift:解锁九大网盘下载自由的专业级解决方案

LinkSwift:解锁九大网盘下载自由的专业级解决方案 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云盘…

2026/7/29 16:50:07 阅读更多 →
消息队列选型:Kafka、Pulsar 和 NATS 在 AI 场景下的差异

消息队列选型:Kafka、Pulsar 和 NATS 在 AI 场景下的差异

消息队列选型:Kafka、Pulsar 和 NATS 在 AI 场景下的差异 一、AI 工作流对消息队列的特殊要求:不止"发出去就行" 传统业务系统的消息队列关注点是吞吐量和可靠性——订单消息不能丢、支付消息要精确一次。但 AI 场景有自己特殊的需求维度&…

2026/7/29 16:50:07 阅读更多 →
计算机毕业设计之基于springboot的二手图书交易系统设计与实现

计算机毕业设计之基于springboot的二手图书交易系统设计与实现

信息技术是当今社会发展的重要方向之一,它已经深入到各个行业中。随着计算机技术的发展,信息技术已经从传统的数据处理转变为网络信息的处理和交互。在管理方面,通过信息管理技术,系统可以快速的处理大量的数据,并且能…

2026/7/29 16:50:07 阅读更多 →
084、LVGL日历日期选择与事件

084、LVGL日历日期选择与事件

LVGL日历日期选择与事件 一段让人抓狂的调试经历 上个月做智能家居中控屏,客户要求日历控件能高亮显示有预约的日期,点击日期弹出对应日程。我心想LVGL的日历控件(lv_calendar)不是现成的吗?结果一跑起来,点击日期没反应,高亮日期全乱套,回调函数里拿到的日期永远是1…

2026/7/29 16:49:07 阅读更多 →

日新闻

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:00:23 阅读更多 →
AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础 在上一期「AI编程系列」中,我们学习了如何构建一个基础的 AI 问答系统,通过简单的输入输出让模型回应问题。但现实世界中的 AI 应用往往需要处理更复杂的场景:…

2026/7/29 0:00:23 阅读更多 →
AI智能体开发实战:从工具调用到企业级部署

AI智能体开发实战:从工具调用到企业级部署

1. 从被动问答到主动执行:AI Agent的范式转变过去两年,大语言模型最显著的应用形态是聊天机器人——用户提问,AI回答。但真正的生产力革命发生在2023年下半年:当AI学会主动调用工具完成任务时,生产力工具的历史被彻底改…

2026/7/29 0:00:23 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/28 12:04:22 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/29 14:34:28 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/29 15:00:03 阅读更多 →

月新闻