当我从 Azure AD 使用 Python 收到 JWK 时,我想对其进行验证和解码。但是,我不断收到错误“签名验证失败”。
我有以下设置:
使用 Azure 门户中的凭据,我设置了一个用于获取令牌的客户端。
import msal
ad_auth_client = msal.ConfidentialClientApplication(
client_id = client_id,
client_credential = client_secret,
authority = "https://login.microsoftonline.com/consumers"
)
my_token = ad_auth_client.acquire_token_for_client(scopes=['https://graph.microsoft.com/.default'])
如果我将令牌放入像 https://jwt.io/ 这样的网站,一切看起来都很好。接下来,我需要来自 Azure 的公钥来验证令牌。
import requests
response = requests.get("https://login.microsoftonline.com/common/discovery/keys")
keys = response.json()['keys']
为了将公钥与令牌相匹配,我在令牌标头中使用“kid”。我还知道使用哪种算法进行加密。
import jwt
token_headers = jwt.get_unverified_header(my_token['access_token'])
token_alg = token_headers['alg']
token_kid = token_headers['kid']
public_key = None
for key in keys:
if key['kid'] == token_kid:
public_key = key
现在我有来自 Azure 的正确公钥来验证我的令牌,但问题是它是 JWT 密钥。在使用它进行解码之前,我需要将其转换为 RSA PEM 密钥。
from cryptography.hazmat.primitives import serialization
rsa_pem_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(public_key))
rsa_pem_key_bytes = rsa_pem_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
这就是 Azure 公钥的样子:
b'-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyr3v1uETrFfT17zvOiy0\n1w8nO+1t67cmiZLZxq2ISDdte9dw+IxCR7lPV2wezczIRgcWmYgFnsk2j6m10H4t\nKzcqZM0JJ/NigY29pFimxlL7/qXMB1PorFJdlAKvp5SgjSTwLrXjkr1AqWwbpzG2\nyZUNN3GE8GvmTeo4yweQbNCd+yO/Zpozx0J34wHBEMuaw+ZfCUk7mdKKsg+EcE4Z\nv0Xgl9wP2MpKPx0V8gLazxe6UQ9ShzNuruSOncpLYJN/oQ4aKf5ptOp1rsfDY2IK\n9frtmRTKOdQ+MEmSdjGL/88IQcvCs7jqVz53XKoXRlXB8tMIGOcg+ICer6yxe2it\nIQIDAQAB\n-----END PUBLIC KEY-----\n'
我需要做的最后一件事是使用公钥验证令牌。
decoded_token = jwt.decode(
my_token['access_token'],
key=rsa_pem_key_bytes,
verify=True,
algorithms=[token_alg],
audience=[client_id],
issuer="https://login.microsoftonline.com/consumers"
)
我得到的结果是:
jwt.exceptions.InvalidSignatureError: Signature verification failed
我也尝试遵循这个流行的指南:How to verify JWT id_token generated by MS Azure AD? 将 x5c 放入证书前缀和后缀中只会生成无效格式的错误。
你们能看到任何明显的错误吗?我的主要猜测是 audience 或 issuer 有问题,但我无法确定它是什么,而且微软的文档一如既往地糟糕。另外,Azure 中的应用程序注册中有一个密钥,但它似乎也不起作用。
所以事实证明我的验证码是正确的,但我试图验证错误的令牌。创建轻微修改后,我现在收到一个 id_token,可以对其进行解码和验证。
您获取了范围
https://graph.microsoft.com/.default
的令牌,这意味着访问令牌用于 MS Graph API。
您不应该验证这一点,这是 API 的工作目的。
Graph API 也很特殊,它使用与其他 API 不同的签名方法。
尝试改变你的
scopes=['https://graph.microsoft.com/.default']
至
scopes:['[YOUR_CLIENT_ID]/.default']
该问题似乎与您生成的 JWT 用于 MS Graph API 有关,结果 JWT 将在 JWT 标头中包含“随机数”,并且不打算进行验证。
调整是生成具有范围 != https://graph.microsoft.com/.default 的 JWT,但格式为 = "scope": [ "api://client-id/.default" ]。
之后,您可以验证 JWT 标头在 JWT 标头中不再具有“nonce”,并且像上面的代码这样的正常验证将起作用。
至少有 2 个选项可用于解码 Microsoft Azure AD ID 令牌:
jwt
OP提供的代码给了我例外
InvalidIssuerError
。即使用 issuer
替换 https://login.microsoftonline.com/{your-tenant-id}
参数对我来说也不起作用。然而,忽略这个参数让我能够解码 ID 令牌。这是完整的代码:
# Get the public keys from Microsoft
import requests
response = requests.get("https://login.microsoftonline.com/common/discovery/keys")
keys = response.json()['keys']
# Format keys as PEM
from cryptography.hazmat.primitives import serialization
rsa_pem_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(public_key))
rsa_pem_key_bytes = rsa_pem_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
# Get algorithm from token header
alg = jwt.get_unverified_header(your-id-token)['alg']
# Decode token
jwt.decode(
your-id-token,
key=rsa_pem_key_bytes,
algorithms=[alg],
verify=True,
audience=[your-client-id],
options={"verify_signature": True}
)
msal
的 decode_id_token
微软的包
msal
提供了解码id令牌的功能。代码简单地变成:
from msal.oauth2cli.oidc import decode_id_token
decode_id_token(id_token=your-token-id, client_id=your-client-id)