如何在Python中验证Azure访问令牌?签名验证失败排查
解决Azure AD访问令牌签名验证失败问题
以下是可能导致你用jwt.decode时出现“Signature verification failed”的原因及对应解决方案:
1. 公钥处理错误
你直接从discovery端点拿的x5c字段内容加证书头尾后作为字符串传入jwt.decode,这是错误的。PyJWT需要的是加密学意义上的公钥对象,而非字符串格式的证书。正确做法是把证书字符串解析成公钥对象:
- 从JWK的
x5c数组中取出第一个元素(Azure AD通常只返回一个),包裹证书头尾后,用cryptography库解析成X.509证书,再提取公钥。
2. 未匹配正确的密钥
每个JWT的header里包含kid(密钥ID),你需要从Azure AD返回的公钥集中找到kid完全匹配的密钥,不能随便拿一个公钥就用。如果密钥不匹配,签名肯定验证失败。
3. 令牌受众(aud)不匹配
确认你传入的audience参数和令牌中的aud声明完全一致:
- 如果用户获取的是针对其他API的访问令牌,
aud会是目标API的客户端ID,而非你的应用ID,这时候验证会失败。 - 可以先用
jwt.get_unverified_payload(token)查看令牌的aud值,确认是否和你的client_id一致。
4. 缺少发行者(issuer)验证
Azure AD的令牌发行者格式为https://login.microsoftonline.com/{tenant_id}/v2.0,验证时最好显式指定issuer参数,避免接受来自其他发行者的伪造令牌。
正确的验证代码示例
先安装依赖:
pip install pyjwt cryptography requests
验证代码:
import jwt import requests from cryptography.x509 import load_pem_x509_certificate from cryptography.hazmat.backends import default_backend def get_azure_ad_public_keys(tenant_id): # 获取Azure AD公钥集 url = f"https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys" resp = requests.get(url) resp.raise_for_status() return resp.json()["keys"] def get_matching_public_key(token, public_keys): # 根据令牌header的kid匹配对应公钥 unverified_header = jwt.get_unverified_header(token) for key in public_keys: if key["kid"] == unverified_header["kid"]: # 解析证书为公钥对象 cert_pem = f"-----BEGIN CERTIFICATE-----\n{key['x5c'][0]}\n-----END CERTIFICATE-----" cert = load_pem_x509_certificate(cert_pem.encode(), default_backend()) return cert.public_key() raise ValueError("未找到与令牌匹配的公钥") def verify_access_token(token, tenant_id, client_id): public_keys = get_azure_ad_public_keys(tenant_id) public_key = get_matching_public_key(token, public_keys) try: decoded_token = jwt.decode( token, public_key, algorithms=["RS256"], audience=client_id, issuer=f"https://login.microsoftonline.com/{tenant_id}/v2.0" ) return decoded_token except jwt.InvalidTokenError as e: raise ValueError(f"令牌验证失败: {str(e)}") # 使用示例 if __name__ == "__main__": TENANT_ID = "你的租户ID" CLIENT_ID = "你的客户端ID" USER_ACCESS_TOKEN = "用户传来的访问令牌" try: decoded = verify_access_token(USER_ACCESS_TOKEN, TENANT_ID, CLIENT_ID) print("验证成功,解码内容:", decoded) except ValueError as e: print("验证失败:", e)
额外排查步骤
- 先用
jwt.get_unverified_header(token)和jwt.get_unverified_payload(token)查看令牌的元数据,确认:alg字段是RS256kid存在且能在公钥集中找到匹配项aud等于你的client_idiss符合Azure AD v2.0的格式
- 确认用户传入的是**访问令牌(access_token)**而非ID令牌(id_token),ID令牌的受众和用途和访问令牌不同。
内容的提问来源于stack exchange,提问作者khoshahmad
相关产品推荐
相关产品推荐

