如何验证由MS Azure AD生成的JWT id_token?

19

我有一个使用ADAL-JS (和 adal-angular) 的 AngularJS SPA Web 应用程序,它设置了身份验证以针对我们在 MS Azure 中的公司 AD 进行身份验证。登录流程似乎工作正常,并且 SPA 接收到一个 id_token。

接下来,当用户单击按钮时,SPA 将向我在 AWS API Gateway 上托管的 REST API 发出请求。我在 Authorization: Bearer <id_token> 标头中传递 id_token。API Gateway 按预期接收到头信息,现在必须确定给定的令牌是否有效,以允许或拒绝访问。

我有一个示例标记,在https://jwt.io/上正确解析,但是我迄今为止未能找到应该用于验证签名的公钥或证书。我查看了以下内容:

  • https://login.microsoftonline.com/common/discovery/v2.0/keys
  • 我认为应该使用与JWT id_token中的kid和x5t属性匹配的https://login.microsoftonline.com/common/discovery/keys键的x5c属性值(当前为a3QN0BZS7s4nN-BdrjbF0Y_LdMM,对应的x5c值以“MIIDBTCCAe2gAwIBAgIQY…”开头)。但是,https://jwt.io/网页显示“无效签名”(我还尝试用“-----BEGIN CERTIFICATE-----”和“-----END CERTIFICATE-----”包裹密钥值)。

    此外,是否有(可能是Python的)库能够方便地验证上述id_token(这样我就不必自己获取签名密钥了)?最好能找到的(Python的ADAL)似乎并未提供此功能?


    你看过这个吗? - 4c74356b41
    嗨 @4c74356b41 - 感谢链接!可能会很方便,但是...我没看到该库中检查令牌签名的地方在哪里? - FOR
    3个回答

    28

    到目前为止,我能够提供的最佳解决方案是:

    https://login.microsoftonline.com/common/discovery/keyshttps://login.microsoftonline.com/common/discovery/v2.0/keys中获取证书(在x5c属性数组中的第一个值),并匹配来自id_token的kidx5t

    将证书-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----包装起来(换行符似乎很重要),并将结果作为公钥(与id_token一起,在https://jwt.io/上)使用。

    当然,您实际的用例可能是要让某些程序验证传入的JWT id_tokens,因此您的目标不会仅仅是通过https://jwt.io/上的Web UI获取令牌进行验证。

    例如,在Python中,我需要这样做:

    #!/usr/bin/env python
    
    import jwt
    from cryptography.x509 import load_pem_x509_certificate
    from cryptography.hazmat.backends import default_backend
    
    PEMSTART = "-----BEGIN CERTIFICATE-----\n"
    PEMEND = "\n-----END CERTIFICATE-----\n"
    
    mspubkey = "The value from the x5c property"
    IDTOKEN = "the id_token to be validated"
    tenant_id = "your tenant id"
    
    cert_str = PEMSTART + mspubkey + PEMEND
    cert_obj = load_pem_x509_certificate(cert_str, default_backend())
    public_key = cert_obj.public_key()
    
    decoded = jwt.decode(IDTOKEN, public_key, algorithms=['RS256'], audience=tenant_id)
    if decoded:
        print "Decoded!"
    else:
        print "Could not decode token."
    

    关于各种编程语言的JWT库列表,请参见JWT网站。 我正在使用pyjwt,以及它的cryptography依赖项(它具有二进制依赖项,因此需要为目标操作系统构建和打包)。

    当然,您还可以验证其他详细信息,例如声明,如此处推荐的那样。


    2
    我以前从未编写过Python代码。根据我的理解,您不应该这样做,因为Azure AD使用的签名密钥可能会非常频繁地更换,而且从今年开始,Microsoft甚至不再通知,因为期望是程序/应用程序可以无缝处理它。请参见以下链接:https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-signing-key-rollover - Gopi Kolla
    1
    @Gopi Kolla,您说得完全正确。事实上,在这个最初的代码之外(旨在找出令牌验证的工作原理),我已经按照他们建议每天定期获取MS公钥,并将它们保存在可以随时使用的地方。 但是我很感兴趣...如果一个人不应该做我一直盯着的所有工作(并慢慢地削减)...那么如何使用MS Azure AD进行身份验证而不是.NET应用程序(在Azure上运行)? - FOR
    2
    当我查询https://login.microsoftonline.com/common/discovery/keys时,我得到了2个密钥。我该如何知道使用哪个密钥?在解码令牌后,我能够在标头中看到x5t,但这是在解码之后。我应该尝试两个并查看哪一个成功吗? - sargeMonkey
    2
    @sargeMonkey 请查看此处的“令牌验证”:https://francescorizzi.github.io/AWSomesauce/articles/BAS4-pws/index.html“在列出的密钥中,找到与要验证的令牌中的kid值相匹配的具有kid (和x5t)属性的密钥”并查看此代码(TokenValidator):https://github.com/FrancescoRizzi/AWSomesauce/blob/master/articles/BAS4-pws/custauth/custauth.py - FOR
    3
    我遇到了错误 TypeError: from_buffer() cannot return the address of a unicode object。我认为问题出在语句 cert_obj = load_pem_x509_certificate(cert_str, default_backend()) 上。 - Jacob Nelson
    显示剩余3条评论

    2

    对于JVM解决方案,使用com.nimbusds:numbus-jose-jwt:4.29是解析和验证已签名RSA256 id_token最直接的方法。以下Scala代码使用JSON Web Key解析JWT令牌:

        val jwt = SignedJWT.parse(token)
    
        val n = new Base64URL("Your Modulus Component of RSA Key")
        val e = new Base64URL("AQAB")
        val rsaKey = new RSAKey.Builder(n, e).keyUse(KeyUse.SIGNATURE).algorithm(JWSAlgorithm.RS256).build()
    
        val verified = jwt.verify(new RSASSAVerifier(rsaKey))
    

    您的应用程序仍需要动态地从Azure Active Directory B2C的discovery/v2.0/key获取JSON Web Key Set,以获取可能由AAD B2C使用的密钥集。为了提高效率,这些内容应该被缓存并且TTL不超过24小时。


    0
    此外,是否有(可能是Python的)库可以方便地验证给定的id_token,就像上面的情况一样(这样我就不必自己抓取签名密钥了)...我能找到的最好的(Python的ADAL)似乎没有提供这个功能?
    值得一提的是,ADAL Python的继承者MSAL Python在幕后验证了id token,并为您的应用程序提供了id token中解码的声明。因此,您的应用程序 - 刚刚通过MSAL Python获得了该id token - 可以在本地使用它。
    接下来,当用户点击按钮时,SPA会向我在AWS API Gateway上托管的REST API发出请求。我在Authorization: Bearer <id_token>头中传递了id_token。API Gateway按预期接收到了标头,现在必须确定给定的令牌是否有效,以允许或拒绝访问。
    通常情况下,ID令牌不会用于“在Authorization: Bearer <id_token>头部”发送给第三方进行授权目的。可能因人而异。

    这对OP的问题有效吗?听起来他们正在使用FE客户端库提供的令牌,将其发送到BE需要进行验证。我遇到了类似的困境:在客户端使用@ azure / msal-react并需要发送到Django / DRF服务器以验证令牌是否有效并且属于发送它的人。MSAL Python是否具有此功能或者它是否适用于通过它检索的令牌不是完全清楚的。 - cjones
    @cjones,我目前正在尝试在Django API上验证React中的jwt token,这已经让我头疼了一个星期。如果您已经解决了问题并且有示例代码,请帮忙一下。 - Elliot13

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接