如何在Python应用中验证Microsoft Access Token?签名验证失败排查
解决Azure AD Access Token签名验证失败问题
问题根源
你遇到的签名验证失败,核心原因是ID Token和Access Token的issuer格式、公钥端点可能不匹配,且示例代码中的issuer是v1版本格式,若你的Access Token是v2版本,就会导致验证不通过;另外Access Token的aud(受众)必须严格匹配API的应用标识符,而非客户端ID。
具体解决步骤
1. 确认Access Token版本
先解码Token的未验证字段,查看iss值:
- v1版本Access Token的
iss格式:https://sts.windows.net/{租户ID}/ - v2版本Access Token的
iss格式:https://login.microsoftonline.com/{租户ID}/v2.0
可以用以下代码快速查看:
from jose import jwt unverified_claims = jwt.get_unverified_claims("你的Access Token") print(unverified_claims["iss"]) print(unverified_claims["aud"])
2. 匹配对应版本的公钥端点
根据Token版本选择公钥获取地址:
- v1 Token:
https://login.microsoftonline.com/{租户ID}/discovery/v1.0/keys - v2 Token:
https://login.microsoftonline.com/{租户ID}/discovery/v2.0/keys
3. 修正验证代码
调整jwt.decode的参数,确保issuer与Token的iss完全一致,audience与Token的aud(即你的API应用ID URI,比如api://{API客户端ID})完全匹配。
完整验证示例代码:
from jose import jwt from jose.backends import RSAKey import requests def validate_access_token(token, api_audience, tenant_id): # 获取未验证的头部和claims,用于匹配公钥和确认issuer unverified_headers = jwt.get_unverified_headers(token) unverified_claims = jwt.get_unverified_claims(token) kid = unverified_headers["kid"] issuer = unverified_claims["iss"] # 根据Token版本获取公钥 if "/v2.0" in issuer: jwks_uri = f"https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys" else: jwks_uri = f"https://login.microsoftonline.com/{tenant_id}/discovery/v1.0/keys" jwks = requests.get(jwks_uri).json() # 匹配对应kid的公钥 rsa_key = next((key for key in jwks["keys"] if key["kid"] == kid), None) if not rsa_key: raise ValueError("找不到匹配的签名公钥") # 验证Token payload = jwt.decode( token, RSAKey(rsa_key, algorithm="RS256"), algorithms=["RS256"], audience=api_audience, issuer=issuer ) return payload # 使用示例 API_AUDIENCE = "api://你的API客户端ID" # 必须与Access Token的aud一致 TENANT_ID = "你的租户ID" access_token = "待验证的Access Token" try: payload = validate_access_token(access_token, API_AUDIENCE, TENANT_ID) print("Token验证通过,Payload:", payload) except Exception as e: print("Token验证失败:", str(e))
额外注意事项
- 确保客户端请求Access Token时,请求的
scope包含你的API标识符(比如api://{API客户端ID}/.default),这样返回的Token的aud才会匹配你的API - 不要硬编码
issuer,通过未验证的claims动态获取,兼容v1/v2版本Token - 若你的API允许多租户访问,可将
tenant_id替换为common,但需确保公钥端点和issuer对应
内容的提问来源于stack exchange,提问作者Ghasem
相关产品推荐
相关产品推荐

