☰
FastAPI登录验证实战:OAuth2密码模式+JWT签发与校验全解析
2026/10/3 4:46:07 网站建设 项目流程

先讲个我亲眼见过的事情。有个朋友用FastAPI写后端接口,登录验证完全是自己造的轮子:用户登录成功后,往数据库里塞一条uuid当token,前端每次请求带过来,后端就先查一次表,比对成功才放行。结果接口一上量,数据库连接先被打满;更麻烦的是部署多节点之后,在这台机器登录的token,请求打到另一台机器就查不到,直接变成未登录。他跑来问我怎么解决,我反问他为什么不用OAuth2加JWT,他有点犹豫地说:"感觉那是大厂才用的东西,我们这种小项目用不上吧。"

其实这个想法很有代表性。很多人觉得OAuth2和JWT听着高大上,实际自己写项目时就用最朴素的方式糊一个登录,等流量起来或者服务拆分之后,才被一堆隐藏问题追着跑。这篇文章就围绕FastAPI登录验证这件事展开:从OAuth2密码模式的基本原理,到JWT令牌的签发、校验、续签和实战排坑,完整梳理一套可以直接落地的API安全防线方案。不管是刚接触FastAPI的新手,还是已经写了几个接口但没系统搞过认证的开发者,都可以照着这篇文章的思路把登录验证这块补齐,而且能真正理解每一步在做什么、为什么要这样做。

1. 为什么登录验证必须用OAuth2和JWT,而不是自己手写Session表

很多人第一次接触登录验证,脑子里冒出来的方案就是"搞个session表":登录成功后生成一个随机字符串,存到数据库或者Redis里,以后再请求就带着这个字符串,服务端查一下就知道是谁了。这种方案在小项目、单体应用里确实能跑,但它有几个先天问题。

首先是存储和查询的压力。每个请求都要做一次"查token是否存在"的操作,接口并发一高,这部分查询就成了瓶颈。其次是分布式环境下的会话共享问题。服务拆成多个节点之后,用户请求可能被负载均衡打到任意一台机器上,如果token状态只存在某一台机器的内存或某个数据库里,就得额外引入Redis这样的集中式存储,架构复杂度直接上升。第三是token本身不携带任何信息,服务端必须"记住"它,这本质上是一种有状态的设计。

JWT恰恰解决了这三个问题。它是一个自包含的令牌,用户是谁、权限是什么、什么时候过期,全部写在令牌本身的Payload里边。服务端拿到JWT之后,只需要用密钥验证一下签名,确认这个令牌确实是自己签发的、没有被篡改、没有过期,就可以直接信任里面的信息,完全不需要查数据库。这就是"无状态认证"的核心含义。用一个生活化的类比:session方案像你去健身房办卡,每次进门工作人员都要去电脑里查一下你的开卡记录;而JWT方案像你拿了一张盖了钢印的通行证,门口保安只要检查钢印是真的、日期没过期,就能直接放行。

那OAuth2又是干什么的?OAuth2本身是一个授权框架,和"登录"这件事不完全划等号,但它定义了一套标准的"客户端换令牌"流程。最常见的是授权码模式,比如你用微信登录某个网站;而FastAPI官方文档里推荐的场景是OAuth2的密码模式(Resource Owner Password Credentials),也就是客户端直接拿用户名密码向认证服务换一个access_token,专门适用于前后端分离的API场景。FastAPI之所以把OAuth2和JWT放在一起讲,是因为它内置的OAuth2PasswordBearer依赖项,可以帮你自动完成"从请求头里提取Bearer Token"这件事,你再把JWT的签发和验证逻辑挂上去,一套标准的认证链路就闭合了。

为了看清这套组合的定位,我通常会把三种常用方案放在一起对比:

  • Session/Cookie:状态在服务端,需要集中存储,适合传统Web应用,不适合纯API和跨服务场景。
  • JWT Bearer Token:状态在令牌本身,服务端无状态校验,适合前后端分离、微服务、API开放平台。
  • API Key:一个固定字符串代表一个调用方,适合机器对机器、第三方开放接口,不适合承载用户身份和权限细节。

