リソース指向なアーキテクチャ(Resource Oriented Architecture:ROA)なサイト構築には
RESTFulがデファクトな感がありますが、今回は、以前(結構前に)、社内の勉強会でも発表させて頂いた
このROAに対しての認証で良く使用されるJWT(JSON Web Tokenの略で、ジョットと読む)について共有させて頂きます。
JWTの構成
JWTは、以下の構成で成り立っています。
- ヘッダー
- ペイロード
- シグネチャ
この文字列をbase64UrlEncodeし、ドット「.」で文字連結することでJWTとなります。
{ヘッダー}.{ペーロード}.{シグネチャ}
※詳細は、jwt.io をご参照ください。
ヘッダー
・alg … アルゴリズム。シグネチャを暗号化する為のアルゴリズム名を指定(ES384やHS256 などを指定)
・typ … トークンタイプ(JWTを指定)
データの例:{“alg”: “HS256″,”typ”: “JWT”}
base64UrlEncodeした値:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
ペイロード
・サーバーへ送付する実際のデータになります。key – value 形式で指定します。
・key – value 形式の組み合わせの事をクレーム(Claim)と言います。
データの例:{“sub”: “1234567890”,”name”: “John Doe”,”iat”: 1516239022}
base64UrlEncodeした値:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
シグネチャ
・ヘッダーとペイロードをドット「.」で文字連結し、指定したアルゴリズム(この例の場合は「HS256」)で暗号化したものをシグネチャとします。
・暗号化する際のキーは、サーバでしか知らないシークレットキーを使用します。
データ例:base64UrlEncode(ヘッダーの値) + “.” +base64UrlEncode(ペイロードの値) をシークレットキーで暗号化
base64エンコードした値:SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
上の3つの値をドット「.」で文字連結したものがJWTとなります。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
サーバーサイドでしか知り得ない、シークレットキーを使用することにより
JWTの値の有効性が担保されます。(と言うか、シークレットキーが分からないとシグネチャの生成が適当な値となる)
JWTの認証フロー
実際の使用方法はとフローは以下な感じになります。
フローは図の上から順に…
- 認証
- 例えばユーザID(メアド)とパスワードをサーバーへPOSTします。
- レスポンス(アクセストークン付与)
- サーバーは、取得した情報を基に認証を実施します。
- 認証NGだった場合、エラーコードとメッセージを返却します。
- 認証OKだった場合、シークレットキーを使用し取得情報からJWTを生成します。
- 生成したJWTをレスポンスします。
- リクエスト(アクセストークン付与)
- サーバーから返却されたJWTの値をヘッダーなりパラメータなりに付与します。
- APIをリクエストします。(OAuthなどで、Authorization: Bearer Tokenとして良く使用されたりする。)
- 検証
- サーバーサイドで、シークレットキーを使用し、JWTのシグネチャ値を検証します。
- れすぽんす
- 認証できた場合は、リクエストに応じた値をレスポンスします。
Laravel/Lumenでの実装
ライブラリ(パッケージ)をインストールします。
1 |
composer require lcobucci/jwt |
※ライブラリの利用制限や、使用方法はこちらをご参照下さい。
JWT用のクラスを生成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
<?php use Lcobucci\JWT\Builder; use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Parser; use Lcobucci\JWT\ValidationData; class Jwt { // Retrieves the token private $token = null; // Configures the issuer (iss claim or aud claim) private $URI = 'https://sria.co.jp'; // Configures the id (jti claim), replicating as a header item private $JTI = '4f1g23a12aa'; // SECRET_KEY private $SECRET_KEY = 'XXXXX'; /* * */ public function seJwt( $jwt ) { $this->jwt = $jwt ; } /* * */ public function getJwt( $uid ) { return (new Builder())->setIssuer( $this->URI ) ->setAudience( $this->URI ) ->setId( uniqid(), true ) ->setIssuedAt( time() ) ->setNotBefore( time() + 60 ) ->setExpiration( time() + 3600 ) ->set( 'uid' , $uid ) ->sign( new Sha256() , $this->SECRET_KEY ) ->getToken(); } /* * */ public function parserJwt( $jwt ) { $validata = new ValidationData(); $validata->setIssuer( $this->URI ); $validata->setAudience( $this->URI ); $validata->setId( $this->JTI ); $this->token = ( new Parser() )->parse( (string ) $jwt ); if( $this->token->verify( $jwt, $this->SECRET_KEY ) ) { if( $this->token->validate( $validata ) === false ) { throw new Exception( 'Validation Error!'); } } else { throw new Exception( 'Signature Error!' ); } } /* * */ public function getValue( $key ) { retrun $this->token->getClaim( $key ); } } |
後は、こいつをNew Jwt();して使うだけです。
でわ!