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:35:51 阅读更多 →
Unity Shader LOD技术详解:动态性能优化与多级着色器切换实战

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

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

2026/7/31 14:35:51 阅读更多 →
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:35:51 阅读更多 →

最新新闻

盘锦高三综合评价报考,家长怎么少走弯路?

盘锦高三综合评价报考,家长怎么少走弯路?

这两年我身边不少盘锦高三家长,一聊到综合评价就有点懵: “听说能多一条路,但到底适不适合我家孩子?” “材料要准备什么,时间节点会不会错过?” “是不是只看成绩,还是还要看活动、奖项、面试&…

2026/7/31 17:49:03 阅读更多 →
南宁五象福朋喜来登-邕城商务差旅优选

南宁五象福朋喜来登-邕城商务差旅优选

经常来南宁五象新区跑业务,试过周边好几家酒店,现在出差基本锁定这家福朋喜来登。对商务人来说,住宿不用追求花里胡哨,省心、方便最重要,聊聊这次入住真实感受。1.位置交通酒店就在五象总部基地这边,跑客户…

2026/7/31 17:49:03 阅读更多 →
HarmonyOS应用开发实战:猫猫大作战-LiveViewKit 的使用【apple_product_name】

HarmonyOS应用开发实战:猫猫大作战-LiveViewKit 的使用【apple_product_name】

HarmonyOS应用开发实战:猫猫大作战-LiveViewKit 的使用【apple_product_name】 前言 欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net 猫猫大作战的直播间玩法依赖 LiveViewKit——把玩家棋盘实时画面推流给观众,观…

2026/7/31 17:49:03 阅读更多 →
一篇让你明白整个网络架构的计算机网络入门教程。

一篇让你明白整个网络架构的计算机网络入门教程。

引言 很多朋友在学习计算机网络时,一上来就会被教材或者网课老师灌输各种概念协议,学习完后会非常很懵逼,各层的协议到底有什么用,我在一台手机的应用程序上发送消息给另外一台手机相应的应用程序过程中,发生了什么事情…

2026/7/31 17:48:02 阅读更多 →
AI赋能城市治理:3步构建实时响应系统,错过这波升级将落后5年

AI赋能城市治理:3步构建实时响应系统,错过这波升级将落后5年

更多请点击: https://codechina.net 第一章:AI赋能城市治理:3步构建实时响应系统,错过这波升级将落后5年 城市治理正从“经验驱动”迈向“数据智能驱动”的临界点。当交通拥堵预警延迟超过90秒、突发事件处置平均耗时超12分钟、公…

2026/7/31 17:48:02 阅读更多 →
大模型 API / Token 检测工具怎么选?别被中转站坑了,这份实测指南请收好

大模型 API / Token 检测工具怎么选?别被中转站坑了,这份实测指南请收好

花了100块的API,跑出来的是10块模型的水平——这事你遇到过吗? 对接大模型API,尤其是用中转/代理接口时,有几个问题绕不开:这个Key还能用吗?返回的模型对不对?有没有被降级或“掺水”&#xff…

2026/7/31 17:48:02 阅读更多 →

日新闻

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

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

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

2026/7/31 0:00:34 阅读更多 →
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:34 阅读更多 →
有哪些游戏数据AI平台?游戏行业Data+AI融合方案盘点

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

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

2026/7/31 0:00:34 阅读更多 →

周新闻

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

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

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

2026/7/31 1:03:03 阅读更多 →
深度学习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/31 4:19:39 阅读更多 →

月新闻