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:30阅读更多 →
二手手机管理系统全栈开发实践与优化

二手手机管理系统全栈开发实践与优化

1. 项目背景与核心价值华强北作为全国最大的电子产品集散地,每天有数万部二手手机在市场流通。传统的Excel表格管理方式已经无法满足商户对库存周转率、质检标准化和价格波动的实时管控需求。这套系统正是针对二手手机行业特有的"一机一况"管理痛点设计的…

2026/7/29 16:41:30阅读更多 →
A5000与STM32G474RE实现物联网安全云连接方案

A5000与STM32G474RE实现物联网安全云连接方案

1. 项目概述:基于A5000与STM32G474RE的安全云连接方案 在物联网设备爆炸式增长的今天,如何确保终端设备与云端通信的安全性已成为开发者面临的核心挑战。NXP的A5000安全芯片与STMicroelectronics的STM32G474RE微控制器组合,为解决这一难题提供…

2026/7/29 16:41:30阅读更多 →
DBeaver数据库管理工具:5步打造高效数据管理环境

DBeaver数据库管理工具:5步打造高效数据管理环境

DBeaver数据库管理工具:5步打造高效数据管理环境 【免费下载链接】Ryujinx 用 C# 编写的实验性 Nintendo Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/ry/Ryujinx 你是否经常在不同数据库系统之间切换,为每个数据库安装独立的客…

2026/7/29 18:03:44阅读更多 →
Pixelle-Video AI短视频生成终极指南:一键生成专业短视频

Pixelle-Video AI短视频生成终极指南:一键生成专业短视频

Pixelle-Video AI短视频生成终极指南:一键生成专业短视频 【免费下载链接】Pixelle-Video 🚀 AI 全自动短视频引擎 | AI Fully Automated Short Video Engine 项目地址: https://gitcode.com/GitHub_Trending/pi/Pixelle-Video 你是否曾梦想制作专…

2026/7/29 18:03:44阅读更多 →
拯救格式焦虑✨Paperxie智能一键排版|论文告别扣分、告别熬夜调格式

拯救格式焦虑✨Paperxie智能一键排版|论文告别扣分、告别熬夜调格式

官方入口🌐:https://www.paperxie.cn 写论文最磨人的从来不是写内容!而是无穷无尽的格式bug😭 很多同学论文内容写得超棒,逻辑完整、重复率达标,最后却栽在细节格式上:标题层级混乱、参考文献…

2026/7/29 18:03:44阅读更多 →
GenAura 简曜:打造「专属驻场品牌专家」,让营销决策更自主、更智能

GenAura 简曜:打造「专属驻场品牌专家」,让营销决策更自主、更智能

行业思考:流量竞赛之后,品牌价值谁来守护? 作为企业抢占AI时代的新流量入口,GEO(生成式引擎优化)曾掀起过一阵狂热的行业风暴。但在喧嚣过后,当下的市场似乎陷入了某种“技术内卷”——从业者的…

2026/7/29 18:03:44阅读更多 →
Django-Vue3-Admin升级指南:从旧版本迁移到最新版

Django-Vue3-Admin升级指南:从旧版本迁移到最新版

Django-Vue3-Admin升级指南:从旧版本迁移到最新版 【免费下载链接】django-vue3-admin Django-Vue3-Admin is a comprehensive basic development platform based on the RBAC (Role-Based Access Control) model for permission control, with column-level granul…

2026/7/29 18:03:44阅读更多 →
GraphQL 网关架构升级:从单体 Schema 到联邦查询的渐进迁移与兼容性保障

GraphQL 网关架构升级:从单体 Schema 到联邦查询的渐进迁移与兼容性保障

GraphQL 网关架构升级:从单体 Schema 到联邦查询的渐进迁移与兼容性保障 一、引言 GraphQL 网关在微服务架构中承担着数据聚合与字段级权限控制的关键角色。当后端服务数量增长至一定规模(通常超过 5 个)时,单体 Schema 的维护成本…

2026/7/29 18:01:44阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/29 9:47:45阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/29 7:00:19阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/29 7:58:51阅读更多 →
28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“! 在构建复杂的 Agent 系统时,我们经常会遇到这样的场景:Agent 正在执行一个多步骤的任务,比如“下单购买商品”,但执行到一半时,我们…

2026/7/29 0:01:46阅读更多 →
自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

近日,国际专注开放式技术研发的声学品牌Nank南卡,正式官宣实力艺人曾舜晞担任品牌代言人。消息一经发出便轰动全网。为什么耳机品牌不选择流量明星、老牌歌手?而且是选择曾舜晞?让我们一起来探索一下!比起短期的流量&a…

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

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

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

2026/7/29 0:01:46阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/28 20:22:24阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/29 4:31:51阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/29 14:26:42阅读更多 →