回到开头那个朋友的场景:他用uuid当token存数据库,实际上就是自己实现了一套残缺的Session方案,但缺少了集中存储和过期策略,所以一上量就出问题。换成OAuth2密码模式加JWT之后,登录接口负责签发令牌,受保护接口只负责验签,数据库不再参与每一次请求的认证过程,整个设计清爽很多。

2. 环境准备与用户密码存储:别在第一步就把安全搞砸

方向定了,接下来是动手前的准备。FastAPI登录验证涉及到的核心依赖有这么几个:FastAPI和Uvicorn是基础运行环境,然后需要python-jose来做JWT的编码解码,需要passlib来做密码哈希,还需要python-multipart来解析OAuth2密码模式里的表单数据。如果你用数据库存用户信息,那再加上SQLAlchemy和对应数据库驱动。

我见过不少教程推荐用PyJWT,但FastAPI官方文档用的是python-jose,两者都能做JWT,不过python-jose支持的算法更全,和FastAPI的配合也最顺。安装方式很简单:

pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt] python-multipart

这里有个细节值得注意:python-jose的依赖项里带上[cryptography],是为了支持RS256这类非对称签名算法。如果只用HS256,理论上不装也能跑,但带着它不会有副作用,我建议一律装上,免得以后扩展算法时踩坑。

另一件必须在动手前做好的事情,是准备一个足够安全的SECRET_KEY。这个密钥用于给JWT签名,一旦泄露,任何人都可以伪造登录令牌。网上很多教程直接在代码里写SECRET_KEY = "your-secret-key",如果你只是本地学习无所谓,但如果部署到生产环境,密钥必须放到环境变量或者密钥管理服务里,绝不能硬编码进代码仓库。生成方式用系统自带工具就行:

openssl rand -hex 32

这条命令会生成一个64位的十六进制字符串,随机性足够。然后在代码里读取环境变量:

import os SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-key-change-me") ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30

再来说说密码存储。这是整个登录验证里最容易被轻视的一环。直接存明文密码属于底线问题,不用讨论;稍微有点概念的人会用sha256加密,但这同样不够。普通哈希算法的问题在于速度太快,攻击者拿到数据库之后,可以用彩虹表快速反查,也可以每秒跑上亿次暴力破解。正确的做法是用专门为密码设计的慢哈希算法,比如bcrypt,它内部会自动加盐,并且可以通过调节cost参数控制计算耗时。加盐的意思是每个用户同一密码生成的哈希都不一样,彩虹表直接失效;变慢则让暴力破解的成本高到不划算。

在FastAPI项目里,我一般这样封装密码工具:

from passlib.context import CryptContext pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def hash_password(password: str) -> str: return pwd_context.hash(password) def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password)

这段代码放到security.py或者core/security.py里,后面注册用户和登录校验都会用到。deprecated="auto"的意思是,如果以后切换到更安全的哈希算法,passlib会在验证旧哈希时自动识别并给出警告提示,方便平滑迁移。

实际开发中还有一个高频坑:passlib的版本和bcrypt的版本兼容问题。如果你pip安装之后发现pwd_context.hash()报错,提示AttributeError: module 'bcrypt' has no attribute '__about__',通常是因为passlib 1.7.4和bcrypt 4.1以上的版本不兼容。解决办法是把bcrypt降到3.2.x,或者直接改用bcrypt库自己的接口。我在项目里为了避免这类麻烦,新项目已经倾向于直接用bcrypt库了,但为了贴合FastAPI官方文档的写法,这篇文章仍然采用passlib加bcrypt的组合,遇到报错按我刚才说的处理即可。

用户表的设计也值得说两句。最简的User模型至少要有id、username、hashed_password、is_active这几个字段。注意,表里存的是hashed_password,而不是password;整个项目里任何地方都不应该出现明文密码的存储。注册接口拿到用户输入的密码后,第一时间调用hash_password(),再落入数据库。登录校验的时候,从数据库取出该用户的hashed_password,用verify_password()和用户提交的明文密码比对,返回布尔值。

数据库会话的管理,FastAPI社区的标准做法是用Depends(get_db)依赖注入。这里不展开讲,但你得保证后面的authenticate_user函数能够拿到数据库会话,否则登录校验无从谈起。

