如何在FastAPI中配置多个OIDC身份认证选项?
在FastAPI中实现多OIDC提供商认证
问题背景
已成功在FastAPI中集成Google OIDC认证,但尝试添加Microsoft OIDC选项时遇到两个核心问题:
- 直接使用
Depends(oidc_google or oidc_microsoft)无法生效,无法同时支持两个提供商的token验证 - 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
相关产品推荐
相关产品推荐

