
【个人主页:玄同765】
大语言模型(LLM)开发工程师|中国传媒大学·数字媒体技术(智能交互与游戏设计)
深耕领域:大语言模型开发 / RAG知识库 / AI Agent落地 / 模型微调
技术栈:Python / LangChain/RAG(Dify+Redis+Milvus)| SQL/NumPy | FastAPI+Docker ️
工程能力:专注模型工程化部署、知识库构建与优化,擅长全流程解决方案
专栏传送门:LLM大模型开发 项目实战指南、Python 从真零基础到纯文本 LLM 全栈实战、从零学 SQL + 大模型应用落地、大模型开发小白专属:从 0 入门 Linux&Shell
「让AI交互更智能,让技术落地更高效」
欢迎技术探讨/项目合作! 关注我,解锁大模型与智能交互的无限可能!
你是不是写完 API 接口直接上线,没有任何身份验证和权限控制?比如用户可以随意查看别人的个人信息、删除商品数据?这篇用大量代码示例和对比表格,详解 FastAPI 的 OAuth2 密码模式、JWT 令牌验证、基于依赖注入的权限控制,以及 CORS 跨域资源共享配置,让你的接口 30 分钟内从裸奔变成生产级安全。
一、引言:API 接口裸奔的后果有多严重?
在 API 接口开发中,身份验证和权限控制是保障系统安全的核心环节。如果你的接口没有任何安全措施,后果将不堪设想:
- 数据泄露:攻击者可以随意访问、修改、删除服务器上的敏感数据,比如用户的个人信息、支付记录、公司的财务数据;
- 恶意攻击:攻击者可以通过大量的请求接口消耗服务器资源,导致系统崩溃;
- 合规问题:如果你的接口处理用户的个人信息,没有身份验证和权限控制,可能会违反 GDPR、《个人信息保护法》等法律法规。
直到 FastAPI 出现,API 接口的身份验证和权限控制变得非常简单。FastAPI 内置了对 OAuth2 和 JWT 的支持,并且可以通过依赖注入实现灵活的权限控制,开发效率比传统的 Django 或 Flask 提升了一倍不止。
二、基础准备:项目初始化和依赖安装
如果你已经完成了前三篇的学习,可以直接使用之前创建的fastapi-demo项目。如果没有,请按照以下步骤初始化项目:
- 创建一个新的文件夹,命名为
fastapi-demo; - 在文件夹中创建一个 Python 文件,命名为
main.py; - 在终端中进入
fastapi-demo文件夹,输入以下命令安装依赖:pip install fastapi uvicorn python-multipart python-jose[cryptography] passlib[bcrypt]
其中:
python-jose[cryptography]:用于生成和验证 JWT 令牌;passlib[bcrypt]:用于加密和验证用户密码。
三、身份验证方法详解:OAuth2 密码模式 + JWT 令牌验证
OAuth2 是目前最流行的身份验证和授权协议之一,FastAPI 内置了对 OAuth2 的支持。在 API 接口开发中,最常用的 OAuth2 模式是密码模式,它允许用户通过用户名和密码直接获取访问令牌。
3.1 加密用户密码
为了保障用户密码的安全,我们需要在存储密码时对其进行加密,而不是明文存储。我们可以使用passlib[bcrypt]库的CryptContext类实现密码的加密和验证。
打开main.py文件,输入以下代码:
from fastapi import FastAPI, Depends, HTTPException, status, Form
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
app = FastAPI()
# 模拟用户数据(密码已加密)
mock_users = [
{
"user_id": 123,
"username": "zhangsan",
"email": "zhangsan@example.com",
"password": "$2b$12$EixZaYb4xU58Gpq1R0yWbeb00LU5qUj.GXg0Y91cn.o50Emqh8C9W", # 密码:123456
"is_admin": False,
"is_active": True
},
{
"user_id": 456,
"username": "admin",
"email": "admin@example.com",
"password": "$2b$12$EixZaYb4xU58Gpq1R0yWbeb00LU5qUj.GXg0Y91cn.o50Emqh8C9W", # 密码:123456
"is_admin": True,
"is_active": True
}
]
# 密码加密和验证的上下文
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# JWT令牌的配置
SECRET_KEY = "your-secret-key-here" # 生产环境中应该使用环境变量存储
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30 # 访问令牌的过期时间(分钟)
# OAuth2密码模式的配置
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 定义用户信息的数据结构
class User(BaseModel):
user_id: int
username: str
email: EmailStr
is_admin: bool = False
is_active: bool = True
class Config:
orm_mode = True
# 定义访问令牌的数据结构
class Token(BaseModel):
access_token: str
token_type: str
# 定义访问令牌的有效载荷的数据结构
class TokenData(BaseModel):
username: Optional[str] = None
# 密码加密函数
def get_password_hash(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)
# 根据用户名获取用户信息
def get_user_by_username(username: str) -> Optional[dict]:
for user in mock_users:
if user["username"] == username:
return user
return None
# 认证用户(验证用户名和密码)
def authenticate_user(username: str, password: str) -> Optional[dict]:
user = get_user_by_username(username)
if not user:
return False
if not verify_password(password, user["password"]):
return False
return user
# 生成访问令牌
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# 获取当前用户的依赖注入函数
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except JWTError:
raise credentials_exception
user = get_user_by_username(username=token_data.username)
if user is None:
raise credentials_exception
return user
# 获取当前激活用户的依赖注入函数
async def get_current_active_user(current_user: dict = Depends(get_current_user)):
if not current_user["is_active"]:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
测试密码加密和验证:
- 打开终端,进入
fastapi-demo文件夹; - 输入以下 Python 代码:
from main import get_password_hash, verify_password # 加密密码 hashed_password = get_password_hash("Test1234") print(hashed_password) # 验证密码 is_valid = verify_password("Test1234", hashed_password) print(is_valid) - 运行代码,输出结果应该是类似
$2b$12$EixZaYb4xU58Gpq1R0yWbeb00LU5qUj.GXg0Y91cn.o50Emqh8C9W的加密密码和True。
3.2 获取访问令牌的接口
接下来,我们写一个获取访问令牌的接口,用户可以通过用户名和密码直接获取 JWT 访问令牌。
打开main.py文件,输入以下代码:
# 获取访问令牌的接口
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
"""
获取访问令牌的接口
:param form_data: OAuth2密码模式的请求数据(用户名和密码)
:return: 访问令牌的JSON响应
"""
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"]}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
测试获取访问令牌的接口:
- 启动 FastAPI 服务:
uvicorn main:app --reload - 打开 http://127.0.0.1:8000/docs;
- 点击
/token接口旁边的Try it out按钮; - 在
username输入框中输入zhangsan,在password输入框中输入123456; - 点击
Execute,可以看到返回的 JWT 访问令牌。
3.3 受保护的接口
现在,我们可以写一些受保护的接口,只有持有有效 JWT 访问令牌的用户才能访问。
打开main.py文件,输入以下代码:
# 获取当前用户信息的接口(受保护)
@app.get("/users/me", response_model=User)
async def read_users_me(current_user: dict = Depends(get_current_active_user)):
"""
获取当前用户信息的接口(受保护)
:param current_user: 当前激活用户的信息(依赖注入)
:return: 当前用户信息的JSON响应
"""
return current_user
# 获取用户列表的接口(受保护,只有管理员能访问)
@app.get("/users", response_model=list[User])
async def read_users(current_user: dict = Depends(get_current_active_user)):
"""
获取用户列表的接口(受保护,只有管理员能访问)
:param current_user: 当前激活用户的信息(依赖注入)
:return: 用户列表的JSON响应
"""
if not current_user["is_admin"]:
raise HTTPException(status_code=403, detail="Not enough permissions")
return mock_users
测试受保护的接口:
- 打开 http://127.0.0.1:8000/docs;
- 点击右上角的
Authorize按钮; - 在
Value输入框中输入Bearer加上刚才获取的访问令牌(比如Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ6aGFuZ3NhbiIsImV4cCI6MTczMTczNjUxMX0.abc123); - 点击
Authorize,然后点击Close; - 点击
/users/me接口旁边的Try it out按钮,点击Execute,可以看到当前用户的信息; - 点击
/users接口旁边的Try it out按钮,点击Execute,如果当前用户是普通用户(如 zhangsan),会返回 403 错误;如果是管理员用户(如 admin),会返回用户列表。
四、权限控制详解:基于依赖注入的权限控制
FastAPI 的依赖注入系统是其核心特性之一,我们可以通过依赖注入实现灵活的权限控制。比如,我们可以定义一个依赖注入函数,只有具有特定权限的用户才能访问接口。
4.1 定义权限依赖注入函数
打开main.py文件,输入以下代码:
# 只有管理员能访问的依赖注入函数
async def get_current_active_admin_user(current_user: dict = Depends(get_current_active_user)):
if not current_user["is_admin"]:
raise HTTPException(status_code=403, detail="Not enough permissions")
return current_user
4.2 使用权限依赖注入函数
修改之前的获取用户列表的接口,使用新定义的权限依赖注入函数:
# 获取用户列表的接口(受保护,只有管理员能访问)
@app.get("/users", response_model=list[User])
async def read_users(current_user: dict = Depends(get_current_active_admin_user)):
"""
获取用户列表的接口(受保护,只有管理员能访问)
:param current_user: 当前激活管理员用户的信息(依赖注入)
:return: 用户列表的JSON响应
"""
return mock_users
测试权限控制:
- 打开 http://127.0.0.1:8000/docs;
- 点击右上角的
Authorize按钮,使用管理员用户(admin)的访问令牌进行授权; - 点击
/users接口旁边的Try it out按钮,点击Execute,可以看到用户列表; - 点击右上角的
Authorize按钮,点击Logout,使用普通用户(zhangsan)的访问令牌进行授权; - 点击
/users接口旁边的Try it out按钮,点击Execute,会返回 403 错误。
五、跨域资源共享配置:CORS
跨域资源共享(CORS)是浏览器的一种安全机制,它限制了从一个域名请求另一个域名的资源。如果你的 FastAPI 接口需要被其他域名的前端应用访问,你需要配置 CORS。
FastAPI 内置了对 CORS 的支持,你可以使用fastapi.middleware.cors模块的CORSMiddleware类配置 CORS。
打开main.py文件,输入以下代码:
from fastapi.middleware.cors import CORSMiddleware
# CORS配置
origins = [
"http://localhost",
"http://localhost:3000", # React开发服务器的默认地址
"http://localhost:8080", # Vue开发服务器的默认地址
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins, # 允许访问的域名
allow_credentials=True, # 允许携带凭证(如Cookies)
allow_methods=["*"], # 允许访问的HTTP请求方法
allow_headers=["*"], # 允许访问的HTTP请求头
)
测试 CORS 配置:
- 启动 FastAPI 服务;
- 创建一个简单的 HTML 文件,命名为
index.html,输入以下代码:<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CORS测试</title> </head> <body> <h1>CORS测试</h1> <button onclick="getUserInfo()">获取用户信息</button> <div id="user-info"></div> <script> async function getUserInfo() { const token = prompt("请输入JWT访问令牌:"); try { const response = await fetch("http://127.0.0.1:8000/users/me", { method: "GET", headers: { "Authorization": `Bearer ${token}` } }); const data = await response.json(); document.getElementById("user-info").innerHTML = JSON.stringify(data); } catch (error) { console.error(error); document.getElementById("user-info").innerHTML = "获取用户信息失败"; } } </script> </body> </html> - 保存文件后,使用浏览器打开该文件;
- 点击
获取用户信息按钮,输入 JWT 访问令牌,点击确定; - 如果 CORS 配置正确,会显示用户信息;如果配置错误,会在浏览器控制台中显示 CORS 错误。
六、生产环境中的安全建议
虽然我们已经实现了基本的身份验证和权限控制,但在生产环境中,我们还需要注意以下几点:
- 使用 HTTPS:生产环境中必须使用 HTTPS 协议,防止 JWT 访问令牌在传输过程中被截获;
- 存储 SECRET_KEY:生产环境中应该使用环境变量存储 SECRET_KEY,而不是硬编码到代码中;
- 设置合适的访问令牌过期时间:访问令牌的过期时间应该根据业务需求设置,一般建议不超过 1 小时;
- 刷新令牌:可以实现刷新令牌功能,让用户在访问令牌过期后不需要重新登录;
- 输入验证:对所有用户输入的数据进行严格的验证,防止 SQL 注入、XSS 攻击等;
- 日志记录:记录所有的身份验证和权限控制相关的日志,方便排查问题和审计。
七、常用身份验证和权限控制库对比表
为了让你更直观地了解 FastAPI 常用的身份验证和权限控制库,我们整理了以下对比表:
| 库名称 | 功能 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
python-jose[cryptography] | 生成和验证 JWT 令牌 | 支持多种加密算法,文档完善,性能高 | 需要额外安装依赖 | API 接口开发 |
passlib[bcrypt] | 密码加密和验证 | 支持多种加密算法,安全性高,文档完善 | 加密和验证密码的速度较慢 | 用户注册和登录 |
fastapi.security | OAuth2 和 JWT 的内置支持 | 与 FastAPI 完美集成,代码简洁,开发效率高 | 功能相对简单,不支持复杂的 OAuth2 模式 | 简单的 API 接口开发 |
sqlalchemy | 数据库操作 | 支持多种数据库,ORM 功能强大,文档完善 | 学习曲线较陡,代码量较大 | 复杂的 API 接口开发 |
八、进阶提示:下一步可以学什么?
如果你已经掌握了基本的身份验证和权限控制,下一步你可以学习以下内容:
- 刷新令牌:实现刷新令牌功能,让用户在访问令牌过期后不需要重新登录;
- OAuth2 授权码模式:实现 OAuth2 授权码模式,允许用户通过第三方平台(如微信、GitHub)登录;
- API 密钥验证:实现 API 密钥验证,允许第三方应用程序访问接口;
- 双因素认证:实现双因素认证,提高用户登录的安全性;
- 审计日志:记录所有的身份验证和权限控制相关的审计日志,方便排查问题和审计。
九、结语:身份验证和权限控制是系统安全的核心
身份验证和权限控制是保障 API 接口安全的核心环节,FastAPI 内置了对 OAuth2 和 JWT 的支持,并且可以通过依赖注入实现灵活的权限控制。通过这篇文章的学习,你应该已经掌握了基本的身份验证和权限控制方法,可以让你的接口从裸奔变成生产级安全。
如果你之前对身份验证和权限控制的使用感到困惑,希望这篇文章能给你带来帮助。如果你有任何问题或建议,欢迎在评论区留言,我会及时回复。
355

被折叠的 条评论
为什么被折叠?



