programing

API 백엔드에서 AWS Cognito의 JWT를 확인하는 방법은 무엇입니까?

yoursource 2021. 1. 15. 19:49
반응형

API 백엔드에서 AWS Cognito의 JWT를 확인하는 방법은 무엇입니까?


Angular2 단일 페이지 앱과 ECS에서 실행되는 REST API로 구성된 시스템을 구축하고 있습니다. API는 .Net / Nancy 에서 실행 되지만 변경 될 수 있습니다.

Cognito를 사용해보고 싶습니다. 이것이 인증 워크 플로를 상상 한 방법입니다.

  1. SPA가 사용자를 로그인하고 JWT를받습니다.
  2. SPA는 모든 요청과 함께 JWT를 REST API로 보냅니다.
  3. REST API는 JWT가 인증되었는지 확인합니다.

내 질문은 3 단계에 관한 것입니다. 내 서버 (또는 내 상태 비 저장, 자동 확장,로드 밸런싱 된 Docker 컨테이너)가 토큰이 인증되었는지 어떻게 확인할 수 있습니까? "서버"는 JWT 자체를 발행하지 않았기 때문에 자체 보안 비밀을 사용할 수 없습니다 ( 여기 의 기본 JWT 예제에 설명 됨 ).

Cognito 문서를 읽고 많이 봤지만 서버 측에서 JWT로 무엇을해야하는지에 대한 좋은 가이드 라인을 찾을 수 없습니다.


내가 문서를 제대로 읽지 않은 것으로 밝혀졌습니다. 여기에 설명되어 있습니다 ( "웹 API에서 ID 토큰 및 액세스 토큰 사용"으로 스크롤).

API 서비스는 Cognito의 암호를 다운로드하고이를 사용하여 수신 된 JWT를 확인할 수 있습니다. 완전한.

편집하다

Groady의 코멘트 @ 포인트에 :하지만 어떻게 당신은 토큰의 유효성을 검사합니까? 이를 위해 jose4j 또는 nimbus (둘 다 Java) 와 같은 전투 테스트를 거친 라이브러리를 사용하고 처음부터 직접 검증을 구현하지 마십시오.

다음 은 최근에 java / dropwizard 서비스에서이를 구현해야 할 때 시작된 nimbus를 사용하는 Spring Boot의 구현 예입니다.


NodeJS에서 서명을 확인하는 방법은 다음과 같습니다.

var jwt = require('jsonwebtoken');
var jwkToPem = require('jwk-to-pem');
var pem = jwkToPem(jwk);
jwt.verify(token, pem, function(err, decoded) {
  console.log(decoded)
});


// Note : You can get jwk from https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json 

비슷한 문제가 있었지만 API 게이트웨이를 사용하지 않았습니다. 제 경우에는 AWS Cognito Developer Authenticated identity route를 통해 얻은 JWT 토큰의 서명을 확인하고 싶었습니다.

여러 사이트의 많은 포스터와 마찬가지로 외부에서 즉, 서버 측 또는 스크립트를 통해 AWS JWT 토큰의 서명을 확인하는 데 필요한 비트를 정확히 맞추는 데 문제가있었습니다.

AWS JWT 토큰 서명확인 하고 요점을 파악한 것 같습니다 . PyCrypto의 Crypto.Signature에서 pyjwt 또는 PKCS1_v1_5c를 사용하여 AWS JWT / JWS 토큰을 확인합니다.

네, 제 경우에는 파이썬 이었지만 노드에서도 쉽게 할 수 있습니다 (npm install jsonwebtoken jwk-to-pem 요청).

나는 이것을 알아 내려고 할 때 나는 대부분 옳은 일을하고 있었지만 파이썬 딕셔너리 순서와 같은 약간의 뉘앙스가 있거나, 거기에 json 표현이 부족했기 때문에 주석에서 몇 가지 문제를 강조하려고했습니다.

바라건대 누군가 어딘가에 도움이 될 수 있습니다.


인증 코드 부여 흐름 실행

다음을 가정합니다.

  • AWS Cognito에서 사용자 풀을 올바르게 구성하고
  • 다음을 통해 가입 / 로그인하고 액세스 코드를 얻을 수 있습니다.

    https://<your-domain>.auth.us-west-2.amazoncognito.com/login?response_type=code&client_id=<your-client-id>&redirect_uri=<your-redirect-uri>
    

브라우저가 다음으로 리디렉션되어야합니다. <your-redirect-uri>?code=4dd94e4f-3323-471e-af0f-dc52a8fe98a0


이제 해당 코드를 백엔드로 전달하고 토큰을 요청해야합니다.

POST https://<your-domain>.auth.us-west-2.amazoncognito.com/oauth2/token

  • 사용자의 설정 Authorization에 헤더를 Basic사용 username=<app client id>하고 password=<app client secret>AWS Cognito에 구성된 앱 클라이언트 당
  • 요청 본문에 다음을 설정하십시오.
    • grant_type=authorization_code
    • code=<your-code>
    • client_id=<your-client-id>
    • redirect_uri=<your-redirect-uri>

성공하면 백엔드가 base64로 인코딩 된 토큰 세트를 수신해야합니다.

