2

我正在使用从 Microsoft 到客户端的 jwt 令牌来验证从它到 Web API(服务器)的请求。我可以控制客户端(js)和服务器(Python)的代码。

在客户端,我使用以下请求获取令牌(用户通过租户上的密码/2FA 声明):

`https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/authorize
            ?response_type=id_token+token
            &client_id=${CLIENT_ID}
            &redirect_uri=${redirect_uri}
            &scope=openid+email+profile
            &state=${guid()}
            &nonce=${guid()}`

这里guid有一个独特的价值,TENANT_ID是租户,CLIENT_ID也是客户。

获得此令牌后,我将其作为授权标头发送,如下所示:

init = {
    headers: {
       'Authorization': `Bearer ${token}`,
    }
}

return fetch(url, init).then(response => {
    return response.json()
})

然后在服务器上检索令牌并验证它:

if 'Authorization' in request.headers and request.headers['Authorization'].startswith('Bearer '):
    token = request.headers['Authorization'][len('Bearer '):]
    from authlib.jose import jwt
    claims = jwt.decode(token, jwk)

jwk的内容在哪里https://login.microsoftonline.com/{TENANT_ID}/discovery/v2.0/keys

整个流程一直有效,直到验证失败,并出现以下错误:

authlib.jose.errors.InvalidHeaderParameterName: invalid_header_parameter_name: Invalid Header Parameter Names: nonce

这表明令牌的标头包含一个密钥nonce(我验证了它)。

在此处查看Microsoft 的文档,标头上没有对 anonce的引用——只是在有效负载上。

Q1:我在这里做错了什么?

Q2:假设微软是把 nonce 放在错误的地方(头而不是有效负载)的人,是否可以在将 nonce 传递给 jose 的身份验证库之前从头(在服务器端)中删除它?这样做安全吗?

4

1 回答 1

6

请参阅:https ://robertoprevato.github.io/Validating-JWT-Bearer-tokens-from-Azure-AD-in-Python/

以下是我在 API 中验证 Azure AD Id_tokens 的方法:

import base64
import jwt
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization


def ensure_bytes(key):
    if isinstance(key, str):
        key = key.encode('utf-8')
    return key


def decode_value(val):
    decoded = base64.urlsafe_b64decode(ensure_bytes(val) + b'==')
    return int.from_bytes(decoded, 'big')


def rsa_pem_from_jwk(jwk):
    return RSAPublicNumbers(
        n=decode_value(jwk['n']),
        e=decode_value(jwk['e'])
    ).public_key(default_backend()).public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )


# obtain jwks as you wish: configuration file, HTTP GET request to the endpoint returning them;
jwks = {
    "keys": [
        {
            "kty": "RSA",
            "use": "sig",
            "kid": "piVlloQDSMKx...",
            "x5t": "piVlloQDSMKx...",
            "n": "0XhhwpmEpN-jDBapnzhF...",
            "e": "AQAB",
            "x5c": [
                "MIIDBTCCAe2gAwIBAgIQMCJcg...."
            ],
            "issuer": "https://login.microsoftonline.com/{tenant}/v2.0"
        }
    ]
}

# configuration, these can be seen in valid JWTs from Azure B2C:
valid_audiences = ['dd050a67-ebfd-xxx-xxxx-xxxxxxxx'] # id of the application prepared previously

class InvalidAuthorizationToken(Exception):
    def __init__(self, details):
        super().__init__('Invalid authorization token: ' + details)


def get_kid(token):
    headers = jwt.get_unverified_header(token)
    if not headers:
        raise InvalidAuthorizationToken('missing headers')
    try:
        return headers['kid']
    except KeyError:
        raise InvalidAuthorizationToken('missing kid')



def get_jwk(kid):
    for jwk in jwks.get('keys'):
        if jwk.get('kid') == kid:
            return jwk
    raise InvalidAuthorizationToken('kid not recognized')

def get_issuer(kid):
    for jwk in jwks.get('keys'):
        if jwk.get('kid') == kid:
            return jwk.get('issuer')
    raise InvalidAuthorizationToken('kid not recognized')

def get_public_key(token):
    return rsa_pem_from_jwk(get_jwk(get_kid(token)))


def validate_jwt(jwt_to_validate):
    try:
        public_key = get_public_key(jwt_to_validate)
    issuer = get_issuer(kid)


        options = {
            'verify_signature': True,
            'verify_exp': True,  # Skipping expiration date check
            'verify_nbf': False,
            'verify_iat': False,
            'verify_aud': True  # Skipping audience check
        }

        decoded = jwt.decode(jwt_to_validate,
                             public_key,
                             options=options,
                             algorithms=['RS256'],
                             audience=valid_audiences,
                             issuer=issuer)

        # do what you wish with decoded token:
        # if we get here, the JWT is validated
        print(decoded)

    except Exception as ex:
        print('The JWT is not valid!')
        return False
    else:
        return decoded
        print('The JWT is valid!')

于 2020-01-24T18:59:57.480 回答