第六章 6.3: 基础认证
学习时间: 1 小时
1. API 认证: 进入飞行控制中心的通行证
认证 是验证用户身份的过程。与使用会话和 Cookie 的网站不同,无状态 (stateless) API 通常使用 令牌。
流程如下:
- 用户将其登录名和密码发送到特定的端点(例如,
/login
)。 - 服务器验证它们。如果正确,它会生成一个唯一的、加密的 令牌(一个长字符串)并将其发送回。
- 每次后续请求访问受保护资源(例如,
POST /planets
)时,用户必须在Authorization
头部中附加此令牌。 - 服务器验证令牌的有效性,如果令牌正确,则执行请求。
💡 太空类比:
- 登录名/密码 = 您的生物识别扫描,用于获取通行证。
- 令牌 = 您在进入飞行控制中心时获得的电子通行证(ID 卡)。
Authorization: Bearer <令牌>
头部 = 您将通行证贴在每个受保护门旁的读卡器上。- 受保护的端点 (POST, PUT, DELETE) = 服务器机房或启动控制台的门。
2. Laravel 中的认证: Sanctum
Laravel 为 API 认证提供了一个优雅的解决方案——Laravel Sanctum。它非常适合 SPA(单页应用程序)、移动应用程序和简单的基于令牌的 API。
步骤 1: 安装和配置 Sanctum
Sanctum 已在标准的 Laravel 应用程序中安装,但我们将检查配置。
- 发布配置 (如果尚未完成):
- 运行迁移 (将创建
personal_access_tokens
表): - 在
User
模型中添加 Trait: 打开app/Models/User.php
并确保它使用了HasApiTokens
trait。
步骤 2: 创建令牌颁发端点 我们需要一个用户将发送登录名/密码的路由。
添加到 routes/api.php
:
// routes/api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use Illuminate\Validation\ValidationException;
Route::post('/login', function (Request $request) {
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['凭据无效。'],
]);
}
// 返回令牌
return response()->json([
'token' => $user->createToken('api-token')->plainTextToken
]);
});
您可以通过 Seeder 或 Tinker 创建一个测试用户。
步骤 3: 保护路由
现在,我们将保护我们的 CRUD 操作。修改 routes/api.php
:
// routes/api.php
use App\Http\Controllers\PlanetController;
// 用于查看行星的公共路由
Route::get('/planets', [PlanetController::class, 'index']);
Route::get('/planets/{planet}', [PlanetController::class, 'show']);
// 受保护的路由组
Route::middleware('auth:sanctum')->group(function () {
Route::post('/planets', [PlanetController::class, 'store']);
Route::put('/planets/{planet}', [PlanetController::class, 'update']);
Route::delete('/planets/{planet}', [PlanetController::class, 'destroy']);
// 退出路由 (删除令牌)
Route::post('/logout', function (Request $request) {
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => '您已成功退出'], 200);
});
});
中间件 auth:sanctum
将检查 Authorization
头部中是否存在有效的令牌。
3. FastAPI 中的认证: OAuth2 和 JWT
FastAPI 没有内置的认证系统,但有强大的工具来实现在此。事实标准是 OAuth2 与 JWT 令牌。
步骤 1: 安装依赖
python-jose
: 用于创建和验证 JWT 令牌。passlib
: 用于哈希和验证密码。python-multipart
: 用于处理表单数据 (username
和password
)。
步骤 2: 创建安全模块 (security.py
)
这是一个很好的实践——将所有认证逻辑提取到一个单独的文件中。
创建文件 security.py
:
# security.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta, timezone
# --- 设置 ---
SECRET_KEY = "your-super-secret-key-that-is-long-and-random" # ⚠️ 请替换为您的密钥!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# --- 工具 ---
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/login")
# --- 函数 ---
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def create_access_token(data: dict):
to_encode = data.copy()
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
# --- 令牌验证依赖函数 ---
def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无法验证凭据",
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
except JWTError:
raise credentials_exception
# 在这里可以从数据库返回用户,目前只是返回用户名
return {"username": username}
步骤 3: 集成到 main.py
现在,我们将其连接到我们的应用程序。
-
创建
/login
端点:# main.py from fastapi.security import OAuth2PasswordRequestForm from fastapi import Depends, APIRouter from . import security # 导入我们的模块 # ... 您的 FastAPI 代码 ... router = APIRouter(prefix="/api/v1") @router.post("/login") def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): # 这里应该有数据库中的用户验证 # 例如,我们有一个测试用户 is_user_valid = (form_data.username == "testuser" and security.verify_password("testpass", security.get_password_hash("testpass"))) if not is_user_valid: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="用户名或密码错误", ) access_token = security.create_access_token(data={"sub": form_data.username}) return {"access_token": access_token, "token_type": "bearer"} # ... app.include_router(router)
-
保护端点:
我们使用
get_current_user
依赖。
4. 前端使用令牌
我们的前端现在应该首先获取令牌,保存它(例如,在 localStorage
中),并将其附加到每个受保护的请求中。
JavaScript 示例 (fetch
):
// 1. 登录
async function login(email, password) {
const response = await fetch('http://localhost:8001/api/login', { // Laravel API 地址
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email, password})
});
const data = await response.json();
if (data.token) {
localStorage.setItem('api_token', data.token); // 保存令牌
}
}
// 2. 发送受保护的请求
async function createPlanet(planetData) {
const token = localStorage.getItem('api_token');
const response = await fetch('http://localhost:8001/api/planets', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` // <--- 附加令牌!
},
body: JSON.stringify(planetData)
});
// ...
}
巩固练习
🚀 章节总结:
您已为您的 API 设置了“访问控制系统”。现在,不是每个想要的人都能修改您的“银河数据库”了。
- ✅ 理解了基于令牌的身份验证原则。
- 🔐 在 Laravel Sanctum 中实现了令牌颁发和路由保护。
- ⚙️ 在 FastAPI 中配置了基于 OAuth2 和 JWT 的身份验证。
- 🛰️ 了解了前端如何保存和使用令牌。
您的 API 不仅功能齐全,而且安全可靠。 然而,为了让其他开发人员能够使用它们,他们需要“操作说明”。
📌 检查:
- 尝试使用 Postman 或 Insomnia 对
/api/planets
(在 Laravel 中) 或/api/v1/planets
(在 FastAPI 中) 发送不带令牌的POST
请求。您应该会收到401 Unauthorized
错误。- 请求
/login
,获取令牌,将其添加到Authorization
标头中,然后重复POST
请求。它应该会成功执行。