为啥 AWS Cognito 对 JWT 使用多个公有密钥?

Posted

技术标签:

【中文标题】为啥 AWS Cognito 对 JWT 使用多个公有密钥?【英文标题】:Why does AWS Cognito use multiple public keys for JWTs?为什么 AWS Cognito 对 JWT 使用多个公有密钥? 【发布时间】:2017-10-14 04:34:00 【问题描述】:

当我下载特定用户池的 JWT 集时,可在以下位置获得:https://cognito-idp.region.amazonaws.com/userPoolId/.well-known/jwks.json

JSON 包含 2 个键。我为池创建的所有用户似乎只使用这些密钥之一。

每个用户池有多个密钥的原因是什么?

【问题讨论】:

用于处理密钥轮换期间的重叠? @Michael-sqlbot 你能详细说明一下吗?在阅读您的评论之前,我一直在使用数组中两个键中的第一个,并且我对键轮换一无所知。我刚刚看到了底部的代码示例:aws.amazon.com/blogs/mobile/…,它查找所需的密钥。我在一个似乎工作了一段时间的 api 中只使用了第一个,所以现在我担心它可能会在某个时候失败。 【参考方案1】:

根据the documentation:

Amazon Cognito 为每个用户池生成两对 RSA 加密密钥。其中一个私钥用于对令牌进行签名。

大概是出于安全原因。从有限的反复试验看来,一个用于加密 id 令牌,另一个用于加密 访问令牌。或者也许目的是支持密钥轮换(正如@Michael-sqlbot 所建议的那样)。


一旦你理解了为什么(或不明白),问题就变成了如何用这两个键来做。

再次参考文档,验证您需要的 JWT 签名:

    解码 ID 令牌 将本地密钥 ID(孩子)与公共孩子进行比较 使用公钥来验证使用您的 JWT 库的签名。

步骤 1 和 2 是确定用于加密 JWT 的 RSA 加密密钥所必需的。

import jsonwebtoken from 'jsonwebtoken'
import jwkToPem from 'jwk-to-pem'

const jsonWebKeys = [  // from https://cognito-idp.us-west-2.amazonaws.com/<UserPoolId>/.well-known/jwks.json
    
        "alg": "RS256",
        "e": "AQAB",
        "kid": "ABCDEFGHIJKLMNOPabc/1A2B3CZ5x6y7MA56Cy+6ubf=",
        "kty": "RSA",
        "n": "...",
        "use": "sig"
    ,
    
        "alg": "RS256",
        "e": "AQAB",
        "kid": "XYZAAAAAAAAAAAAAAA/1A2B3CZ5x6y7MA56Cy+6abc=",
        "kty": "RSA",
        "n": "...",
        "use": "sig"
    
]

function validateToken(token) 
    const header = decodeTokenHeader(token)  // "kid":"XYZAAAAAAAAAAAAAAA/1A2B3CZ5x6y7MA56Cy+6abc=", "alg": "RS256"
    const jsonWebKey = getJsonWebKeyWithKID(header.kid)
    verifyJsonWebTokenSignature(token, jsonWebKey, function(err, decodedToken) 
        if (err) 
            console.error(err)
         else 
            console.log(decodedToken)
        
    )


function decodeTokenHeader(token) 
    const [headerEncoded] = token.split('.')[0]
    const buff = new Buffer(headerEncoded, 'base64')
    const text = buff.toString('ascii')
    return JSON.parse(text)


func getJsonWebKeyWithKID(kid) 
    for (let jwk of jsonWebKeys) 
        if (jwk.kid == kid) 
            return jwk
        
    
    return null


function verifyJsonWebTokenSignature(token, jsonWebKey, clbk) 
    const pem = jwkToPem(jsonWebKey)
    jsonwebtoken.verify(token, pem,  algorithms: ['RS256'] , function(err, decodedToken) 
        return clbk(err, decodedToken)
    )


validateToken('xxxxxxxxx.XXXXXXXX.xxxxxxxx')

【讨论】:

【参考方案2】:

来自decoding-aws-cognito-jwt

“首先,从下面的 url 获取 JSON Web Key Set (JWKS) 文件。将 region 和 userPoolId 替换为您的 Cognito 用户池的配置。

https://cognito-idp.region.amazonaws.com/userPoolId/.well-known/jwks.json

这个 JSON 散列是一组 JWK,因此得名 JWKS。它可能包含也可能不包含超过 1 个 JWK。如果有多个 JWK,我们应该使用哪一个来进行解码?

在JWT中,在token的header部分有一个key叫做kid,也就是JWT的第一个hash。而在每个 JWK 中,还有一个名为 Kid 的密钥。因此,我们应该使用具有匹配 Kid 值的 JWK 来解码 JWT。”

【讨论】:

以上是关于为啥 AWS Cognito 对 JWT 使用多个公有密钥?的主要内容,如果未能解决你的问题,请参考以下文章

AWS Cognito 基于 JWT 属性的路由

ASP.NET Core AWS Cognito JWT

AWS Cognito JWT 令牌验证

更改 AWS Cognito 访问令牌 JWT 中的加密算法

如何在 Go 中验证来自 AWS Cognito 的 JWT 令牌?

如何修改 AWS Cognito JWT Tokens,我们正在使用 NodeJS 技术问题