3. OAuth2PasswordBearer:FastAPI帮你自动提取令牌的那根接缝

FastAPI里所有认证相关的魔法,都建立在依赖注入系统之上。所谓依赖注入,简单说就是你在路由函数里声明一个参数,FastAPI会在调用路由之前,自动去帮你把参数准备好。OAuth2PasswordBearer就是一个现成的依赖项,它的职责只有一个:从请求的Authorization请求头里提取Bearer令牌字符串。

创建它的方式非常简单:

from fastapi.security import OAuth2PasswordBearer oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

这里的tokenUrl="token",指的是客户端将来要拿着用户名密码去换取令牌的接口地址。如果你的登录接口路径是/auth/login,那这里就要写成tokenUrl="/auth/login"。这个参数有两个作用:一是让FastAPI在生成OpenAPI文档时,知道去哪申请令牌,Swagger文档右上角会多出一个"Authorize"按钮,点进去输入用户名密码就能直接测试受保护接口;二是让OAuth2的客户端实现知道该往哪发请求。

那oauth2_scheme到底做了什么?它首先检查请求头里有没有Authorization字段,如果字段值是Bearer xxxxx这种形式,它就把xxxxx提取出来,作为依赖项的值传给路由函数。如果请求头缺失,或者格式不对,它直接抛出一个401异常。但请注意,它只负责"提取",不负责"验证"。也就是说,哪怕你随便写一串乱码放进Authorization头,oauth2_scheme也能把它提取出来,至于这串token是不是真的、有没有过期,那是后面JWT校验环节的事。

这种"提取和验证分离"的设计,恰好是FastAPI依赖注入优雅的地方。你可以把认证拆成几个层次:第一层是提取Bearer Token,第二层是验证JWT签名,第三层是从token里解析出用户信息。每层都是一个独立的依赖项,按需组合。比如有的接口可能只需要"确实是咱家签发的token"就行,而有的接口需要"必须是某个具体用户",还有的接口需要"必须是管理员"。这种粒度控制,手写中间件反而很难做到。

我再强调一下密码模式和另一种常见模式的区别。OAuth2规范里除了密码模式,还有授权码模式,后者是浏览器网页登录时用的,比如"使用GitHub账号登录",需要跳转到授权页面、用户手动点击确认。而API场景下,前端是我们自己的应用,用户直接在登录页输入用户名密码,不需要跳转,所以用密码模式最直接。FastAPI官方文档把这两者区分得很清楚,密码模式只适用于"客户端和资源服务是同一方"的可信场景,如果你做的是开放平台、允许第三方应用接入,那就得研究授权码模式了,不能生搬硬套。

开发的时候,建议把OAuth2PasswordBearer实例放在一个公共模块里,因为很多文件都要引用它。比如放在core/security.py,或者单独建一个dependencies.py。这样后续在多个路由里Depends(oauth2_scheme)的时候,只需要import一次,不会出现重复创建导致tokenUrl不一致的混乱局面。

4. 登录接口与JWT签发:把用户名密码变成一张一次性通行证

有了提取令牌的机制,接下来就要实现真正的登录接口。在这一步,OAuth2密码模式带来了一个看似不起眼、但很关键的细节:客户端提交用户名和密码时,用的不是JSON,而是标准表单格式(application/x-www-form-urlencoded)。这是OAuth2规范的规定。FastAPI为此专门提供了OAuth2PasswordRequestForm这个依赖类,你只需要在路由参数里声明它,FastAPI就会自动从请求体里解析出username和password两个字段。

登录接口的完整写法如下:

from datetime import timedelta from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from pydantic import BaseModel class Token(BaseModel): access_token: str token_type: str @app.post("/token", response_model=Token) async def login_for_access_token( form_data: OAuth2PasswordRequestForm = Depends() ): user = authenticate_user(form_data.username, form_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="用户名或密码错误", headers={"WWW-Authenticate": "Bearer"}, ) access_token = create_access_token(data={"sub": user.username}) return {"access_token": access_token, "token_type": "bearer"}

