ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FastAPI 安全实战:OAuth2 密码流 + Argon2 密码哈希 + PyJWT Bearer 令牌完整实现

FastAPI 安全实战:OAuth2 密码流 + Argon2 密码哈希 + PyJWT Bearer 令牌完整实现 FastAPI 安全实战OAuth2 密码流 Argon2 密码哈希 PyJWT Bearer 令牌完整实现【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi本文将带你基于 FastAPI 官方安全教程构建一套真正可投入生产的认证体系使用pwdlibArgon2对密码做单向哈希存储使用PyJWT签发与校验带过期时间的 JWT 访问令牌并通过OAuth2PasswordBearer 依赖注入把“当前登录用户”安全地注入到每个受保护接口。读完你就能在自己的 FastAPI 应用里复刻这套“用户名/密码 → 令牌 → 受保护接口”的完整闭环并能对照本仓库源码与测试用例理解每一层的底层原理。1. 教程在安全系列中的位置本节内容位于 FastAPI 官方文档安全教程的收官位置它建立在前三章之上first-steps.md介绍OAuth2PasswordBearer建立oauth2_scheme OAuth2PasswordBearer(tokenUrltoken)安全方案simple-oauth2.md引入OAuth2PasswordRequestForm实现/token端点与get_current_user/get_current_active_user依赖——但当时密码只是“假哈希”fakehashed password令牌就是用户名本身完全不安全get-current-user.md把 token 从str提升为 PydanticUser模型并注入路径操作函数本节 oauth2-jwt.md把“假哈希/假令牌”升级为Argon2 密码哈希 JWT 签名令牌让整套流程真正可用于生产。对应上一章的完整示例见 tutorial003_an_py310.py其中fake_hash_password与fake_decode_token都明确标注了“This doesnt provide any security at all / Check the next version”这正是本节要替换的部分。本仓库中本节完整的可运行源码是 tutorial004_an_py310.py还有不带Annotated写法的 tutorial004_py310.py配套测试位于 test_tutorial004.py。本文所有代码均以该源文件为准。2. 关于 JWTJSON Web TokensJWT 是“JSON Web Tokens”的缩写是一套把一个 JSON 对象编码进一长串无空格的稠密字符串的标准。一个典型的 JWT 长这样eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c该字符串由句点分隔为三个 Base64Url 片段分别对应片段内容说明Header{alg:HS256,typ:JWT}声明签名算法此处为 HS256Payload{sub:1234567890,name:John Doe,iat:1516239022}携带声明claims如主体sub、签发时间iatSignature签名值由 HeaderPayload 与密钥计算得到用于防篡改校验理解 JWT 需要抓住两条关键性质它没有加密任何人都能直接解码中间那段载荷、恢复其中的信息。因此不要把密码等机密信息放进 token但它是有签名的当你收到一个自己签发过的 token 时可以验证它确实出自你手。若用户或第三方试图篡改载荷比如把过期时间改长签名将无法匹配服务端能够立刻发现。基于签名机制你可以签发一个“有效期 1 周”的 token第二天用户带着旧 token 回来你知道他仍处于登录态一周后 token 过期用户不再被授权、需要重新登录换取新 token。提示文档本身建议读者去 jwt.io 这类在线工具拆解体验 token 的三段结构。你可以用仓库中生成的真实 token配合PyJWT的jwt.decode(..., options{verify_signature: False})或在线解码工具查看其 Payload。3. 安装PyJWT签发与校验令牌的底层库要在 Python 中生成与校验 JWT需要安装PyJWT。使用uv添加到项目$ uv add pyjwt --- 100%几点补充说明本仓库的 pyproject.toml依赖列表 168–169 行附近将文档示例所需的最低版本固定为pyjwt 2.9.0与该教程一致若你打算使用RSA、ECDSA 等非对称数字签名算法需要额外安装其密码学底层依赖即pyjwt[crypto]本教程采用对称算法HS256签发与校验使用同一个SECRET_KEY因此不需要额外的cryptography依赖安装普通pyjwt即可。在代码中使用到的 API 主要是两个详见后文完整代码jwt.encode(payload_dict, SECRET_KEY, algorithmALGORITHM)签发令牌jwt.decode(token, SECRET_KEY, algorithms[ALGORITHM])校验并解析令牌校验失败签名不符、过期、格式非法等会抛出InvalidTokenError及其子类。4. 密码哈希为什么不能明文存密码“哈希Hashing”是指把某段内容这里就是密码转换为一段看起来像乱码的字节串。它有两条核心特性确定性只要输入完全相同的密码就总是得到完全相同的“乱码”单向性无法从乱码反推出原始密码。为什么要用哈希保存密码假设你的数据库被盗如果存的是明文小偷直接拿到所有用户的真实密码如果存的是哈希小偷拿到的只是一堆不可逆的哈希值。由于大量用户喜欢在不同网站复用同一密码明文泄露意味着攻击者可以把这些密码拿去撞库其他系统造成连锁危害而哈希存储让这种“跨系统撞库”的破坏力被大幅削弱。需要特别强调的是哈希不等于“弱加密”。本节采用的 Argon2 是专门为口令设计的“慢哈希”算法通过引入时间/内存代价来抵御 GPU 暴力破解详见第 5、6 节。5. 安装pwdlib[argon2]密码哈希工具pwdlib是一个优秀的 Python 密码哈希处理包支持多种安全哈希算法及其配套工具。官方推荐算法是Argon2。使用uv安装带 Argon2 支持的版本$ uv add pwdlib[argon2] --- 100%同样地仓库 pyproject.toml 中将其最低版本固定为pwdlib[argon2] 0.2.1。关于算法互操作性的两个重要提示pwdlib还支持 bcrypt但不包含旧式/遗留算法若要读取那些用旧算法如 passlib 生成的 MD5/SHA1 口令等产生的过期哈希官方建议配合passlib使用跨框架共享数据借助pwdlib你可以配置它读取由Django、Flask 安全插件等系统生成的密码哈希。这意味着 FastAPI 与 Django 可以共享同一个数据库中的用户口令数据、支持渐进式迁移——Django 应用与 FastAPI 应用的用户可以同时在两套系统上登录。6. 哈希与校验密码PasswordHash 防计时攻击的“假哈希”技巧核心思路是创建一个全局的PasswordHash实例使用推荐配置 Argon2再由它派生出三个工具函数get_password_hash入库前哈希、verify_password登录时比对、authenticate_user按用户名取用户并校验口令。对应源码见 tutorial004_an_py310.py 的关键片段from pwdlib import PasswordHash # ...其他 import 与模型定义见第 8 节完整代码 password_hash PasswordHash.recommended() DUMMY_HASH password_hash.hash(dummypassword) def verify_password(plain_password, hashed_password): return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): return password_hash.hash(password) def get_user(db, username: str): if username in db: user_dict db[username] return UserInDB(**user_dict) def authenticate_user(fake_db, username: str, password: str): user get_user(fake_db, username) if not user: verify_password(password, DUMMY_HASH) return False if not verify_password(password, user.hashed_password): return False return user逐段解读password_hash PasswordHash.recommended()以推荐参数创建实例。pwdlib的 Argon2 推荐配置落在argon2id变体上。仓库示例数据库中存储的哈希$argon2id$v19$m65536,t3,p4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sBwtDVu/P4uod1Qof8h1g7bbDlBID48Rc即揭示了其参数编码v19为算法版本、m65536内存代价KiB、t3迭代次数、p4并行度get_password_hash注册/改密时对明文密码做哈希产出的字符串含算法标识与随机盐可直接入库verify_password登录时把用户提交的明文与库中哈希比对——PasswordHash.verify会从哈希串解析算法与盐再重新计算因此即使日后换算法旧哈希依然可验证authenticate_user完整登录校验。关键细节在if not user分支——当用户名不存在时它仍会调用一次verify_password(password, DUMMY_HASH)对预先算好的“假哈希”做一次同样开销的比对。这样无论用户名是否合法该端点耗时都大致相同从而阻止攻击者通过响应时间差异timing attack枚举合法用户名。提示注意在代码中任何地方都找不到明文密码secret——数据库里只有fake_users_db[johndoe][hashed_password]这段 Argon2 哈希见上文示例值。secret只在教程演示登录时作为输入出现。7. 处理 JWT 令牌密钥、算法、过期时间与签发函数接下来为令牌部分补充所需配置与工具函数。第一步是生成一个随机安全密钥作为 JWT 签名密钥在终端执行$ openssl rand -hex 32 09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7把输出复制到SECRET_KEY变量——务必用自己生成的新密钥不要使用示例中的那个。对应的配置与工具函数见源码第 4、7、13–15、29–31、82–90 行import jwt from jwt.exceptions import InvalidTokenError # to get a string like this run: # openssl rand -hex 32 SECRET_KEY 09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 ALGORITHM HS256 ACCESS_TOKEN_EXPIRE_MINUTES 30 # ... class Token(BaseModel): access_token: str token_type: str class TokenData(BaseModel): username: str | None None 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(minutes15) to_encode.update({exp: expire}) encoded_jwt jwt.encode(to_encode, SECRET_KEY, algorithmALGORITHM) return encoded_jwt要点解析SECRET_KEYHS256 是对称算法签名与校验共用此密钥。openssl rand -hex 32产生 64 个十六进制字符256 位熵生产环境应放到环境变量/密钥管理服务中严禁硬编码入库ALGORITHM HS256与 JWT Header 中的alg对应jwt.decode(..., algorithms[ALGORITHM])显式指定白名单可防止算法混淆类攻击ACCESS_TOKEN_EXPIRE_MINUTES 30业务层令牌有效期为 30 分钟create_access_token内置兜底为 15 分钟过期时间使用 UTCdatetime.now(timezone.utc)Python 3.11 起也可用datetime.now(UTC)——避免本地时区造成的时间偏差这是签发令牌的通用最佳实践expexpiration是 JWT 注册声明之一PyJWT在校验时会自动比对当前时间TokenData与Token是两个 Pydantic 模型分别承载“解码后的令牌数据”与“/token 响应的载荷结构”。8. 更新依赖让get_current_user真正解码并校验 JWT在简单教程里get_current_user拿到的是str类型的 token并且直接当作用户名用fake_decode_token。现在改为接收同样的 token → 用SECRET_KEY解码 → 提取sub→ 查库返回用户任何一步失败立即抛 401见源码第 93–110 行async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): credentials_exception HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detailCould not validate credentials, headers{WWW-Authenticate: Bearer}, ) try: payload jwt.decode(token, SECRET_KEY, algorithms[ALGORITHM]) username payload.get(sub) if username is None: raise credentials_exception token_data TokenData(usernameusername) except InvalidTokenError: raise credentials_exception user get_user(fake_users_db, usernametoken_data.username) if user is None: raise credentials_exception return user值得注意的三点统一走InvalidTokenErrorPyJWT对过期、签名不匹配、格式错误、字段非法等统统抛出jwt.exceptions.InvalidTokenError及其子类。此处只捕获这一个异常基类即可覆盖所有失败场景WWW-Authenticate: Bearer响应头HTTP 规范要求 401 响应携带WWW-Authenticate头Bearer 方案下其值应为Bearer。虽然省略也能工作但按规范补齐有利于各类 OAuth2 工具链识别token 无sub、用户名查无此人分别走两条分支最终都返回同一个 401 与统一文案Could not validate credentials避免向攻击者泄露“用户名是否存在”的信息。get_current_active_user保持不变作为内层依赖过滤被禁用账号async def get_current_active_user( current_user: Annotated[User, Depends(get_current_user)], ): if current_user.disabled: raise HTTPException(status_code400, detailInactive user) return current_user9. 更新/token路径操作签发真正的 JWT登录端点从“直接回传用户名”改为认证通过后生成带过期时间的timedelta构造{sub: 用户名}载荷调用create_access_token最终返回符合 OAuth2 规范的Token响应模型见源码第 121–136 行app.post(/token) async def login_for_access_token( form_data: Annotated[OAuth2PasswordRequestForm, Depends()], ) - Token: user authenticate_user(fake_users_db, form_data.username, form_data.password) if not user: raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detailIncorrect username or password, headers{WWW-Authenticate: Bearer}, ) access_token_expires timedelta(minutesACCESS_TOKEN_EXPIRE_MINUTES) access_token create_access_token( data{sub: user.username}, expires_deltaaccess_token_expires ) return Token(access_tokenaccess_token, token_typebearer)逐层说明OAuth2PasswordRequestForm负责从application/x-www-form-urlencoded表单中解析username、password还可选解析scope、grant_type、client_id、client_secret它的具体语义在前一章 simple-oauth2.md 中有完整讲解authenticate_user失败统一返回 401 Incorrect username or password不区分是用户名不存在还是密码错误配合第 6 节的假哈希比对从时间与文案两个维度防枚举token 载荷只放入sub用户名——这符合“令牌中不放敏感信息”的 JWT 使用准则返回体用TokenPydantic 模型约束确保 JSON 中一定包含规范要求的access_token与token_type两个键token_type为bearer。受保护的示例接口则保持极简形态全部安全逻辑沉淀在依赖层app.get(/users/me/) async def read_users_me( current_user: Annotated[User, Depends(get_current_active_user)], ) - User: return current_user10. 完整可运行代码汇总将以上各部分拼接后即得到一份完整、可独立运行的示例应用含全部 import 与 Pydantic 模型基于 Python 3.10 的Annotated/X | None语法。该文件即仓库中的 tutorial004_an_py310.pyfrom datetime import datetime, timedelta, timezone from typing import Annotated import jwt from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jwt.exceptions import InvalidTokenError from pwdlib import PasswordHash from pydantic import BaseModel # to get a string like this run: # openssl rand -hex 32 SECRET_KEY 09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 ALGORITHM HS256 ACCESS_TOKEN_EXPIRE_MINUTES 30 fake_users_db { johndoe: { username: johndoe, full_name: John Doe, email: johndoeexample.com, hashed_password: $argon2id$v19$m65536,t3,p4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sBwtDVu/P4uod1Qof8h1g7bbDlBID48Rc, disabled: False, } } class Token(BaseModel): access_token: str token_type: str class TokenData(BaseModel): username: str | None None class User(BaseModel): username: str email: str | None None full_name: str | None None disabled: bool | None None class UserInDB(User): hashed_password: str password_hash PasswordHash.recommended() DUMMY_HASH password_hash.hash(dummypassword) oauth2_scheme OAuth2PasswordBearer(tokenUrltoken) app FastAPI() def verify_password(plain_password, hashed_password): return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): return password_hash.hash(password) def get_user(db, username: str): if username in db: user_dict db[username] return UserInDB(**user_dict) def authenticate_user(fake_db, username: str, password: str): user get_user(fake_db, username) if not user: verify_password(password, DUMMY_HASH) return False if not verify_password(password, user.hashed_password): return False return user 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(minutes15) to_encode.update({exp: expire}) encoded_jwt jwt.encode(to_encode, SECRET_KEY, algorithmALGORITHM) return encoded_jwt async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): credentials_exception HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detailCould not validate credentials, headers{WWW-Authenticate: Bearer}, ) try: payload jwt.decode(token, SECRET_KEY, algorithms[ALGORITHM]) username payload.get(sub) if username is None: raise credentials_exception token_data TokenData(usernameusername) except InvalidTokenError: raise credentials_exception user get_user(fake_users_db, usernametoken_data.username) if user is None: raise credentials_exception return user async def get_current_active_user( current_user: Annotated[User, Depends(get_current_user)], ): if current_user.disabled: raise HTTPException(status_code400, detailInactive user) return current_user app.post(/token) async def login_for_access_token( form_data: Annotated[OAuth2PasswordRequestForm, Depends()], ) - Token: user authenticate_user(fake_users_db, form_data.username, form_data.password) if not user: raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detailIncorrect username or password, headers{WWW-Authenticate: Bearer}, ) access_token_expires timedelta(minutesACCESS_TOKEN_EXPIRE_MINUTES) access_token create_access_token( data{sub: user.username}, expires_deltaaccess_token_expires ) return Token(access_tokenaccess_token, token_typebearer) app.get(/users/me/) async def read_users_me( current_user: Annotated[User, Depends(get_current_active_user)], ) - User: return current_user app.get(/users/me/items/) async def read_own_items( current_user: Annotated[User, Depends(get_current_active_user)], ): return [{item_id: Foo, owner: current_user.username}]使用提示示例中的SECRET_KEY仅供演示请务必执行openssl rand -hex 32自行生成fake_users_db实际使用时替换为真实数据库查询即可——整段安全逻辑与数据层完全解耦。11. JWT 的subsubject字段技术细节JWT 规范定义了一个名为sub的声明表示“令牌主体subject”。它是可选的但正是存放用户标识的标准位置因此本示例把用户名放进sub。sub并不局限于标识“用户”JWT 也可用于标识一辆“汽车”、一篇“博客文章”等实体你可以在载荷中附加针对该实体的权限声明如drive驾驶汽车、edit编辑博客然后把令牌交给某个用户或机器人让对方无需注册账号、仅凭你签发的 JWT 即可对相应资源执行操作。这样 JWT 就能支撑远比“登录认证”复杂的授权场景。而在多实体场景中不同实体可能拥有相同 ID比如用户foo、汽车foo、博客foo都存在。为避免 ID 冲突签发用户令牌时可为sub加前缀命名空间例如username:johndoe——sub的取值在本示例中也可以写成username:johndoe。两个必须牢记的硬性约束sub在整个应用范围内应当是唯一的标识符sub的值必须是字符串。这也解释了为什么get_current_user里要检查payload.get(sub) is None并拒绝缺少sub的令牌任何不携带主体标识的令牌都应视为无效。12. 运行验证在交互式文档中走一遍完整流程启动应用后打开交互式文档地址http://127.0.0.1:8000/docs你会看到包含/token、/users/me/、/users/me/items/三个端点的 API 页面右上角有绿色的Authorize按钮。点击 Authorize 打开授权弹窗输入本教程演示账号的凭据UsernamejohndoePasswordsecret授权完成后调用GET /users/me/由于当前用户已通过 JWT 认证且未被禁用接口返回该用户资料{ username: johndoe, email: johndoeexample.com, full_name: John Doe, disabled: false }注意上面的返回结果来自get_current_user经由 Pydantic 模型返回的用户数据——它不包含hashed_password字段因为返回类型被声明为User而非UserInDBUserInDB才是携带哈希字段的内部模型仅用于查库环节FastAPI 会根据响应模型自动过滤多余字段。如果想观察请求细节可以打开浏览器开发者工具Network 标签再调用一次/users/me/你会看到后续所有请求只携带令牌密码仅在第一次换取 token 的请求中出现此后不再传输注意请求头Authorization的值以Bearer开头——这正是 first-steps.md 中OAuth2PasswordBearer所声明并注入到 OpenAPI 安全方案的 HTTP Bearer 认证形态。若令牌缺失或非法接口会返回 401并附带WWW-Authenticate: Bearer响应头。13. 用仓库测试验证各安全分支本仓库为该教程维护了一套覆盖完整行为矩阵的自动化测试test_tutorial004.py。它通过TestClient直接驱动tutorial004_an_py310.py/tutorial004_py310.pyfixture 参数化两条代码变体你可以对照着验证自己对本节安全模型的理解测试函数验证的安全分支预期结果test_login正确凭据提交/token200返回含access_tokentoken_type bearertest_login_incorrect_password/test_login_incorrect_username密码错 / 用户名不存在均 401文案统一为Incorrect username or passwordtest_no_token完全不携带令牌访问/users/me401Not authenticatedWWW-Authenticate: Bearertest_token携带合法 Bearer 令牌200返回 johndoe 完整资料test_incorrect_token携带伪造令牌Bearer nonexistent401Could not validate credentialstest_incorrect_token_type携带非 Bearer 前缀的令牌401Not authenticatedOAuth2PasswordBearer 层拒绝test_token_no_sub/test_token_no_username/test_token_nonexistent_user令牌缺sub/sub无对应用户均 401Could not validate credentialstest_token_inactive_user被禁用用户patch 注入 alice登录后访问400Inactive usertest_verify_password/test_get_password_hash/test_create_access_token工具函数单元行为哈希可验证、函数可正常生成令牌test_openapi_schema导出的 OpenAPI 结构snapshot 精确断言OAuth2PasswordBearer被声明为type: oauth2、flows.password.tokenUrl: token/users/me/与/users/me/items/的安全要求均为OAuth2PasswordBearer其中test_openapi_schema的 snapshot 恰好印证了第 1 节的链路FastAPI 之所以知道要把 OAuth2 流写进 OpenAPI正是因为OAuth2PasswordBearer是一个被识别为安全方案的依赖而OAuth2PasswordRequestForm只是普通类依赖只会以表单 schema 形式出现在/token的请求体描述中见 snapshot 中Body_login_for_access_token_token_post的username/password/grant_type/scope/client_id/client_secret字段。若你想查看本地生成的完整结构运行应用后请求http://127.0.0.1:8000/openapi.json即可。14. 进阶scopes与细粒度权限OAuth2 规范还有 “scopes” 概念可用于给 JWT 令牌附加一组特定的权限集合再把这个受限令牌直接交给用户或第三方让对方在约定的限制范围内操作你的 API。在本教程基础上FastAPI 的高级用户指南Advanced User Guide会进一步讲解 scopes 的用法及其与 FastAPI 的集成方式。这是 Facebook、Google、GitHub、Microsoft、XTwitter等大型认证提供方用来授权第三方应用代表用户访问其 API的标准机制。在掌握本节“密码流 JWT”后可继续沿着同一标准体系向 scopes 推进。OAuth2PasswordRequestForm中可选的scope表单字段空格分隔的字符串依赖实例中对应scopes列表属性正是为此预留的入口。15. 总结至此你已经拥有搭建一套安全 FastAPI 应用的完整工具箱密码单向哈希存储用pwdlib[argon2]推荐算法 Argon2加盐哈希配合假哈希比对抵御计时攻击并支持跨框架Django/Flask口令数据互认与渐进迁移JWT 无状态认证用PyJWT以 HS256 签名签发带exp过期时间的令牌任何篡改都会被签名校验识破令牌只在首次登录换取时涉及密码依赖注入式的统一安全层get_current_user/get_current_active_user把校验逻辑收敛到一次定义数千个端点即可用三行代码复用同一套安全体系标准协议兼容/token端点、WWW-Authenticate: Bearer、access_tokentoken_type响应、OpenAPI 安全方案声明均遵循 OAuth2 与 HTTP Bearer 规范。不同框架的安全实现往往很快变得复杂许多“一键简化”的包不得不在数据模型、数据库与功能上做出妥协甚至隐含安全缺陷。而 FastAPI 不对任何数据库、数据模型或工具做假设——它把选择权完全交给你并让你直接使用pwdlib、PyJWT这类成熟且广泛维护的库因为集成外部包不需要任何复杂机制同时它又提供了尽可能简化流程的工具在不牺牲灵活性、健壮性与安全性的前提下让 OAuth2 这类标准协议可以用相对简单的方式落地。关键参考文件本节源码docs_src/security/tutorial004_an_py310.py、docs_src/security/tutorial004_py310.py上一章不安全的基线实现docs_src/security/tutorial003_an_py310.py行为测试/tests/test_tutorial/test_security/test_tutorial004.py文档首页docs/en/docs/tutorial/security/index.md系列前置章节first-steps.md、simple-oauth2.md、get-current-user.md【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进