feat(auth): integrate Google OAuth2 and secure cookie management

- Configured CORS middleware to allow cross-origin requests from the frontend.
- Implemented Google OAuth2 login flow and post-auth frontend redirection.
- Managed secure HTTP-only cookies for access, refresh, and state tokens.
This commit is contained in:
2026-05-19 23:55:03 +08:00
parent 2b5e193046
commit c68d88b8c1
2 changed files with 67 additions and 11 deletions
+15
View File
@@ -1,4 +1,6 @@
import os
from fastapi import FastAPI, Depends, HTTPException from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from strawberry import Schema from strawberry import Schema
from strawberry.fastapi import GraphQLRouter from strawberry.fastapi import GraphQLRouter
@@ -19,6 +21,19 @@ Base.metadata.create_all(bind=engine)
# 创建 FastAPI 实例 # 创建 FastAPI 实例
app = FastAPI() app = FastAPI()
# 获取环境变量字符串,并转成列表
origins_str = os.getenv("CORS_ORIGINS", "http://localhost:3000")
origins = [origin.strip() for origin in origins_str.split(",")]
# 配置 CORS 中间件,允许前端访问
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 组装 GraphQL Schema # 组装 GraphQL Schema
schema = Schema(query=Query, mutation=Mutation) schema = Schema(query=Query, mutation=Mutation)
+52 -11
View File
@@ -1,8 +1,13 @@
from ast import Is
import httpx import httpx
from fastapi import APIRouter, Depends, Request, HTTPException, BackgroundTasks from fastapi import APIRouter, Depends, Request, HTTPException, BackgroundTasks
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
import os import os
import secrets import secrets
import json
from urllib.parse import quote
from auth.deps import get_current_user from auth.deps import get_current_user
from app.db import SessionLocal from app.db import SessionLocal
from app.models import User from app.models import User
@@ -19,6 +24,18 @@ GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token" GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo" GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo"
FRONTEND_URL = os.getenv("FRONTEND_URL", "http://localhost:3000")
ENV_MODE = os.getenv("ENV_MODE")
IS_PROD = ENV_MODE == "prod"
# 在文件顶部获取环境变量
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60"))
REFRESH_TOKEN_EXPIRE_DAYS = int(os.getenv("REFRESH_TOKEN_EXPIRE_DAYS", "7"))
# 统一转换为秒数(max_age 需要秒)
ACCESS_TOKEN_MAX_AGE = ACCESS_TOKEN_EXPIRE_MINUTES * 60
REFRESH_TOKEN_MAX_AGE = REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60
# 定义独立的后台任务函数,用于处理头像转存到 S3 的耗时操作 # 定义独立的后台任务函数,用于处理头像转存到 S3 的耗时操作
async def async_upload_avatar_task(google_avatar_url: str, user_id: str): async def async_upload_avatar_task(google_avatar_url: str, user_id: str):
""" """
@@ -159,17 +176,41 @@ async def auth_callback(request: Request, code: str, state: str, background_task
"sub": user.id, "sub": user.id,
"email": user.email, "email": user.email,
}) })
return { response = RedirectResponse(url=f"{FRONTEND_URL}")
"access_token": jwt_token,
"refresh_token": refresh_token, response.set_cookie(
"token_type": "bearer", key="access_token",
# "user": { value=jwt_token,
# "email": user.email, httponly=True,
# "name": user.name, max_age=ACCESS_TOKEN_MAX_AGE,
# "avatar": get_image_url(user.avatar), samesite="lax",
# } secure=IS_PROD,
} )
response.set_cookie(
key="refresh_token",
value=refresh_token,
httponly=True,
max_age=REFRESH_TOKEN_MAX_AGE,
samesite="lax",
secure=IS_PROD,
)
response.set_cookie(
key="user_info",
value=quote(json.dumps({
"email": user.email,
"name": user.name,
"avatar": user.avatar
})),
max_age=ACCESS_TOKEN_MAX_AGE,
httponly=False
)
response.delete_cookie("oauth_state")
return response
# @router.get("/me") # @router.get("/me")
# def me(user = Depends(get_current_user)): # def me(user = Depends(get_current_user)):