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

FastAPI OAuth2中如何为scopes字典传入标量Scope值?

我来帮你理清这两个核心问题——毕竟我之前在FastAPI对接Azure AD认证时也踩过类似的坑:

一、OAuthFlow的scopes字典该怎么处理你的Azure .default Scope?

首先得明确:OAuthFlow里的scopes字段是Dict[str, str]类型,键是给用户/开发者看的「友好名称」,值是实际的Scope字符串。这是FastAPI用来生成OpenAPI文档时展示可选权限用的,比如在Swagger UI的授权弹窗里,用户能看到这些友好名称,选择对应的权限。

针对你的api://a12b34cd-5e67-89f0-a12b-c3de456f78ab/.default这个Scope,你可以这么配置:

  • 如果需要友好名称(方便在文档里说明):
    scopes={
        "API默认访问权限": "api://a12b34cd-5e67-89f0-a12b-c3de456f78ab/.default"
    }
    
  • 如果不需要额外说明,也可以直接用Scope字符串本身作为键(虽然不太直观,但完全可行):
    scopes={
        "api://a12b34cd-5e67-89f0-a12b-c3de456f78ab/.default": "默认访问权限"
    }
    
  • 空白项?其实FastAPI不允许键或值为空字符串(会影响OpenAPI文档生成),所以至少要填一个有意义的描述,哪怕是简短的“Default”。

二、SecurityScopes类和OAuthFlow的scopes字典的区别与交互逻辑

这俩其实是分工明确的两个东西:

1. 核心区别

  • OAuthFlow的scopes字典:是「静态定义」,用来声明你的API支持哪些Scope,作用是生成OpenAPI文档,告诉客户端“我支持这些权限,你请求令牌时可以指定它们”。它不参与实际的权限校验逻辑,只是文档和客户端引导用的。
  • SecurityScopes类:是「动态处理」,用来解析请求中令牌携带的Scope。Azure返回的令牌里,Scope是用空格分隔的字符串(比如如果客户端请求了多个Scope,会是scope1 scope2),SecurityScopes会自动把这个字符串拆成列表,同时帮你生成WWW-Authenticate响应头里的scope参数,方便客户端知道需要哪些权限。

2. 交互逻辑

举个贴合你场景的例子,你自定义SecurityBase子类时的典型流程:

  1. 先在OAuthFlow的scopes字典里定义好你支持的Azure Scope(比如你的.default Scope),让Swagger UI等工具能展示这些权限选项。
  2. 在路由里使用Security(your_custom_auth, scopes=["xxx/.default"])来指定该路由需要的权限。
  3. 在自定义认证依赖的__call__方法中,注入SecurityScopes对象:它会自动收集路由指定的scopes,同时你可以从令牌里解析出实际携带的Scope字符串,用security_scopes.scope_str或者自己拆分列表,然后做权限校验。

比如你的自定义依赖里的核心逻辑:

from fastapi import Security, HTTPException, status
from fastapi.security.base import SecurityBase
from fastapi.security.oauth2 import SecurityScopes, OAuth2, OAuthFlowAuthorizationCode

class AzureOAuth2(SecurityBase):
    def __init__(self, tenant_id: str, client_id: str):
        self.scheme_name = "OAuth2AuthorizationCodeBearer"
        self.model = OAuth2(
            flows={
                "authorizationCode": OAuthFlowAuthorizationCode(
                    authorizationUrl=f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize",
                    tokenUrl=f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
                    scopes={
                        "API默认访问权限": "api://a12b34cd-5e67-89f0-a12b-c3de456f78ab/.default"
                    }
                )
            }
        )

    async def __call__(self, security_scopes: SecurityScopes):
        # 1. 从请求头获取Bearer令牌
        auth_header = await self._get_auth_header()
        token = auth_header.split("Bearer ")[1]
        
        # 2. 验证Azure令牌(这里可以用pyjwt或者azure-identity的工具)
        decoded_token = self._validate_azure_token(token)
        
        # 3. 解析令牌里的Scope字符串(Azure令牌里的Scope在"scp"字段)
        token_scopes = decoded_token.get("scp", "").split()
        
        # 4. 校验所需Scope是否存在
        for required_scope in security_scopes.scopes:
            if required_scope not in token_scopes:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="权限不足",
                    headers={"WWW-Authenticate": f'Bearer scope="{security_scopes.scope_str}"'},
                )
        return decoded_token

在路由里使用时:

@app.get("/protected-resource")
async def get_protected_data(
    token_data = Security(AzureOAuth2(tenant_id="your-tenant", client_id="your-client"), 
                         scopes=["api://a12b34cd-5e67-89f0-a12b-c3de456f78ab/.default"])
):
    return {"data": "敏感内容", "user": token_data["name"]}

这里的SecurityScopes会自动把路由指定的scopes转换成空格分隔的字符串(security_scopes.scope_str),用来在401响应里告诉客户端需要哪些权限;同时你可以用security_scopes.scopes获取拆分后的列表,和令牌里的Scope做对比。

最后总结一下

  • 把你的Azure .default Scope放到OAuthFlow的scopes字典的值里,键填一个友好名称即可;
  • OAuthFlow的scopes是给文档和客户端看的静态声明,SecurityScopes是处理请求令牌的动态工具;
  • 自定义认证时,用SecurityScopes来管理路由所需权限,同时解析令牌里的实际Scope做校验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 18:05:17