这里有几个细节值得展开。authenticate_user是我封装的一个函数,内部先按用户名查库,查到用户后用verify_password比对密码,两个条件都满足才返回用户对象。这个函数里最忌讳的是给攻击者提供"用户名存在但密码不对"这种信息差,所以无论用户不存在还是密码错误,都统一返回同样的提示,也就是代码里的"用户名或密码错误"。这种模糊化处理在安全实践中属于基本素养。

headers={"WWW-Authenticate": "Bearer"}这行很容易被忽略,但它是OAuth2规范的一部分,作用是告诉客户端"这个接口需要Bearer认证,请带上Authorization头"。很多API调试工具依赖这个响应头来提示开发者,加上它,401响应才更加标准。

然后是create_access_token函数。JWT的生成逻辑集中在函数里,方便复用和测试。一个典型的实现长这样:

from datetime import datetime, timedelta, timezone from jose import jwt def create_access_token(data: dict, expires_delta: timedelta | None = None): to_encode = data.copy() if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else: expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt

JWT本身由三部分组成,用点号分隔:Header(算法信息)、Payload(声明数据)、Signature(签名)。HS256签名算法的工作原理,是用同一个密钥对"Header + Payload"这两段内容做HMAC运算,生成签名。服务端拿到token后,用同样的密钥重新计算一遍签名,如果和token上的签名一致,说明内容没有被篡改。这种对称签名的好处是计算快、实现简单,坏处是签发和验证必须在同一个信任域内,秘密就一把钥匙。如果你有多个服务都需要验证token,且不是所有服务都适合持有这把密钥,那就该考虑RS256非对称签名了,公钥给别人验证、私钥留在认证中心签发。这是后话,但理解了这个区别,你以后架构演进时不会走错方向。

JWT的Payload里可以放很多信息,但有两个标准字段必须注意。一个是sub(Subject),用来标识用户主体,我习惯放用户名;另一个是exp(Expiration Time),令牌过期时间。FastAPI官方示例中把过期时间设置成30分钟,这个默认值背后是有考量的:如果过期时间太长,token泄露后的风险窗口太大;如果太短,用户频繁重新登录,体验又不好。我一般建议15到30分钟,但这只是access_token的过期策略,关于续签我们后面专门讲。

还有一个非常常见的误区:有些人以为JWT是加密的,所以敢把密码、身份证号等敏感信息放进Payload。实际上JWT的Payload只是Base64Url编码,任何人拿到token都能轻松解码看到里面内容,只不过他们改不了,因为没有签名密钥。所以Payload里只能放非敏感信息,比如用户ID、用户名、角色代码,所有的敏感数据都应当通过受保护接口去数据库查询。

生成Token之后,前端怎么存储又是一个绕不开的问题。最安全的做法是把token放在内存变量里,页面刷新就没了,但用户体验差;很多人选择放localStorage,简单方便,但XSS攻击一打到就能偷走。折中方案是把token放在HttpOnly的Cookie里,由后端设置,配合CSRF防护。这个话题展开又是一篇文章,但核心原则是:不要把token放在URL参数里,也不要让token轻易被JavaScript读取到。这一点设计的时候就要想好,后面改起来伤筋动骨。

5. get_current_user全程链路:从一坨字符串到"当前用户对象"

登录接口签发完token,受保护接口就得会验证token。验证逻辑不能在每个路由里重复写,FastAPI的解法依然是依赖注入——封装一个get_current_user依赖函数,谁要保护就声明一个参数。这是整套认证体系里最核心的编排环节。

一个完整的get_current_user长这样:

from fastapi import Depends, HTTPException, status from jose import JWTError, jwt def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无法验证凭据", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username = payload.get("sub") if username is None: raise credentials_exception except JWTError: raise credentials_exception user = get_user_by_username(db, username) if user is None: raise credentials_exception return user

这个函数的执行链路,我拆开解释一下。

第一步,token: str = Depends(oauth2_scheme)这一行是整条链的入口。FastAPI先调用oauth2_scheme从请求头提取Bearer Token的字符串,然后把它传给get_current_user。如果请求头里压根没有Authorization字段,到这里就直接返回401了,后续代码根本不会执行。

