You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在FastAPI中配置多个OIDC身份认证选项?

在FastAPI中实现多OIDC提供商认证

问题背景

已成功在FastAPI中集成Google OIDC认证,但尝试添加Microsoft OIDC选项时遇到两个核心问题:

  1. 直接使用Depends(oidc_google or oidc_microsoft)无法生效,无法同时支持两个提供商的token验证
  2. FastAPI默认Swagger UI仅支持配置单个OIDC参数,无法提供多提供商登录选项

原Google OIDC集成代码:

from fastapi import Depends
from fastapi.security import OpenIdConnect

oidc_google = OpenIdConnect(openIdConnectUrl='https://accounts.google.com/.well-known/openid-configuration')

@app.get('/foo')
def bar(token: Depends(oidc_google)):
    return "You're Authenticated"

尝试添加Microsoft OIDC的无效代码:

oidc_google = OpenIdConnect(openIdConnectUrl='https://accounts.google.com/.well-known/openid-configuration')
oidc_microsoft = OpenIdConnect(openIdConnectUrl='https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration')

@app.get('/foo')
def bar(token: Depends(oidc_google or oidc_microsoft)):
    return "You're Authenticated"

Swagger UI单OIDC配置:

app = FastAPI(
    swagger_ui_oauth2_redirect_url='/api/v1/oidc/callback',
    swagger_ui_init_oauth={
        'usePkceWithAuthorizationCodeGrant': True,
        'clientId': settings.OIDC_CLIENT_ID,
        'clientSecret': settings.OIDC_CLIENT_SECRET,
    },
)

解决方案

1. 自定义依赖实现多OIDC token验证

核心思路是编写自定义依赖函数,依次尝试用每个OIDC提供商的规则验证请求中的token,只要有一个验证通过就返回用户信息,全部失败则返回401。

示例代码:

from fastapi import Depends, HTTPException, status
from fastapi.security import OpenIdConnect
from jose import jwt, JWTError
from jose.backends import RSAKey
import httpx

# 初始化两个OIDC提供商
oidc_google = OpenIdConnect(openIdConnectUrl='https://accounts.google.com/.well-known/openid-configuration')
oidc_microsoft = OpenIdConnect(openIdConnectUrl='https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration')

# 预获取并缓存提供商的JWKS(用于验证token签名)
async def get_jwks(uri: str):
    async with httpx.AsyncClient() as client:
        response = await client.get(uri)
        response.raise_for_status()
        return response.json()

# 缓存JWKS,避免重复请求
google_jwks = await get_jwks(oidc_google.openid_configuration["jwks_uri"])
microsoft_jwks = await get_jwks(oidc_microsoft.openid_configuration["jwks_uri"])

async def multi_oidc_auth(token: str = Depends(oidc_google)):
    # 尝试Google OIDC验证
    try:
        header = jwt.get_unverified_header(token)
        rsa_key = next(k for k in google_jwks["keys"] if k["kid"] == header["kid"])
        public_key = RSAKey(rsa_key, algorithm=header["alg"])
        
        payload = jwt.decode(
            token,
            public_key,
            algorithms=oidc_google.openid_configuration["id_token_signing_alg_values_supported"],
            audience=settings.GOOGLE_CLIENT_ID,
            issuer=oidc_google.openid_configuration["issuer"]
        )
        return {"provider": "google", **payload}
    except (JWTError, StopIteration):
        # Google验证失败,尝试Microsoft OIDC验证
        try:
            header = jwt.get_unverified_header(token)
            rsa_key = next(k for k in microsoft_jwks["keys"] if k["kid"] == header["kid"])
            public_key = RSAKey(rsa_key, algorithm=header["alg"])
            
            payload = jwt.decode(
                token,
                public_key,
                algorithms=oidc_microsoft.openid_configuration["id_token_signing_alg_values_supported"],
                audience=settings.MICROSOFT_CLIENT_ID,
                issuer=oidc_microsoft.openid_configuration["issuer"]
            )
            return {"provider": "microsoft", **payload}
        except (JWTError, StopIteration):
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="无效的认证凭证",
                headers={"WWW-Authenticate": "Bearer"},
            )

