我正在构建一个由Angular2单页面应用程序和在ECS上运行的REST API组成的系统。 API在.Net / Nancy上运行,但这可能会发生变化。
我想尝试一下Cognito,这就是我想象的身份验证工作流程:
我的问题是关于第3步。我的服务器(或者更确切地说:我的无状态,自动扩展,负载平衡的Docker容器)如何验证令牌是否可信?自“服务器”以来没有发布JWT本身,它不能使用自己的秘密(如基本的JWT示例here中所述)。
我已经阅读了Cognito文档并搜索了很多内容,但是我找不到任何关于如何在服务器端使用JWT的好指南。
答案 0 :(得分:28)
原来我没有正确阅读文档。它解释了here(向下滚动到“在Web API中使用ID令牌和访问令牌”)。
API服务可以下载Cognito的秘密并使用它们来验证收到的JWT。完美。
修改强>
@ Groady的评论很明确:但如何验证令牌?我会说使用经过实战考验的库,如jose4j或nimbus(两者都是Java),并且不要自己从头开始实施验证。
Here是使用nimbus的Spring Boot示例实现,当我最近不得不在java / dropwizard服务中实现它时,我开始使用它。
答案 1 :(得分:20)
这是验证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
答案 2 :(得分:8)
我有类似的问题,但没有使用API网关。在我的情况下,我想验证通过AWS Cognito Developer Authenticated身份路由获得的JWT令牌的签名。
与各个网站上的许多海报一样,我无法拼凑出我需要在外部验证AWS JWT令牌签名的位,即服务器端或脚本
我想我已经弄明白并向verify an AWS JWT token signature提出了一个要点。它将从PyCrypto中的Crypto.Signature验证带有pyjwt或PKCS1_v1_5c的AWS JWT / JWS令牌
所以,是的,在我的情况下这是python,但它在节点中也很容易实现(npm install jsonwebtoken jwk-to-pem request)。
我试图强调评论中的一些问题,因为当我试图解决这个问题时,我主要是做正确的事,但有一些细微差别,如python dict排序,或者缺少json和json表示。
希望它可以帮助某个人。
答案 3 :(得分:5)
cognito-jwt-verifier是一个微型npm软件包,用于以最小的依赖关系验证ID并访问从您的节点/ Lambda后端中的AWS Cognito获得的JWT令牌。
免责声明:我是这个的作者。我想出了它,是因为找不到适合我的所有物品:
用法(有关更多详细示例,请参见github repo):
const { verifierFactory } = require('@southlane/cognito-jwt-verifier')
const verifier = verifierFactory({
region: 'us-east-1',
userPoolId: 'us-east-1_PDsy6i0Bf',
appClientId: '5ra91i9p4trq42m2vnjs0pv06q',
tokenType: 'id', // either "access" or "id"
})
const token = 'eyJraWQiOiI0UFFoK0JaVE...' // clipped
try {
const tokenPayload = await verifier.verify(token)
} catch (e) {
// catch error and act accordingly, e.g. throw HTTP 401 error
}
答案 4 :(得分:2)
执行授权码授予流程
假设您:
能够通过以下方式注册/登录并获取访问代码:
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
,并在AWS Cognito中配置的每个应用程序客户端使用username=<app client id>
和password=<app client secret>
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'
}
现在,根据documentation,您的后端应该通过以下方式验证JWT签名:
由于AWS Cognito为每个用户池生成了两对RSA加密密钥,因此您需要确定使用哪个密钥来加密令牌。
这是一个 NodeJS 代码段,用于演示如何验证JWT。
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')
答案 5 :(得分:2)
您可以在此处从Lambda代码中获得见识
https://github.com/awslabs/aws-support-tools/tree/master/Cognito/decode-verify-jwt
在Golang https://gist.github.com/tmaiaroto/e2ee5e88fc6ae035307d7c5ee71a99cf
答案 6 :(得分:1)
简短答案:
您可以从以下端点获取用户池的公钥:
https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
如果您使用此公钥成功解码了令牌,则该令牌有效,否则将被伪造。
长答案:
通过cognito成功进行身份验证后,您将获得访问权限和ID令牌。现在,您要验证此令牌是否已被篡改。传统上,我们会将这些令牌发送回身份验证服务(该服务首先发布此令牌),以检查令牌是否有效。这些系统使用symmetric key encryption
之类的HMAC
算法来使用密钥对有效负载进行加密,因此只有该系统才能知道此令牌是否有效。
传统的auth JWT令牌标头:
{
"alg": "HS256",
"typ": "JWT"
}
请注意,此处使用的加密算法是对称的-HMAC + SHA256
但是,像Cognito这样的现代身份验证系统使用asymmetric key encryption
之类的RSA
算法来使用一对公钥和私钥对有效载荷进行加密。有效载荷使用私钥加密,但可以通过公钥解码。使用这种算法的主要优点是,我们不必请求单个身份验证服务即可知道令牌是否有效。由于每个人都可以访问公钥,因此任何人都可以验证令牌的有效性。验证的负载是平均分配的,没有单点故障。
Cognito JWT令牌标头:
{
"kid": "abcdefghijklmnopqrsexample=",
"alg": "RS256"
}
在这种情况下使用的非对称加密算法-RSA + SHA256
答案 7 :(得分:1)
有人还编写了一个名为cognitojwt的python程序包,该程序包可在异步/同步模式下工作,以解码和验证Amazon Cognito JWT。
答案 8 :(得分:0)
这在dot net 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;
}
答案 9 :(得分:0)
这是基于Derek(answer)的详尽解释。我已经能够为PHP创建一个工作示例。
我已经使用https://github.com/firebase/php-jwt进行pem创建和代码验证。
收到一组以base64编码的令牌后,将使用此代码。
<?php
require_once(__DIR__ . '/vendor/autoload.php');
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use Firebase\JWT\BeforeValidException;
function debugmsg($msg, $output) {
print_r($msg . "\n");
}
$tokensReceived = array(
'id_token' => '...',
'access_token' => '...',
'refresh_token' => '...',
'expires_in' => 3600,
'token_type' => 'Bearer'
);
$idToken = $tokensReceived['id_token'];
// 'https://cognito-idp.us-west-2.amazonaws.com/<pool-id>/.well-known/jwks.json'
$keys = json_decode('<json string received from jwks.json>');
$idTokenHeader = json_decode(base64_decode(explode('.', $idToken)[0]), true);
print_r($idTokenHeader);
$remoteKey = null;
$keySets = JWK::parseKeySet($keys);
$remoteKey = $keySets[$idTokenHeader['kid']];
try {
print_r("result: ");
$decoded = JWT::decode($idToken, $remoteKey, array($idTokenHeader['alg']));
print_r($decoded);
} catch(Firebase\JWT\ExpiredException $e) {
debugmsg("ExpiredException","cognito");
} catch(Firebase\JWT\SignatureInvalidException $e) {
debugmsg("SignatureInvalidException","cognito");
} catch(Firebase\JWT\BeforeValidException $e) {
debugmsg("BeforeValidException","cognito");
}
?>