AWS Cognitoで認証したJWT(トークン)の有効性チェック

クライアントサイドで認証したトークンをサーバーサイドでもチェックする場合のチェック処理です。

# 初歩的なチェック処理。
```java:ValidationUtil.Java
package com.yksc.lambda.util;

import java.net.URL;
import java.security.interfaces.RSAPublicKey;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;

public class ValidationUtil {
	public static void main( String[] args ) throws Exception {
		String region = "us-east-1";
		StringString userPoolId = "us-east-1_XXXXXX";
		String token = "YOUR_TOKEN";
		String jwksUrl = "https://cognito-idp." + region + ".amazonaws.com/" + userPoolId + "/.well-known/jwks.json";

		System.out.println( jwksUrl );

		// JWTのヘッダーからkidを取得
		DecodedJWT jwt = JWT.decode( token );
		String kid = jwt.getKeyId();

		// JWKSから公開鍵を取得
		JWKSet jwkSet = JWKSet.load( new URL( jwksUrl ) );
		JWK jwk = jwkSet.getKeyByKeyId( kid );
		RSAPublicKey publicKey = (RSAPublicKey) jwk.toRSAKey().toPublicKey();

		// 公開鍵を使用してJWTの署名を検証
		Algorithm algorithm = Algorithm.RSA256( publicKey, null );
		JWT.require( algorithm )
				.withIssuer( "https://cognito-idp." + region + ".amazonaws.com/" + userPoolId )
				.build()
				.verify( token );

		System.out.println( "Token is valid." );
	}
}

```

Mavenの依存関係はこれ。
```xml:pom.xml
		<dependency>
		    <groupId>com.nimbusds</groupId>
		    <artifactId>nimbus-jose-jwt</artifactId>
		    <version>9.37.3</version>
		</dependency>
		<dependency>
		    <groupId>com.auth0</groupId>
		    <artifactId>java-jwt</artifactId>
		    <version>4.4.0</version>
		</dependency>
```

# 認証処理で失敗したら飛んでくるExceptionの例
SignatureVerificationException	
AlgorithmMismatchException
JWTDecodeException
TokenExpiredException
InvalidClaimException	

# Exceptionの例
Exception in thread "main" com.auth0.jwt.exceptions.SignatureVerificationException: The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA
	at com.auth0.jwt.algorithms.RSAAlgorithm.verify(RSAAlgorithm.java:51)
	at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:463)
	at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:445)
	at com.yksc.lambda.util.ValidationUtil.main(ValidationUtil.java:39)


# 更に詳細にチェックする場合。
1. Issuer (iss)
トークンの発行者が期待される値（例: Amazon Cognito User PoolのURL）と一致することを確認します。これにより、トークンが信頼できるソースから発行されたことが保証されます。

2. Subject (sub)
トークンの主題（通常はユーザー識別子）が有効であることを確認します。これは、トークンが特定のユーザーに関連付けられていることを保証するために使用されます。

3. Audience (aud)
トークンの受信者が期待される値（例: アプリケーションのクライアントID）と一致することを確認します。これにより、トークンが適切な受信者のために発行されたことが保証されます。

4. Expiration Time (exp)
トークンの有効期限が現在時刻を超えていないことを確認します。これにより、期限切れのトークンが使用されるのを防ぎます。

5. Not Before (nbf)
トークンが指定された時刻よりも前には使用されないことを確認します。これは、将来の特定の時点からトークンを有効にしたい場合に使用されます。

6. Issued At (iat)
トークンの発行時刻が期待する範囲内であることを確認します。これにより、古いトークンが再利用されるのを防ぎます。

7. JWT ID (jti)
トークンの一意性を確認するために使用される識別子です。これは、トークンの再生攻撃を防ぐために利用されることがあります。

# 色々チェックする処理
```java:ValidationUtil.Java
import java.net.URL;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;

public class ValidationUtil {

	public static void main( String[] args ) throws Exception {
		String region = "us-east-1";
		String userPoolId = "us-east-1_XXXXXX";
		String token = "YOUR_TOKEN";
		String applicationClientId = "xxxxxxxxxxxxxxxxxxxxxxxx"; 
		String userName = "GUIDみたいなやつ";

		String jwksUrl = "https://cognito-idp." + region + ".amazonaws.com/" + userPoolId + "/.well-known/jwks.json";
		String expectedIssuer = "https://cognito-idp." + region + ".amazonaws.com/" + userPoolId;

		// JWTのヘッダーからkidを取得
		DecodedJWT jwt = JWT.decode( token );
		String kid = jwt.getKeyId();

		// JWKSから公開鍵を取得
		JWKSet jwkSet = JWKSet.load( new URL( jwksUrl ) );
		JWK jwk = jwkSet.getKeyByKeyId( kid );
		RSAPublicKey publicKey = (RSAPublicKey) jwk.toRSAKey().toPublicKey();

		// 公開鍵を使用してJWTの署名を検証
		Algorithm algorithm = Algorithm.RSA256( publicKey, null );
		JWTVerifier verifier = JWT.require( algorithm ).build();

		// トークンを検証
		DecodedJWT verifiedJwt = verifier.verify( token );

		// Issuerの検証
		if( !verifiedJwt.getIssuer().equals( expectedIssuer ) ) {
			throw new RuntimeException( "Issuerが一致しません。" );
		}

		// Subjectの検証
		if( !verifiedJwt.getSubject().equals( userName ) ) {
			throw new RuntimeException( "Subjectが一致しません。" );
		}

		// Audienceの検証
		if( !verifiedJwt.getAudience().contains( applicationClientId ) ) {
			throw new RuntimeException( "Audienceが一致しません。" );
		}

		// Expiration Timeの検証
		if( verifiedJwt.getExpiresAt().before( new Date() ) ) {
			throw new RuntimeException( "トークンの有効期限が切れています。" );
		}

		// Not Beforeの検証、Optional項目
		Date now = new Date();
		Date notBefore = verifiedJwt.getNotBefore();
		if( notBefore != null && notBefore.after( now ) ) {
			throw new RuntimeException( "トークンはまだ有効ではありません。" );
		}

		// Issued Atの検証
		if( verifiedJwt.getIssuedAt().after( new Date() ) ) {
			throw new RuntimeException( "トークンの発行時刻が未来です。" );
		}

		// JWT IDの検証
		if( verifiedJwt.getId() == null || verifiedJwt.getId().isEmpty() ) {
			throw new RuntimeException( "JWT IDが無効です。" );
		}

		System.out.println( "Token is valid and all claims are verified." );
	}

}
```

Mavenの依存関係はこれ。
```xml:pom.xml
		<dependency>
		    <groupId>com.nimbusds</groupId>
		    <artifactId>nimbus-jose-jwt</artifactId>
		    <version>9.37.3</version>
		</dependency>
		<dependency>
		    <groupId>com.auth0</groupId>
		    <artifactId>java-jwt</artifactId>
		    <version>4.4.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.12.0</version>
		</dependency>
```