如何验证Azure Ad生成的JWT?

Esb*_*rdt 10 python decode public-key-encryption jwt azure-active-directory

问题

当我从 Azure AD 使用 Python 收到 JWK 时,我想对其进行验证和解码。但是,我不断收到错误“签名验证失败”。

我的设置

我有以下设置:

  1. Azure 设置
    在 Azure 中,我创建了一个应用程序注册,设置为“仅限个人 Microsoft 帐户”。
  2. Python 设置
    在 Python 中,我使用 MSAL 包来接收令牌。我使用来自 Azure 的公钥来验证令牌。

代码

使用 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'])
Run Code Online (Sandbox Code Playgroud)

如果我将令牌放入https://jwt.io/这样的网站,一切看起来都很好。接下来,我需要来自 Azure 的公钥来验证令牌。

import requests
response = requests.get("https://login.microsoftonline.com/common/discovery/keys")
keys = response.json()['keys']
Run Code Online (Sandbox Code Playgroud)

为了将公钥与令牌相匹配,我在令牌标头中使用“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
Run Code Online (Sandbox Code Playgroud)

现在我有来自 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
)
    
Run Code Online (Sandbox Code Playgroud)

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'
Run Code Online (Sandbox Code Playgroud)

我需要做的最后一件事是使用公钥验证令牌。

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"
)
Run Code Online (Sandbox Code Playgroud)

我得到的结果是:

jwt.exceptions.InvalidSignatureError: Signature verification failed
Run Code Online (Sandbox Code Playgroud)

我也尝试过

我还尝试遵循这个流行指南:How to verify JWT id_token generated by MS Azure AD? 将 x5c 放入证书前缀和后缀中只会生成无效格式的错误。

下一步是什么?

大家能看出有什么明显的错误吗?我的主要猜测是受众发行者有问题,但我无法确定问题是什么,而且微软的文档一如既往地糟糕。另外,Azure中的应用程序注册中有一个密钥,但它似乎也不起作用。

更新

所以事实证明我的验证码是正确的,但我试图验证错误的令牌。创建轻微修改后,我现在收到一个 id_token,可以对其进行解码和验证。

MaF*_*aFF 0

至少有 2 个选项可用于解码 Microsoft Azure AD ID 令牌:

选项 1:使用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}
)
Run Code Online (Sandbox Code Playgroud)

选项 2:使用msal'sdecode_id_token

微软的包msal提供了一个解码 id token 的函数。代码简单地变成:

from msal.oauth2cli.oidc import decode_id_token 
decode_id_token(id_token=your-token-id, client_id=your-client-id)
Run Code Online (Sandbox Code Playgroud)