{
    id_token: '...',
    access_token: '...',
    refresh_token: '...',
    expires_in: 3600,
    token_type: 'Bearer'
}

이제 문서 에 따르면 백엔드는 다음을 통해 JWT 서명을 검증해야합니다.

  1. ID 토큰 디코딩
  2. 로컬 키 ID (아이)와 공개 키드 비교
  3. 공개 키를 사용하여 JWT 라이브러리를 사용하여 서명을 확인합니다.

AWS Cognito는 각 사용자 풀에 대해 두 쌍의 RSA 암호화 키를 생성하므로 토큰을 암호화하는 데 사용 된 키를 파악해야합니다.

다음 은 JWT 확인을 보여주는 NodeJS 스 니펫입니다.

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')

간단한 대답 :
다음 끝점에서 사용자 풀에 대한 공개 키를 얻을 수 있습니다.이 공개 키를
https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
사용하여 토큰을 성공적으로 디코딩하면 토큰이 유효하지 않으면 위조됩니다.


긴 답변 :
코크 릿을 통해 성공적으로 인증하면 액세스 및 ID 토큰을받습니다. 이제이 토큰이 변조되었는지 여부를 확인하려고합니다. 전통적으로 우리는 토큰이 유효한지 확인하기 위해 이러한 토큰을 인증 서비스 (처음에이 토큰을 발행 한)로 다시 보냅니다. 이러한 시스템 은 비밀 키를 사용하여 페이로드를 암호화하는 symmetric key encryption것과 같은 알고리즘 HMAC을 사용하므로이 시스템 만이 토큰이 유효한지 여부를 알 수 있습니다.
기존 인증 JWT 토큰 헤더 :

{
   "alg": "HS256",
   "typ": "JWT"
}

여기에 사용 된 암호화 알고리즘은 대칭입니다

-HMAC + SHA256 그러나 Cognito와 같은 최신 인증 시스템 은 공개 및 개인 키 쌍을 사용하여 페이로드를 암호화하는 asymmetric key encryption것과 같은 알고리즘 RSA을 사용합니다. 페이로드는 개인 키를 사용하여 암호화되지만 공개 키를 통해 디코딩 할 수 있습니다. 이러한 알고리즘을 사용하는 주요 이점은 토큰이 유효한지 여부를 알리기 위해 단일 인증 서비스를 요청할 필요가 없다는 것입니다. 누구나 공개 키에 접근 할 수 있기 때문에 누구나 토큰의 유효성을 확인할 수 있습니다. 유효성 검사로드는 상당히 분산되어 있으며 단일 실패 지점이 없습니다.
Cognito JWT 토큰 헤더 :

{
  "kid": "abcdefghijklmnopqrsexample=",
  "alg": "RS256"
}

이 경우에 사용되는 비대칭 암호화 알고리즘-RSA + SHA256


이것은 닷 넷 4.5에서 나를 위해 일하고 있습니다.

    public static bool VerifyCognitoJwt(string accessToken)
    {
        string[] parts = accessToken.Split('.');

        string header = parts[0];
        string payload = parts[1];

        string headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
        JObject headerData = JObject.Parse(headerJson);

        string payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
        JObject payloadData = JObject.Parse(payloadJson);

        var kid = headerData["kid"];
        var iss = payloadData["iss"];

        var issUrl = iss + "/.well-known/jwks.json";
        var keysJson= string.Empty;

        using (WebClient wc = new WebClient())
        {
            keysJson = wc.DownloadString(issUrl);
        }

        var keyData = GetKeyData(keysJson,kid.ToString());

        if (keyData==null)
            throw new ApplicationException(string.Format("Invalid signature"));

        var modulus = Base64UrlDecode(keyData.Modulus);
        var exponent = Base64UrlDecode(keyData.Exponent);

        RSACryptoServiceProvider provider = new RSACryptoServiceProvider();

        var rsaParameters= new RSAParameters();
        rsaParameters.Modulus = new BigInteger(modulus).ToByteArrayUnsigned();
        rsaParameters.Exponent = new BigInteger(exponent).ToByteArrayUnsigned();

        provider.ImportParameters(rsaParameters);

        SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider();
        byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(parts[0] + "." + parts[1]));

        RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(provider);
        rsaDeformatter.SetHashAlgorithm(sha256.GetType().FullName);

        if (!rsaDeformatter.VerifySignature(hash, Base64UrlDecode(parts[2])))
            throw new ApplicationException(string.Format("Invalid signature"));

        return true;
    }

 public class KeyData
    {
        public string Modulus { get; set; }
        public string Exponent { get; set; }
    }

    private static KeyData GetKeyData(string keys,string kid)
    {
        var keyData = new KeyData();

        dynamic obj = JObject.Parse(keys);
        var results = obj.keys;
        bool found = false;

        foreach (var key in results)
        {
            if (found)
                break;

            if (key.kid == kid)
            {
                keyData.Modulus = key.n;
                keyData.Exponent = key.e;
                found = true;
            }
        }

        return keyData;
    }

ReferenceURL : https://stackoverflow.com/questions/40302349/how-to-verify-jwt-from-aws-cognito-in-the-api-backend

반응형