FastAPI中如何让Google认证用户访问依赖本地JWT的受保护端点?
解决方法:让受保护路由兼容本地JWT和Google认证
有两种可行方案,优先推荐第一种,因为不需要改动现有受保护路由的逻辑,成本最低。
方案一:Google登录后签发本地JWT(推荐)
核心思路是:用户通过Google授权后,把Google返回的用户信息同步到本地用户体系,然后给用户签发和本地登录完全一致的JWT。这样前端后续请求用这个JWT访问受保护路由时,现有的get_current_user完全能正常验证,不需要任何修改。
具体实现步骤
写Google登录的回调接口:
from fastapi import APIRouter, HTTPException, status from starlette.requests import Request from your_module import User, settings, create_access_token # 替换成你自己的业务模块 router = APIRouter() @router.get("/auth/google/callback") async def google_auth_callback(request: Request): # 从Google获取授权令牌和用户信息 token = await google_oauth.google.authorize_access_token(request) user_info = token.get("userinfo") if not user_info or not user_info.get("email"): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="无法从Google获取用户信息" ) # 查找本地用户,不存在则创建新用户 email = user_info["email"] user = await User.find_one(User.email == email) if not user: user = User( email=email, name=user_info.get("name"), is_active=True ) await user.save() # 生成本地JWT返回给前端 access_token = create_access_token(data={"sub": email}) return {"access_token": access_token, "token_type": "bearer"}
(注:create_access_token是你本地登录系统里生成JWT的函数,直接复用即可)
前端拿到这个JWT后,和本地登录的使用方式完全一致——请求时在Header里带Authorization: Bearer <token>,get_current_user会自动验证,完全不用改。
方案二:修改get_current_user支持两种Token验证
如果想让前端直接用Google返回的ID Token访问受保护路由,就需要修改get_current_user,让它能同时验证本地JWT和Google的ID Token。
具体实现步骤
- 先安装Google认证的依赖库:
pip install google-auth
- 修改
get_current_user函数:
import google.auth from google.auth.transport.requests import Request as GoogleAuthRequest from google.oauth2 import id_token from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from your_module import User, settings, ALGORITHM, TokenData oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/jwt/login") async def get_current_user(token: str = Depends(oauth2_scheme)): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="凭证验证失败", headers={"WWW-Authenticate": "Bearer"}, ) # 先尝试验证本地JWT try: payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[ALGORITHM]) email = payload.get("sub") if not email: raise credentials_exception token_data = TokenData(email=email) except JWTError: # 本地JWT验证失败,再尝试验证Google ID Token try: # 验证Google的ID Token,记得替换成你的Google客户端ID id_info = id_token.verify_oauth2_token( token, GoogleAuthRequest(), settings.GOOGLE_CLIENT_ID ) # 校验签发者是否为官方Google域名 if id_info["iss"] not in ["accounts.google.com", "https://accounts.google.com"]: raise ValueError("签发者无效") email = id_info["email"] token_data = TokenData(email=email) except ValueError: # 两种Token都验证失败,抛出异常 raise credentials_exception # 查找本地用户,不存在则自动创建(可选,根据业务需求调整) user = await User.find_one(User.email == token_data.email) if not user: user = User( email=email, name=id_info.get("name"), is_active=True ) await user.save() return user
注意事项
- 方案二中Google的ID Token有效期只有1小时,前端需要处理Token过期后重新登录的逻辑
- 两种方案都要确保用户邮箱的唯一性,避免本地用户和Google用户邮箱冲突
内容的提问来源于stack exchange,提问作者Felipe
相关产品推荐
相关产品推荐

