JOSE4J サンプル

お仕事である企業のWEBアプリの一部機能をAPI化することになり、認証周りをちょっと考える必要があり JWT に触ってみました。

今回はAPI化と言っても限られた企業間ですし、そもそも提供企業のユーザID・パスワードを 使用して周辺企業もサービスを構築しているので、大げさなOAuth 2.0 でいう「認可コード」フロー のような大げさなことは必要ありません。

では、電子署名だけでトークンの正当性を確認できる JSON Web Token(JWT)でいいんじゃね? ということになり、JWTのJavaの実装系である JOSE4J についてサンプルコードを書いてみました。

/**
 + @author Shamon
 + @version 0.0.1
 */


package jp.ibm.jose4jtest;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.Key;
import java.util.ArrayList;
import java.util.List;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.ErrorCodes;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.HmacKey;

/**
 + JOSE4J
 + を検証するためのサンプルコード.
 */
public class Jose4JTest {
    public static void main(String [] args) {

        // 秘密鍵の生成
        String secret = "サイゼリア メガワイン";
        Key secret_key = null;
        try {
            secret_key = new HmacKey(secret.getBytes("UTF-8"));
        } catch(Exception e) {
            System.out.println("エラー");
        }

        // クレームセットの生成
        JwtClaims claims = new JwtClaims();

        // JWT発行者
        claims.setIssuer("MY COMPANY");
        // JWTの利用者
        claims.setAudience("YOUR COMPANY");
        // 有効期限の設定
        claims.setExpirationTimeMinutesInTheFuture(1);
        claims.setGeneratedJwtId();
        // JWTの発行日
        claims.setIssuedAtToNow();
        // JWTが有効になる日時
        claims.setNotBeforeMinutesInThePast(2);
        // ユーザ識別子
        claims.setSubject("019293948101");
        //独自のクレームの設定
        claims.setStringClaim("加入者番号","019293948191");


        // JSON形式への変換
        String claimsString = claims.toJson();
        System.out.println("平文のJWT");
        System.out.println("------------------------------------------------------");
        System.out.println(claimsString);
        System.out.println("");
        System.out.println("------------------------------------------------------");

        try{
            System.out.println("しばしシステム休止中");
            Thread.sleep(120000);
        } catch(InterruptedException e) {}

        // 署名オブジェクトの生成
        JsonWebSignature jws = new JsonWebSignature();
        // クレームセットのペイロード
        jws.setPayload(claimsString);
        // 署名のアルゴリズムの設定
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
        // 秘密キーの設定
        jws.setKey(secret_key);
        jws.setDoKeyValidation(false);

        String jwt =  null;
        try{
            jwt = jws.getCompactSerialization();
        } catch (Exception e) {
            System.err.println("エラー");
        }
        System.out.println("エンコードされたJWT");
        System.out.println("------------------------------------------------------");
        System.out.println("JWT: " + jwt);
        System.out.println("------------------------------------------------------");

        // 受けた時の処理
        System.out.println("クライアントから戻されたJWTの処理・・・・");
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
            .setRequireExpirationTime()         // 有効期限は必須
            .setAllowedClockSkewInSeconds(30)   // 検証時にシステム時計のズレの許容範囲
            .setRequireSubject()                // JWTはSubjectが必須
            .setExpectedIssuer("MY COMPANY")    // JWTの発行者はMY COMPANYでなければならない
            .setExpectedAudience("YOUR COMPANY") // JWTはYOUR COMPANY向けに発行されたものでなければならない
            .setVerificationKey(secret_key)     // 署名を検査するキーを設定。今回は秘密キー
            .setRelaxVerificationKeyValidation()
            .build();                           // JwtConsumerをビルド

        try
        {
            //  JWTの検査を行い、クレームセットを取り出す
            JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
            System.out.println("JWT validation succeeded!");
            System.out.println("JWT : " + jwtClaims);
        }
        catch (InvalidJwtException e)
        {
            System.out.println("Invalid JWT! " + e);

            try {
                if (e.hasExpired())
                {
                    System.err.println("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
                }

                if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID))
                {
                    System.err.println("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
                }
            } catch (Exception ex) {
                System.err.println("例外処理中にエラー");
            }
        }
        catch(Exception e) {
            System.err.println("その他のエラー");
        }

    }
}

お手軽でいいですね。

トークンを振り出した側は、トークン自体は保管もせずに戻ってきたトークンの 署名だけ確認すれば正当性が分かりますから。