Un client authentifié avec AWS Amplify Authentication (https://aws-amplify.github.io/docs/js/authentication) envoie un id_token au serveur et valide l'id_token sur un serveur implémenté en Java.
TL;DR
IdTokenValidator.java
package com.exampleawsCognito.jwt;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.interfaces.RSAPublicKey;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import com.auth0.jwk.GuavaCachedJwkProvider;
import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.UrlJwkProvider;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
/**
*Gérer les jetons d'identification obtenus en s'authentifiant avec Cognito
*
* <p>
*Source référencée pour la mise en œuvre
* https://github.com/awslabs/cognito-proxy-rest-service/blob/master/src/main/kotlin/com/budilov/cognito/services/CognitoService.kt
* </p>
*
* @ThreadSafe
*
*/
public class IdTokenValidator {
private static final String AWS_REGION = "ap-northeast-1";
private static final String AWS_COGNITO_USER_POOL_ID = "my_userpool_id";
private static JWTVerifier verifier = null;
private static long verifierExpire = -1;
private static final long VERIFIER_LIVE_MILISEC = 10 * 60 * 1000; //10 minutes
private static final JWT JWT = new JWT();
/**
*constructeur
*/
public IdTokenValidator() {
}
/**
*Valider le jeton d'identification
*
* @Param ID Jeton IDToken à vérifier
* @Charge utile du jeton d'identification si la validation de retour est réussie
*
* @jette InvalidTokenException L'authentification a échoué car la valeur du jeton d'ID n'est pas valide
*/
public DecodedJWT verify(String idToken) throws InvalidTokenException {
DecodedJWT decodedToken = JWT.decodeJwt(idToken);
//Assurez-vous qu'il est connecté au pool d'utilisateurs cognito
String iss = decodedToken.getIssuer();
if (!jwtTokenIssuer().equals(iss)) {
throw new InvalidTokenException("L'émetteur du jeton d'identification n'est pas le système cible. Iss=" + iss, idToken);
}
//Assurez-vous que le jeton d'identification est utilisé pour "ID".
String tokenUse = decodedToken.getClaim("token_use").asString();
if (!"id".equals(tokenUse)) {
throw new InvalidTokenException("Le but du jeton ID n'est pas ID. jeton_use=" + tokenUse, idToken);
}
//Vérifiez l'algorithme de signature.
String alg = decodedToken.getAlgorithm();
if (!"RS256".equals(decodedToken.getAlgorithm())) {
throw new InvalidTokenException("L'algorithme de signature de jeton d'identification ne le prend pas en charge. alg=" + alg, idToken);
}
//Validez la charge utile et la signature.
DecodedJWT decodedJWT = null;
if ((decodedJWT = tokenVerify(decodedToken)) == null) {
throw new InvalidTokenException("La vérification du jeton d'identité a échoué.", idToken);
}
return decodedJWT;
}
/**
*Effectuer la vérification à l'aide de la bibliothèque d'auth0
*
* @param kid ID ID de clé dans l'en-tête du jeton
* @Si return n'est pas nul, le jeton d'ID décodé
*
* @jette InvalidTokenException La validation a échoué
*/
private DecodedJWT tokenVerify(DecodedJWT jwToken) throws InvalidTokenException {
try {
DecodedJWT verified = getVerifier(jwToken.getKeyId()).verify(jwToken);
return verified;
} catch (Exception e) {
throw new InvalidTokenException(e);
}
}
/**
*Obtenez une instance de JWTVerifier.
*
* <p>
*JWTVerifier est ver.Puisqu'il est devenu thread-safe à partir de 3, il sera réutilisé.
*Cependant, il doit être mis à jour régulièrement, étant donné que la paire de clés RSA utilisée pour la signature peut être mise à jour.
* </p>
*
* @ID de clé param kid utilisé pour la signature
*
* @return
*
* @throws MalformedURLException
* @throws JwkException
*/
private JWTVerifier getVerifier(String kid) throws MalformedURLException, JwkException {
if (verifier != null && System.currentTimeMillis() < verifierExpire) {
//S'il est dans la date d'expiration, utilisez-le tel quel
return verifier;
}
synchronized (JWT) {
//J'ai le verrou, alors vérifiez-le à nouveau au cas où puis créez l'instance
if (verifier != null && System.currentTimeMillis() < verifierExpire) {
return verifier;
}
UrlJwkProvider http = new UrlJwkProvider(new URL(jwksUrl()));
GuavaCachedJwkProvider provider = new GuavaCachedJwkProvider(http);
Jwk jwk = provider.get(kid);
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
verifier = JWT.require(algorithm)
.withIssuer(jwtTokenIssuer())
.build();
//Prolongez la vie de JWTVerifier
verifierExpire = System.currentTimeMillis() + VERIFIER_LIVE_MILISEC;
Calendar expire = GregorianCalendar.getInstance();
expire.setTimeInMillis(verifierExpire);
Logger.info("Création d'une instance de JWTVerifier. Date limite"
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(expire.getTime()));
}
return verifier;
}
/**
*Obtenez l'émetteur du jeton d'identification
*
* @return
*/
private String jwtTokenIssuer() {
return String.format("https://cognito-idp.%s.amazonaws.com/%s", AWS_REGION, AWS_COGNITO_USER_POOL_ID);
}
/**
*Jeton Web JSON(JWT)Obtenez l'URL de l'ensemble.
*
* @return
*/
private String jwksUrl() {
return jwtTokenIssuer() + "/.well-known/jwks.json";
}
}
InvalidTokenException.class
public class InvalidTokenException extends Exception {
public InvalidTokenException(String message, String idToken) {
super(message + " token is " + idToken);
}
public InvalidTokenException(Throwable e) {
super(e);
}
}
Côté utilisateur
try{
//TODO IdTokenValidator est thread-safe, alors rendez-le statique
DecodedJWT payload = new IdTokenValidator().verify(idToken);
String cognitoUserName = payload.getClaim("cognito:username").asString();
//Traitement requis
}catch (InvalidTokenException e) {
Logger.error("La vérification du jeton d'identité a échoué", e);
badRequest("La valeur de IdToken n'est pas valide");
}
Recommended Posts