API 백엔드에서 AWS Cognito의 JWT를 확인하는 방법은 무엇입니까?
Angular2 단일 페이지 앱과 ECS에서 실행되는 REST API로 구성된 시스템을 구축하고 있습니다. API는 .Net / Nancy 에서 실행 되지만 변경 될 수 있습니다.
Cognito를 사용해보고 싶습니다. 이것이 인증 워크 플로를 상상 한 방법입니다.
- SPA가 사용자를 로그인하고 JWT를받습니다.
- SPA는 모든 요청과 함께 JWT를 REST API로 보냅니다.
- 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 서명을 검증해야합니다.
- ID 토큰 디코딩
- 로컬 키 ID (아이)와 공개 키드 비교
- 공개 키를 사용하여 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
'programing' 카테고리의 다른 글
유닉스 내림차순 정렬 (0) | 2021.01.15 |
---|---|
git log 하나의 커밋 ID 만 표시 (0) | 2021.01.15 |
Dependency-reduced-pom.xml을 기본 디렉토리에 추가하는 Maven Shade 플러그인 (0) | 2021.01.14 |
"ReferenceError : expect is not defined"오류 메시지를 어떻게 해결할 수 있습니까? (0) | 2021.01.14 |
`struct X typedef`와`typedef struct X`의 의미는 무엇입니까? (0) | 2021.01.14 |