如何让FastAPI端点兼容Azure AD与API密钥双重认证?
FastAPI多认证方式兼容问题解决(Azure Auth + API Key)
问题背景
我用fastapi-azure-auth实现了Azure用户认证,功能正常,但需要让部分API端点同时支持请求头中的API密钥访问。当前遇到的问题:
- 仅携带
x-api-key的curl请求返回401 Unauthorized - 若移除
dual_authentication里的Depends(azure_scheme),Swagger完成Azure授权后无法访问端点(不会自动携带Bearer令牌)
需要兼容三个场景:
- Swagger完成授权后携带令牌请求,返回200 OK
- curl仅携带
X-API-Key请求,返回200 OK - curl携带
Authorization: Bearer令牌请求,返回200 OK
项目结构:认证配置在dependencies.py,路由定义在api/v1/contracts.py
现有代码
端点代码(api/v1/contracts.py)
@router.get("/", response_model=list[ContractSchema]) def get_contracts( filters: ContractFilter = Depends(), db: Session = Depends(get_db), user: User = Depends(dual_authentication), ) -> list[ContractSchema]: return []
认证配置(dependencies.py)
from fastapi_azure_auth.auth import SingleTenantAzureAuthorizationCodeBearer from fastapi_azure_auth.user import User azure_scheme = SingleTenantAzureAuthorizationCodeBearer( app_client_id=settings.azure_ad.app_client_id, tenant_id=settings.azure_ad.tenant_id, token_version=1, scopes={f"api://{settings.azure_ad.app_client_id}/user_impersonation": "user_impersonation"}, ) api_key_scheme = APIKeyHeader(name="x-api-key", auto_error=False) protected_dependencies = [Security(azure_scheme, scopes=["user_impersonation"])] def dual_authentication( api_key: Optional[str] = Security(api_key_scheme), azure_token: Optional[User] = Depends(azure_scheme) ) -> User: """Authenticate via API key or Azure AD.""" # Check for API key in the header and validate if _is_api_key_present_and_valid(api_key): return EXTERNAL_API_USER # Fall back to Azure AD authentication if azure_token: return azure_token # If neither is valid, raise an error raise HTTPException(status_code=401, detail="User not authenticated")
问题原因
当前dual_authentication中使用Depends(azure_scheme)时,azure_scheme默认是强制校验的——哪怕参数标记为Optional,当请求只带API Key时,Azure认证逻辑会先触发校验,发现没有Bearer令牌就直接返回401,根本走不到API Key的校验步骤。
而如果移除Depends(azure_scheme),Swagger无法识别该端点需要Azure认证,授权后不会自动携带Bearer令牌,导致请求失败。
解决方案
修改dual_authentication中Azure令牌的依赖方式,用Security替代Depends并添加auto_error=False,让Azure认证变为非强制校验:当没有Bearer令牌时,不会直接抛出401,而是让azure_token为None,继续执行API Key的校验逻辑,同时保留Swagger的认证识别能力。
修改后的dependencies.py代码:
from fastapi_azure_auth.auth import SingleTenantAzureAuthorizationCodeBearer from fastapi_azure_auth.user import User from fastapi import Security, HTTPException, APIKeyHeader from typing import Optional azure_scheme = SingleTenantAzureAuthorizationCodeBearer( app_client_id=settings.azure_ad.app_client_id, tenant_id=settings.azure_ad.tenant_id, token_version=1, scopes={f"api://{settings.azure_ad.app_client_id}/user_impersonation": "user_impersonation"}, ) api_key_scheme = APIKeyHeader(name="x-api-key", auto_error=False) def dual_authentication( api_key: Optional[str] = Security(api_key_scheme), azure_token: Optional[User] = Security(azure_scheme, scopes=["user_impersonation"], auto_error=False) ) -> User: """Authenticate via API key or Azure AD.""" # 优先校验API Key if api_key and _is_api_key_present_and_valid(api_key): return EXTERNAL_API_USER # 校验Azure令牌 if azure_token: return azure_token # 两种认证都失败时返回401 raise HTTPException(status_code=401, detail="User not authenticated")
关键修改点
- 将
azure_token的依赖从Depends(azure_scheme)改为Security(azure_scheme, scopes=["user_impersonation"], auto_error=False) auto_error=False:让Azure认证在无令牌时不直接返回401,而是将azure_token设为None,继续执行后续逻辑- 调整API Key校验条件:明确判断
api_key存在再执行校验,避免空值传入校验函数
验证场景
- Swagger授权请求:在Swagger中完成Azure OAuth2授权后,请求自动携带
Authorization: Bearer令牌,azure_token正常解析,返回200 OK - 仅API Key请求:curl只携带
X-API-Key头,校验通过后返回200 OKcurl -H "X-API-Key: your-valid-api-key" http://localhost:8000/api/v1/contracts/ - Bearer令牌请求:curl携带
Authorization: Bearer令牌,Azure认证通过后返回200 OKcurl -H "Authorization: Bearer your-azure-token" http://localhost:8000/api/v1/contracts/
内容的提问来源于stack exchange,提问作者Emu
相关产品推荐
相关产品推荐

