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("その他のエラー"); } } }
お手軽でいいですね。
トークンを振り出した側は、トークン自体は保管もせずに戻ってきたトークンの 署名だけ確認すれば正当性が分かりますから。