第二步,jwt.decode()用同样的密钥和算法去验证token。这一步做了几件事:检查签名是否有效,检查exp是否过期,最后把Payload解出来。注意algorithms=[ALGORITHM]不能省略,否则jose库可能根据token头里的信息选择算法,产生安全风险。

第三步,从Payload里取出sub字段,再根据这个用户名去数据库查询用户记录。这里有个看起来很矛盾的环节:我们前面刚说了JWT是无状态的、不需要查库,为什么拿到token之后又要查一次数据库?原因在于JWT只能证明"这个令牌确实是咱家签发的、内容没被改过",但token签发之后,用户可能被删除了、被禁用了、角色变了,这些状态变化JWT是感知不到的。所以对于"需要实时用户状态"的接口,查一次库是必要的。如果完全不查库,相当于token一旦签发就成了铁券,哪怕用户被管理员踢出去,他手里的token也一直有效,这是很多项目出安全事故的根源之一。

第四步,用户不存在或者已经被删除,跟token无效一样,统一抛401。不要把"token过期"和"用户不存在"区分成两种错误,让攻击者通过这些细节去探测系统内部状态。

有了get_current_user,受保护路由写起来非常舒服。比如"获取当前用户信息"接口:

@app.get("/users/me") async def read_users_me(current_user: User = Depends(get_current_user)): return current_user

就一行依赖注入,FastAPI会先执行get_current_user,把返回的User对象传给current_user参数,路由函数内部可以直接使用。如果认证失败,请求根本到不了路由函数内部,直接被401拦截。这种链路的清晰度,是手写装饰器或者中间件很难达到的。

你还可以在这个基础上做权限控制。比如加一个get_current_active_user,里面先调用get_current_user,再检查user.is_active是否为True,False就抛403。这样"谁能访问"和"谁已登录"就能分层控制。FastAPI的依赖注入还支持嵌套依赖,只要一个依赖函数里声明了另一个依赖,链路就会自动展开。在线生成OpenAPI文档里,每个受保护接口都会显示一个带锁的标记,点进去就能用登录时拿到的token去测试,整个开发体验相当顺滑。

依赖注入里还有一个容易被忽略的机制:FastAPI默认会缓存依赖的计算结果。同一个请求里,如果多个路由或者嵌套依赖都用到了get_current_user,它只会执行一次,后面的直接复用结果。这个设计对性能很友好,但如果你的认证逻辑里包含随机数检查之类快速变化的状态,就要小心缓存的干扰。不过对于JWT验证这类幂等操作,完全不用担心。

6. 实战中的高频坑位排查,以及令牌续签与登出的进阶方案

文章写到这个份上,光讲"怎么做"还不够,我把自己在实际项目里踩过的坑和排查思路一并分享出来,顺序是:先排查401问题,再解决时区坑,然后设计续签方案,最后说登出问题。

首先是401排查链路。我见过太多人报"unexpected status 401 unauthorized",但真正的原因五花八门。接到这类问题,我总是按下面这个顺序排查:

  • 检查请求头格式。必须是Authorization: Bearer xxxxx,注意"Bearer"和token之间有一个空格,Bearer首字母大写,缺一不可。前端代码里写错一个字母就全盘401。
  • 检查token是否真的拿到了。如果登录接口返回的是{access_token: ...},前端有没有正确地把这个字段存起来并塞进下一次请求的Header里,很多前端小白的错误在这里。
  • 确认解密算法一致。签发token时用HS256,验证时也要指定HS256,不要token头里写着什么就跟什么走。
  • 检查密钥是否一致。本地开发用了一个SECRET_KEY,部署到服务器换了一个,旧token自然全部失效,这是最让人抓狂的一种情况。
  • 检查时间是否正确。JWT的exp机制依赖服务器时间,如果服务器时钟偏差太大,token会莫名其妙"提前过期"。生产环境务必配置NTP时间同步。

其次是时区坑。JWT的过期时间必须用UTC,千万别用本地时间。网上很多老教程写datetime.utcnow(),但Python 3.12之后这玩意deprecated了,更规范的写法是我的代码示例里的datetime.now(timezone.utc)。这里的核心不是用哪个函数,而是保证签发和验证都在同一个时间基准上。如果签发用本地时间、验证用UTC时间,token的有效期就会产生偏移,用户一脸懵地看着"刚登录就过期"。

