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

基于Azure AD与Python的应用间认证授权问题咨询

问题背景

我有两个已在Azure AD中注册的应用:

  • APP_1:基于Flask搭建的Web应用,已集成Azure AD实现单点登录(SSO),所有端点均受权限限制。用户通过浏览器访问时会自动触发登录,验证身份后可访问有权限的端点,否则返回PermissionError。
    配置信息:
    tenant_id = "tenant_id_of_APP_1_and_APP_2"
    client_id_app_1 = "app_1"
    client_secret_app_1 = "secret_app_1"
    
  • APP_2:需要向APP_1发起API请求的应用
    配置信息:
    tenant_id = "tenant_id_of_APP_1_and_APP_2"
    client_id_app_2 = "app_2"
    client_secret_app_2 = "secret_app_2"
    
初始尝试的问题

我希望为APP_2开放部分APP_1的端点权限,最初尝试通过以下方式获取access_token(当时意识到请求token时应指定目标为APP_1,但没找到对应参数):

import requests
# 认证并获取access_token
auth_url = f'https://login.microsoftonline.com/{tenant_id_of_APP_1_and_APP_2}/oauth2/v2.0/token'
data = {
    'grant_type': 'client_credentials',
    'client_id': client_id_app_2,
    'client_secret': client_secret_app_2,
    'scope': 'https://graph.microsoft.com/.default'
}
response = requests.post(auth_url, data=data).json()
access_token = response['access_token']

携带该token请求APP_1的受保护API:

headers = {
    'Authorization': f'Bearer {access_token}',
    'Content-Type': 'application/octet-stream',
}

x = requests.post("https://localhost:5000/test_api", headers=headers)
response = requests.get("http://localhost:5000/test_api", headers=headers)

APP_1收到请求后,我尝试验证access_token,但使用JWT解码时始终提示无效签名:

token = request.headers.get("Authorization").removeprefix("Bearer ")
public_key = get_public_key(token)
decoded = jwt.decode(
    token,
    public_key,
    verify=True,
    algorithms=["RS256"],
    audience=[client_id_app_1],
)

同时想知道是否有更优方案,能将access_token发送至Azure进行验证。

修改后的代码与疑问

我已修改代码如下:
在APP_2中:

server_client_id = server_app_config.CLIENT_ID
app = msal.ConfidentialClientApplication(client_id=client_id_app_2, client_credential=client_secret_app_2, authority=f"https://login.microsoftonline.com/{tenant_id_of_APP_1_and_APP_2}")
result = app.acquire_token_for_client(scopes=[f"{client_id_app_1}/.default"])
access_token = result["access_token"]
headers = {
    'Authorization': f'Bearer {access_token}',
    'Content-Type': 'application/octet-stream',
}
response = requests.post(url, headers=payload)

在APP_1中验证token的方式:

token = request.headers.get("Authorization").removeprefix("Bearer ")
public_key = get_public_key(token)
decoded = jwt.decode(
    token,
    public_key,
    verify=True,
    algorithms=['RS256'],
    audience=[client_id_app_1],
)

请问当前的工作流程是否符合规范?


解答

1. 修改后的核心流程符合规范

你在APP_2中使用{client_id_app_1}/.default作为scope的做法完全正确——在客户端凭证流(client_credentials)中,这个scope代表请求APP_1的所有已配置应用权限,是服务间调用的标准权限模型。

2. 关于令牌验证的问题

初始无效签名的原因

最初你用了Graph API的scope,导致令牌的aud(受众)字段是Graph API而非APP_1,所以APP_1验证时会因为受众不匹配失败;同时公钥对应Graph的颁发者,自然会出现签名无效的提示。修改scope后,令牌的aud会自动变为APP_1的client_id,这一步已经修正了核心问题。

两种令牌验证方案

方案一:本地JWT验证(当前方案)

如果继续用本地验证,要确保get_public_key函数从正确的端点获取公钥:
Azure AD的公钥需从租户的开放ID配置端点获取:https://login.microsoftonline.com/{tenant_id}/v2.0/.well-known/openid-configuration,从中提取jwks_uri后,拉取对应令牌kid字段的公钥。公钥获取错误是签名验证失败的常见原因。

另外,解码时除了aud,还需要验证以下关键字段:

  • iss:颁发者,必须是https://login.microsoftonline.com/{tenant_id}/v2.0
  • exp:令牌过期时间,确保未过期
  • nbf:令牌生效时间,确保已生效
方案二:调用Azure AD验证端点(更省心)

可以直接把令牌发送到Azure AD的验证端点,让Azure完成所有验证逻辑:

import requests

def validate_token_with_azure(token, tenant_id, client_id, client_secret):
    validate_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/introspect"
    data = {
        'token': token,
        'client_id': client_id,
        'client_secret': client_secret,
        'token_type_hint': 'access_token'
    }
    response = requests.post(validate_url, data=data).json()
    return response.get('active', False)

这个端点会返回令牌是否有效,以及完整的令牌信息,无需自己维护公钥和字段校验逻辑,适合不想折腾本地验证的场景。

3. 额外配置注意事项

  • 确保在Azure AD中,APP_2已被授予APP_1的应用权限(客户端凭证流只能用应用权限,不能用委派权限),并且完成了管理员同意。
  • 检查APP_1的暴露API设置,确认已添加对应的权限(比如默认的access_as_application或自定义权限),并分配给APP_2。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 01:40:11