在路由中使用该自定义依赖:

@app.get('/foo')
def bar(user_info = Depends(multi_oidc_auth)):
    return f"已通过{user_info['provider']}认证,用户邮箱:{user_info['email']}"

2. 自定义Swagger UI支持多提供商登录

FastAPI默认Swagger UI仅支持单个OIDC配置,需通过自定义HTML模板添加多提供商登录选项:

步骤1:创建自定义Swagger UI模板

在项目中创建templates/swagger_ui.html文件,修改默认模板并添加自定义登录按钮:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>FastAPI - Swagger UI</title>
  <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" />
  <style>
    .custom-login-buttons {
      margin: 10px 20px;
    }
    .custom-login-btn {
      margin-right: 10px;
      padding: 8px 16px;
      border: none;
      border-radius: 4px;
      background-color: #0070f3;
      color: white;
      cursor: pointer;
    }
    .custom-login-btn:hover {
      background-color: #0056b3;
    }
  </style>
</head>
<body>
  <div class="custom-login-buttons">
    <button class="custom-login-btn" id="google-login">Google登录</button>
    <button class="custom-login-btn" id="microsoft-login">Microsoft登录</button>
  </div>
  <div id="swagger-ui"></div>
  <script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
  <script>
    const ui = SwaggerUIBundle({
      url: "{{ openapi_url }}",
      dom_id: '#swagger-ui',
      deepLinking: true,
      presets: [
        SwaggerUIBundle.presets.apis,
        SwaggerUIBundle.SwaggerUIStandalonePreset
      ],
      plugins: [
        SwaggerUIBundle.plugins.DownloadUrl
      ],
      layout: "StandaloneLayout",
      oauth2RedirectUrl: "{{ swagger_ui_oauth2_redirect_url }}"
    });

    // Google登录逻辑
    document.getElementById('google-login').addEventListener('click', () => {
      ui.initOAuth({
        clientId: "{{ settings.GOOGLE_CLIENT_ID }}",
        clientSecret: "{{ settings.GOOGLE_CLIENT_SECRET }}",
        issuer: "https://accounts.google.com",
        usePkceWithAuthorizationCodeGrant: true
      });
      ui.authActions.authorize('oauth2');
    });

    // Microsoft登录逻辑
    document.getElementById('microsoft-login').addEventListener('click', () => {
      ui.initOAuth({
        clientId: "{{ settings.MICROSOFT_CLIENT_ID }}",
        clientSecret: "{{ settings.MICROSOFT_CLIENT_SECRET }}",
        issuer: "https://login.microsoftonline.com/common/v2.0",
        usePkceWithAuthorizationCodeGrant: true
      });
      ui.authActions.authorize('oauth2');
    });
  </script>
</body>
</html>

步骤2:在FastAPI中加载自定义模板

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates

# 设置模板目录
templates = Jinja2Templates(directory="templates")

app = FastAPI(docs_url=None)  # 禁用默认docs路由

@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui(request: Request):
    return templates.TemplateResponse(
        "swagger_ui.html",
        {
            "request": request,
            "openapi_url": app.openapi_url,
            "swagger_ui_oauth2_redirect_url": "/api/v1/oidc/callback",
            "settings": settings  # 传入包含双提供商配置的settings对象
        }
    )

注意事项

  • 确保每个OIDC提供商的clientId、clientSecret配置正确,且已在对应平台完成应用注册(回调URL需完全匹配)
  • JWKS可缓存至内存或外部存储,避免每次验证都请求提供商接口,提升性能
  • 自定义Swagger UI模板时,需保持Swagger UI资源链接与FastAPI版本兼容

内容的提问来源于stack exchange,提问作者Ghasem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 06:02:00