Python全栈实战 Day 3:FastAPI用户注册、密码加密与JWT登录认证
Python全栈实战 Day 3用户注册与JWT登录认证前言这是“10天Python全栈项目实战”系列的第3篇。前两天已经完成FastAPI与Vue3项目搭建第一个前后端接口调用MySQL数据库连接SQLAlchemy用户模型创建。今天将在现有项目中实现完整的后端用户认证流程用户注册密码哈希用户登录生成JWT Token根据Token获取当前用户。注意数据库中不能直接保存用户明文密码只保存经过密码哈希算法处理后的结果。一、安装认证依赖进入后端项目目录并激活虚拟环境cd backend .venv\Scripts\activate安装依赖pip install pyjwt pwdlib[argon2] python-dotenv email-validator各依赖的作用依赖作用PyJWT生成和解析JWTpwdlib密码哈希与校验argon2实际使用的密码哈希算法python-dotenv读取环境变量email-validator校验邮箱格式FastAPI当前官方JWT教程使用PyJWT处理Token并使用pwdlib处理密码。PasswordHash.recommended()目前默认选择Argon2。更新依赖文件pip freeze requirements.txt二、调整项目结构本文按照以下后端结构编写backend ├── app │ ├── api │ │ ├── __init__.py │ │ └── auth.py │ ├── core │ │ ├── __init__.py │ │ └── security.py │ ├── models │ │ ├── __init__.py │ │ └── user.py │ ├── schemas │ │ ├── __init__.py │ │ └── user.py │ ├── __init__.py │ ├── database.py │ └── main.py ├── .env ├── .gitignore └── requirements.txtdatabase.py和models/user.py应该已经在Day 2中创建。三、检查用户数据模型打开app/models/user.py确认用户表至少包含以下字段from datetime import datetime from sqlalchemy import Boolean, DateTime, String, func from sqlalchemy.orm import Mapped, mapped_column from app.database import Base class User(Base): __tablename__ users id: Mapped[int] mapped_column( primary_keyTrue, indexTrue, ) username: Mapped[str] mapped_column( String(30), uniqueTrue, indexTrue, nullableFalse, ) email: Mapped[str] mapped_column( String(100), uniqueTrue, indexTrue, nullableFalse, ) hashed_password: Mapped[str] mapped_column( String(255), nullableFalse, ) is_active: Mapped[bool] mapped_column( Boolean, defaultTrue, nullableFalse, ) created_at: Mapped[datetime] mapped_column( DateTime, server_defaultfunc.now(), nullableFalse, )这里保存的是hashed_password而不是password如果Day 2创建的表没有hashed_password字段需要修改数据模型和数据库表。开发阶段没有重要数据时可以删除旧表后重新创建已有数据时应使用数据库迁移或执行对应的ALTER TABLE语句不能直接删表。四、创建请求和响应模型创建app/schemas/user.py写入from pydantic import BaseModel, ConfigDict, EmailStr, Field class UserRegister(BaseModel): username: str Field( min_length3, max_length30, patternr^[a-zA-Z0-9_]$, ) email: EmailStr password: str Field( min_length8, max_length128, ) class UserLogin(BaseModel): username: str password: str class TokenResponse(BaseModel): access_token: str token_type: str bearer class UserResponse(BaseModel): id: int username: str email: EmailStr is_active: bool model_config ConfigDict(from_attributesTrue)这几个模型分别负责UserRegister校验注册参数UserLogin校验登录参数TokenResponse定义登录成功的返回内容UserResponse定义返回给前端的用户信息。不要把hashed_password放进UserResponse否则可能将密码哈希结果返回给前端。五、配置JWT密钥在项目根目录创建.env先生成随机密钥python -c import secrets; print(secrets.token_hex(32))终端会输出一段随机字符串将它复制进.envSECRET_KEY替换成刚才生成的随机字符串 ACCESS_TOKEN_EXPIRE_MINUTES30创建或检查.gitignore.venv/ __pycache__/ *.pyc .env.env中包含JWT签名密钥不应该提交到公开代码仓库。六、实现密码加密和JWT生成创建app/core/security.py写入import os from datetime import datetime, timedelta, timezone import jwt from dotenv import load_dotenv from pwdlib import PasswordHash load_dotenv() SECRET_KEY os.getenv(SECRET_KEY) ALGORITHM HS256 ACCESS_TOKEN_EXPIRE_MINUTES int( os.getenv(ACCESS_TOKEN_EXPIRE_MINUTES, 30) ) if not SECRET_KEY: raise RuntimeError(没有配置SECRET_KEY请检查.env文件) password_hash PasswordHash.recommended() def hash_password(password: str) - str: 将明文密码转换为密码哈希。 return password_hash.hash(password) def verify_password( plain_password: str, hashed_password: str, ) - bool: 验证明文密码是否与密码哈希匹配。 return password_hash.verify( plain_password, hashed_password, ) def create_access_token( subject: str, expires_delta: timedelta | None None, ) - str: 生成JWT访问令牌。 expire datetime.now(timezone.utc) ( expires_delta if expires_delta is not None else timedelta(minutesACCESS_TOKEN_EXPIRE_MINUTES) ) payload { sub: subject, exp: expire, } return jwt.encode( payload, SECRET_KEY, algorithmALGORITHM, ) def decode_access_token(token: str) - dict: 校验并解析JWT访问令牌。 return jwt.decode( token, SECRET_KEY, algorithms[ALGORITHM], )JWT中保存{ sub: 用户ID, exp: 过期时间, }其中sub表示Token对应的主体exp表示Token过期时间。PyJWT在执行jwt.decode()时会校验expToken过期后会抛出相应异常解析时还应显式限制允许使用的签名算法。七、实现注册接口创建app/api/auth.py先写入注册功能import jwt from fastapi import ( APIRouter, Depends, HTTPException, status, ) from fastapi.security import ( HTTPAuthorizationCredentials, HTTPBearer, ) from sqlalchemy import select from sqlalchemy.orm import Session from app.core.security import ( create_access_token, decode_access_token, hash_password, verify_password, ) from app.database import get_db from app.models.user import User from app.schemas.user import ( TokenResponse, UserLogin, UserRegister, UserResponse, ) router APIRouter( prefix/api/auth, tags[用户认证], ) bearer_scheme HTTPBearer(auto_errorFalse) router.post( /register, response_modelUserResponse, status_codestatus.HTTP_201_CREATED, ) def register( data: UserRegister, db: Session Depends(get_db), ) - User: username_exists db.scalar( select(User).where( User.username data.username ) ) if username_exists: raise HTTPException( status_codestatus.HTTP_409_CONFLICT, detail用户名已经存在, ) email_exists db.scalar( select(User).where( User.email data.email ) ) if email_exists: raise HTTPException( status_codestatus.HTTP_409_CONFLICT, detail邮箱已经被注册, ) user User( usernamedata.username, emaildata.email, hashed_passwordhash_password(data.password), ) try: db.add(user) db.commit() db.refresh(user) except Exception: db.rollback() raise HTTPException( status_codestatus.HTTP_500_INTERNAL_SERVER_ERROR, detail用户注册失败, ) return user注册接口的主要流程接收注册参数 ↓ 检查用户名是否存在 ↓ 检查邮箱是否存在 ↓ 对密码执行哈希 ↓ 写入MySQL ↓ 返回用户基本信息八、实现登录接口继续在auth.py中加入router.post( /login, response_modelTokenResponse, ) def login( data: UserLogin, db: Session Depends(get_db), ) - TokenResponse: user db.scalar( select(User).where( User.username data.username ) ) if user is None: raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detail用户名或密码错误, ) if not verify_password( data.password, user.hashed_password, ): raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detail用户名或密码错误, ) if not user.is_active: raise HTTPException( status_codestatus.HTTP_403_FORBIDDEN, detail当前账号已被禁用, ) access_token create_access_token( subjectstr(user.id) ) return TokenResponse( access_tokenaccess_token, )无论用户名不存在还是密码错误都统一返回用户名或密码错误这样可以减少接口向外部暴露账号是否存在的信息。登录成功后返回{ access_token: 生成的JWT字符串, token_type: bearer }九、获取当前登录用户继续加入Token验证依赖def get_current_user( credentials: HTTPAuthorizationCredentials | None Depends( bearer_scheme ), db: Session Depends(get_db), ) - User: if credentials is None: raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detail请先登录, headers{WWW-Authenticate: Bearer}, ) try: payload decode_access_token( credentials.credentials ) user_id payload.get(sub) if user_id is None: raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detail无效的登录凭证, ) user_id_int int(user_id) except ( jwt.InvalidTokenError, ValueError, TypeError, ): raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detail登录凭证无效或已经过期, headers{WWW-Authenticate: Bearer}, ) user db.get(User, user_id_int) if user is None: raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detail用户不存在, ) if not user.is_active: raise HTTPException( status_codestatus.HTTP_403_FORBIDDEN, detail当前账号已被禁用, ) return user然后创建受保护接口router.get( /me, response_modelUserResponse, ) def get_me( current_user: User Depends(get_current_user), ) - User: return current_user访问/api/auth/me时必须携带Authorization: Bearer JWT_TOKEN否则接口会返回401。十、注册认证路由打开app/main.py加入认证路由from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.api.auth import router as auth_router app FastAPI( title10天Python全栈项目实战, version1.0.0, ) app.add_middleware( CORSMiddleware, allow_origins[ http://localhost:5173, http://127.0.0.1:5173, ], allow_credentialsTrue, allow_methods[*], allow_headers[*], ) app.include_router(auth_router) app.get(/) def read_root() - dict[str, str]: return { message: FastAPI服务运行成功, }启动项目uvicorn app.main:app --reload打开接口文档http://127.0.0.1:8000/docs十一、测试注册接口找到POST /api/auth/register测试参数{ username: owen, email: owenexample.com, password: Python123456 }成功后返回{ id: 1, username: owen, email: owenexample.com, is_active: true }建议在这里放两张截图Swagger注册成功MySQL用户表新增数据。查看数据库时hashed_password应该是一段密码哈希而不是Python123456十二、测试登录接口找到POST /api/auth/login提交{ username: owen, password: Python123456 }成功后获得{ access_token: 一段JWT字符串, token_type: bearer }复制access_token备用。十三、测试登录用户接口找到GET /api/auth/me点击Swagger页面右上角的“Authorize”输入Bearer 你的JWT_TOKEN部分Swagger页面只需要填写Token本身不需要手动写Bearer以页面提示为准。认证成功后执行GET /api/auth/me应该返回{ id: 1, username: owen, email: owenexample.com, is_active: true }再测试以下情况不携带Token随便修改Token等Token过期后再次请求。这些情况都应该返回401而不是正常获取用户信息。十四、常见报错1. 提示找不到jwt执行pip install pyjwt注意安装包名称是PyJWT代码导入方式是import jwt2. 提示需要安装email-validator执行pip install email-validator因为Pydantic的EmailStr依赖该组件验证邮箱。3. 提示没有配置SECRET_KEY检查.env是否位于后端项目根目录backend/.env并确认内容为SECRET_KEY真实生成的随机密钥修改.env后重新启动FastAPI。4. 用户表没有hashed_password字段说明数据库结构仍然是Day 2的旧版本。需要更新表结构或在没有重要数据的开发环境中删除旧表后重新创建。5. 正确密码也登录失败重点检查注册时是否调用了hash_password()数据库保存的是否为哈希结果登录时是否使用verify_password()是否手动修改过数据库中的密码字段。6. Token刚生成就提示无效重点检查生成和解析Token是否使用同一个SECRET_KEY使用的算法是否都是HS256是否重启项目后更换了随机密钥.env是否正确加载。十五、Day 3验收清单完成以下检查说明Day 3已经通过注册接口能够创建用户用户名不能重复邮箱不能重复密码少于8位时校验失败数据库没有保存明文密码正确账号密码可以登录错误密码无法登录登录成功可以获得JWT Token携带Token可以访问/api/auth/me不携带Token时返回401伪造或过期Token无法通过认证十六、下一篇内容Day 4将完成Vue3登录页面、Axios请求封装、Token保存和路由守卫。下一篇主要包括创建登录页面调用FastAPI登录接口保存JWT TokenAxios自动携带Token未登录用户自动跳转登录页登录后获取当前用户信息。

相关新闻

3分钟快速上手:用pkNX打造专属Switch宝可梦世界

3分钟快速上手:用pkNX打造专属Switch宝可梦世界

3分钟快速上手:用pkNX打造专属Switch宝可梦世界 【免费下载链接】pkNX Pokmon (Nintendo Switch) ROM Editor & Randomizer 项目地址: https://gitcode.com/gh_mirrors/pk/pkNX 想要个性化你的Switch宝可梦游戏体验吗?pkNX是一款功能强大的宝…

2026/7/31 14:36:07阅读更多 →
Unity Shader LOD技术详解:动态性能优化与多级着色器切换实战

Unity Shader LOD技术详解:动态性能优化与多级着色器切换实战

1. 项目概述:Shader LOD 到底是什么?在 Unity 开发中,尤其是面向移动端或需要兼顾性能与画质的项目,我们常常面临一个经典的两难选择:是追求极致的视觉效果,还是保证流畅的运行帧率?Shader LOD&…

2026/7/31 14:36:07阅读更多 →
Ryujinx模拟器:5步快速上手,在PC上畅玩Switch游戏的完整指南

Ryujinx模拟器:5步快速上手,在PC上畅玩Switch游戏的完整指南

Ryujinx模拟器:5步快速上手,在PC上畅玩Switch游戏的完整指南 【免费下载链接】Ryujinx 用 C# 编写的实验性 Nintendo Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/ry/Ryujinx 想在电脑上体验《塞尔达传说:旷野之息》…

2026/7/31 14:36:07阅读更多 →
直流照明|城市非机动车道低碳安全照明解决方案

直流照明|城市非机动车道低碳安全照明解决方案

一.系统概述城市非机动车道、人行绿道是市民日常通勤、慢行休闲的核心场景,具有人流量大、夜间通行频次高、紧邻绿化积水区域等特点。现阶段多数非机动车道沿用传统交流路灯,存在低压区照度不足、雨天漏电隐患、整夜常亮能耗浪费、无法适配行人车流变化等…

2026/7/31 15:42:41阅读更多 →
怀旧手游MMORPG《荣耀出征》正版官方客户端下载指引,九神怀旧游戏正规安全渠道指南

怀旧手游MMORPG《荣耀出征》正版官方客户端下载指引,九神怀旧游戏正规安全渠道指南

首领试炼是《荣耀出征》手游贯穿新手开荒、中期成型、后期毕业的核心单人专属打宝玩法,区别于野外世界BOSS多人争抢、掉落内卷的模式,首领试炼为独立专属副本,所有挑战奖励完全独享,不存在抢归属、分奖励的情况,是平民…

2026/7/31 15:42:40阅读更多 →
AI流程优化不是选择题,而是生存线:IDC最新报告揭示——6个月内未部署智能工作流的企业将面临23%协作衰减

AI流程优化不是选择题,而是生存线:IDC最新报告揭示——6个月内未部署智能工作流的企业将面临23%协作衰减

更多请点击: https://intelliparadigm.com 第一章:AI流程优化不是选择题,而是生存线 在当今技术迭代加速的商业环境中,AI流程优化已不再是锦上添花的“可选项”,而是决定组织响应速度、成本韧性与决策质量的“生存底线…

2026/7/31 15:42:40阅读更多 →
【AI学习进度黑洞预警系统】:基于时间序列异常检测的早期偏离识别框架(实测提前5.3天预警)

【AI学习进度黑洞预警系统】:基于时间序列异常检测的早期偏离识别框架(实测提前5.3天预警)

更多请点击: https://intelliparadigm.com 第一章:AI学习进度跟踪 AI学习进度跟踪是构建可持续成长路径的关键环节,它不仅反映知识吸收效率,更揭示模型理解深度与实践转化能力。有效的跟踪机制需融合量化指标、阶段性产出验证与反…

2026/7/31 15:42:40阅读更多 →
实用免安装科研工具合集汇总 助力科研工作高效便捷开展无需折腾安装流程

实用免安装科研工具合集汇总 助力科研工作高效便捷开展无需折腾安装流程

做科研最耗人的,从来不是难题本身,而是检索、整理、写作、分析里的重复劳动——2026年,一批更精准、更贴合科研全流程的AI工具已成熟,能帮你把时间还给思考。本文实测7款全新工具,覆盖文献检索、阅读、写作、数据分析、…

2026/7/31 15:42:40阅读更多 →
中老年带货新玩法:AI文案+克隆配音批量起号

中老年带货新玩法:AI文案+克隆配音批量起号

# 中老年带货新玩法:AI文案克隆配音批量起号## 引言视频号中老年用户群体已达3亿以上,消费力强、信任感高,成为带货蓝海。然而,传统团队面临**三大痛点**:文案产出慢、真人配音成本高、单个账号起号周期长。本文将介绍…

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

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

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

2026/7/30 15:03:16阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

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

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

2026/7/30 12:22:27阅读更多 →
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/30 15:13:02阅读更多 →
物理复制比逻辑复制好在哪?数据库复制原理详解

物理复制比逻辑复制好在哪?数据库复制原理详解

数据库复制是把主库数据同步到备库的机制,分为逻辑复制和物理复制两种。逻辑复制传输的是 SQL 语句或行变更事件,物理复制传输的是存储引擎底层的物理日志。阿里云 PolarDB(云原生数据库)采用物理复制,在同步延迟、数据…

2026/7/31 0:00:40阅读更多 →
BilibiliDown:3分钟学会B站视频下载的终极指南

BilibiliDown:3分钟学会B站视频下载的终极指南

BilibiliDown:3分钟学会B站视频下载的终极指南 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader 😳 项目地址: https://gitcode.com/gh_mirrors/bi/Bilib…

2026/7/31 0:00:41阅读更多 →
有哪些游戏数据AI平台?游戏行业Data+AI融合方案盘点

有哪些游戏数据AI平台?游戏行业Data+AI融合方案盘点

当前,游戏行业的“DataAI融合”已从概念验证进入价值落地阶段。根据IDC 2025年数据,中国AI游戏云市场规模已达18.6亿元;同时,游戏研发环节AI渗透率高达86%,生成式AI内容普及率超过50%。面对庞大的市场,游戏…

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

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

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

2026/7/31 0:49:33阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

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

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

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

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

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

2026/7/30 15:43:46阅读更多 →