第三是密钥轮换。SECRET_KEY不是终身不变的,如果怀疑泄露或者团队人员变动,就要轮换密钥。最简单是双密钥方案:验证时新旧密钥都试一遍,签发时只用新密钥。这样旧token在有效期内还能验证通过,新token立即生效,用户无感迁移。等到旧密钥签发的一批token全部过期,再彻底移除旧密钥。

接下来是令牌续签。JWT的exp一旦过期就不可用了,但让用户每隔30分钟重新输一次密码,体验很糟糕。业界常用的方案有两种:滑动过期和刷新令牌。

滑动过期的思路是:每次用户发起请求并成功验证之后,如果token剩余有效期不足某个阈值,就顺便签发一个新token返回给前端。前端收到新token就替换旧的,用户只要在持续使用,token就永远不会断。优点是不用引入额外接口,缺点是无状态服务自己没法主动"续签",所以实际实现时需要额外存储或者让客户端参与。

刷新令牌是我更推荐的方式:登录时签发两个token,一个是短期有效的access_token,比如30分钟,用来访问API;一个长期有效的refresh_token,比如7天,只能用来调"刷新"接口换新的access_token。access_token过期之后,前端拿着refresh_token去/token/refresh接口换新token,全程用户无感知。refresh_token需要有撤销能力,服务端通常要存储它的状态,所以它是有状态的。设计上,refresh_token通过sub加上一个随机jti标识存储在数据库或Redis里,一旦登出或者涉嫌复用,立刻把它标记失效。为什么代码里我签access_token时只放{"sub": user.username}?如果要做refresh_token方案,还要再加上{"jti": uuid4().hex},并且带上token_type字段区分是access还是refresh。

最后的难题是登出。JWT无状态导致服务端无法主动让一个已经签发的token失效,常见的解法是把access_token的有效期缩短到分钟级,同时保证refresh_token支持撤销。如果你想在access_token层面做到"立即失效",那就需要引入黑名单机制,把被登出的token的jti存起来,验证时先查黑名单。这会让认证重新变回有状态,所以绝大多数轻量项目采用"短access_token + 可撤销refresh_token"的组合,在安全性、体验、复杂度之间取一个平衡。我在实际项目中的体会是:不要一上来就追求"完美"的黑名单机制,小项目先保证access_token短过期、refresh_token能撤销,已经能覆盖绝大部分真实场景。

权限控制方面,FastAPI的OAuth2PasswordBearer还支持scopes参数,可以在token里声明权限范围,比如{"sub": "alice", "scopes": ["admin"]},然后在具体路由里用Security依赖检查scope。这个机制比单纯判断"是否登录"更精细,适合做多角色系统。不过如果你的权限模型只是简单的用户/管理员两级,那直接在用户模型上加个is_admin字段,配合get_current_user查库,反而更简单直观,不必为了用scope而用scope。

实际开发中还有一个体验相关的小坑:很多人在登录接口返回的token_type直接硬编码成"bearer",前端拼Authorization头时又自己写死"Bearer "。大小写不敏感还好,万一遇到严格校验的库就会出问题。建议前后端都统一按规范来,响应里的token_type就用"bearer",前端拼头的时候用响应字段而不是硬编码。

说到底,登录验证不是一个可以拿螺丝刀随便拧的环节,它决定了你整个API的门卫系统靠不靠谱。把OAuth2的流程规范、JWT的签发验证、密码哈希的存储原则这套东西弄明白之后,不管接口规模怎么变、服务怎么拆分,认证这块的骨架都是稳的。最后再分享一个我个人的操作习惯:项目里所有HTTPException的状态码和错误信息,我会统一收拾到一个常量模块里,比如AUTH_REQUIRED = (401, "身份验证已失效,请重新登录"),路由里引用常量而不是散落各处,排错的时候能少翻很多代码。希望你照着这篇文章搭完认证体系之后,也能顺手把这类基础设施级别的整洁度做起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询