本指南介绍如何将 FastAPI OAuth2.0 库与各种 OAuth2 提供程序一起使用。
from fastapi_oauth20 import GitHubOAuth20, GoogleOAuth20, FastAPIOAuth20
from fastapi import FastAPI, Depends
from fastapi.responses import RedirectResponse
import secrets
app = FastAPI()
# 初始化 GitHub OAuth2 客户端
github_client = GitHubOAuth20(
client_id="your_github_client_id",
client_secret="your_github_client_secret"
)
# 初始化 Google OAuth2 客户端
google_client = GoogleOAuth20(
client_id="your_google_client_id",
client_secret="your_google_client_secret"
)# 创建 FastAPI OAuth2 依赖
github_oauth = FastAPIOAuth20(
client=github_client,
redirect_uri="http://localhost:8000/auth/github/callback"
)
google_oauth = FastAPIOAuth20(
client=google_client,
redirect_uri="http://localhost:8000/auth/google/callback"
)@app.get("/auth/{provider}")
async def auth_provider(provider: str):
"""重定向用户到 OAuth2 提供商进行授权"""
# 生成安全的 state 参数用于 CSRF 保护
state = secrets.token_urlsafe(32)
if provider == "github":
auth_url = await github_client.get_authorization_url(
redirect_uri="http://localhost:8000/auth/github/callback",
state=state
)
elif provider == "google":
auth_url = await google_client.get_authorization_url(
redirect_uri="http://localhost:8000/auth/google/callback",
state=state
)
else:
raise HTTPException(status_code=404, detail="不支持的提供商")
return RedirectResponse(url=auth_url)from typing import Tuple, Dict, Any
@app.get("/auth/github/callback")
async def github_callback(
oauth_result: Tuple[Dict[str, Any], str] = Depends(github_oauth)
):
"""处理 GitHub OAuth 回调"""
token_data, state = oauth_result
# 获取用户信息
user_info = await github_client.get_userinfo(token_data["access_token"])
return {
"user": user_info,
"access_token": token_data["access_token"],
"state": state
}
@app.get("/auth/google/callback")
async def google_callback(
oauth_result: Tuple[Dict[str, Any], str] = Depends(google_oauth)
):
"""处理 Google OAuth 回调"""
token_data, state = oauth_result
# 获取用户信息
user_info = await google_client.get_userinfo(token_data["access_token"])
return {
"user": user_info,
"access_token": token_data["access_token"],
"state": state
}对于公共客户端(移动应用、SPA),使用 PKCE 增强安全性:
import base64
import hashlib
import secrets
def generate_pkce_challenge():
"""生成 PKCE 代码验证器和挑战码"""
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode('utf-8').rstrip('=')
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode('utf-8')).digest()
).decode('utf-8').rstrip('=')
return code_verifier, code_challenge
@app.get("/auth/github/pkce")
async def github_auth_pkce():
"""GitHub OAuth with PKCE"""
code_verifier, code_challenge = generate_pkce_challenge()
state = secrets.token_urlsafe(32)
# 在实际应用中,应该将 code_verifier 和 state 存储在会话或数据库中
# 这里为了演示目的简化处理
auth_url = await github_client.get_authorization_url(
redirect_uri="http://localhost:8000/auth/github/callback",
state=state,
code_challenge=code_challenge,
code_challenge_method="S256"
)
return RedirectResponse(url=auth_url)某些提供商支持刷新令牌来延长会话:
@app.post("/auth/refresh")
async def refresh_token(refresh_token: str, provider: str):
"""使用刷新令牌获取新的访问令牌"""
if provider == "github":
# GitHub 不支持 OAuth 刷新令牌
raise HTTPException(status_code=400, detail="GitHub 不支持令牌刷新")
elif provider == "google":
try:
new_tokens = await google_client.refresh_token(refresh_token)
return {"access_token": new_tokens["access_token"]}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))用户登出时撤销令牌:
@app.post("/auth/revoke")
async def revoke_token(access_token: str, provider: str):
"""撤销访问令牌"""
if provider == "google":
try:
await google_client.revoke_token(access_token)
return {"message": "令牌撤销成功"}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
else:
raise HTTPException(status_code=400, detail="该提供商不支持令牌撤销")库提供了全面的错误处理:
from fastapi_oauth20.errors import (
OAuth20AuthorizeCallbackError,
AccessTokenError,
GetUserInfoError
)
@app.exception_handler(OAuth20AuthorizeCallbackError)
async def oauth_callback_error_handler(request: Request, exc: OAuth20AuthorizeCallbackError):
"""处理 OAuth 回调错误"""
return {
"error": "OAuth 授权失败",
"detail": exc.detail,
"status_code": exc.status_code
}
@app.exception_handler(AccessTokenError)
async def access_token_error_handler(request: Request, exc: AccessTokenError):
"""处理访问令牌错误"""
return {
"error": "访问令牌交换失败",
"detail": exc.msg
}from fastapi import FastAPI, Depends, HTTPException
from fastapi.responses import RedirectResponse
from fastapi_oauth20 import GitHubOAuth20, FastAPIOAuth20
from fastapi_oauth20.errors import OAuth20AuthorizeCallbackError
import secrets
app = FastAPI()
# 初始化客户端
github_client = GitHubOAuth20(
client_id="your_client_id",
client_secret="your_client_secret"
)
# 创建依赖
github_oauth = FastAPIOAuth20(
client=github_client,
redirect_uri="http://localhost:8000/auth/github/callback"
)
@app.get("/auth/github")
async def github_auth():
"""GitHub 授权入口"""
state = secrets.token_urlsafe(32)
auth_url = await github_client.get_authorization_url(
redirect_uri="http://localhost:8000/auth/github/callback",
state=state
)
return RedirectResponse(url=auth_url)
@app.get("/auth/github/callback")
async def github_callback(
oauth_result: tuple = Depends(github_oauth)
):
"""GitHub 授权回调"""
token_data, state = oauth_result
user_info = await github_client.get_userinfo(token_data["access_token"])
return {"user": user_info}
# 错误处理
@app.exception_handler(OAuth20AuthorizeCallbackError)
async def oauth_error_handler(request, exc):
return {"error": "授权失败", "detail": exc.detail}- 安全性: 始终使用 HTTPS 端点
- 状态管理: 使用安全的 state 参数防止 CSRF 攻击
- 令牌存储: 安全地存储访问令牌和刷新令牌
- 错误处理: 妥善处理各种 OAuth2 错误场景
- 作用域: 只请求必要的作用域,遵循最小权限原则