38
45

More than 5 years have passed since last update.

Javaで二段階認証を実装

Last updated at Posted at 2017-12-27

Overview

まえまえから、二段階認証を実装してみたいと思っていたのですが、良いライブラリが存在していることもあり思いのほか簡単に実装できました。

GoogleAuth (二段階認証のサーバサイド用ライブラリ)

GoogleAuthはRFC6238で定義されている、Time-based One-time Password(TOTP)を作成してくれるサーバサイド向けのJavaライブラリです。

上記のライブラリを使ってサーバサイドを実装してやれば、Googleが提供しているGoogle Authenticatorなどのアプリで発行したワンタイムトークンを使って、ユーザに認証させることができます。

iOS版 Google Authenticator

Android版 Google Authenticator

Google AuthenticatorもTOTPの仕様でワンタイムトークンをつくっているので利用できるというわけです。

※なお、ユーザがGoogle Authenticatorをインストールした端末をなくしたケースは本記事では考慮してません。本番で導入する場合はそういったケースの運用も想定しておく必要があります。

二段階認証のおおまかな流れ

だいたい以下のような流れになるかと思います。

two-factor-auth-seq.png

サンプルコード

上記のシーケンス図でいうところの、Server部分の実装コードになります。

言語はJavaを使用しています。

user情報の保存処理はDBではなくSingletonのMapを使ってごまかしてます。

サンプルコード解説

サンプルコードの解説というか、ほぼライブラリ(GoogleAuth)の使い方の話になります。

SecretKeyの発行 & 保存

下記のコードで"user_id"用のSecretKeyを発行し、データベースなどに保存することができます。

GoogleAuthenticator gAuth = new GoogleAuthenticator();
GoogleAuthenticatorKey key = gAuth.createCredentials("user_id");

なお、上記のコードを実行する前に以下の準備を行っておく必要があります。

ICredentialRepositoryの実装クラスを準備

上記のcreateCredentialsを実行すると、GoogleAuthはICredentialRepositoryの実装クラスのsaveUserCredentialsメソッドを呼び出してsecretKeyの保存処理を実行します。
※呼び出されるICredentialRepositoryの実装クラスの指定方法はJava ServiceLoader用の準備を参照

public class MyCredentialRepository implements ICredentialRepository {
    // ...
    @Override
    public void saveUserCredentials(String userId, String secretKey, int validationCode, List<Integer> scratchCodes) {
        // userIdをkeyにしてsecretKeyを保存する処理を記述
    }
}

ちなみに、scratchCodesはユーザが端末をなくした場合などに使用させるためのコードで、デフォルトで5つ発行されるようです。
https://github.com/wstrange/GoogleAuth#scratch-codes 

validationCodeはどのような用途を想定しているのかよく理解できませんでした(scratchCodesと同じような用途?)。もしご存知の方いれば是非ご教授ください:bow:

サンプルコードではやってませんが、これらの値の処理も必要に応じて実装するのが良いかと思います。

Java ServiceLoader用の準備

GoogleAuthは、createCredentialsが実行された際に、Java ServiceLoader APIを使用してICredentialRepositoryの実装クラスを取得し、secretKeyを保存します。

src/resource配下など、クラスパスが通っている場所に、META-INF/servicesフォルダを作成しcom.warrenstrange.googleauth.ICredentialRepositoryというテキストファイルを作成してください。

作成したファイル内に、以下のようにICredentialRepositoryの実装クラス名を記述しておくと、GoogleAuthがこのクラスを使ってくれるようになります。

io.github.yuizho.twofactorauth.MyCredentialRepository

Secret情報の返却

生成したsecretKeyを含むSecret情報を、Google Authenticatorへ読み込ませる際は、以下のようなフォーマットのURIを生成し、QRコードへ変換します。

otpauth://totp/<userId>?secret=<secretKey>&issuer=<applicationName>

e.g. otpauth://totp/test_user?secret=ZYWAZKOQLG3YHBSZ&issuer=two-factor-auth-sample

URIのフォーマットの詳細は以下を参照してください。
https://github.com/google/google-authenticator/wiki/Key-Uri-Format

トークンの検証

下記のような感じで"user_id"用のsecretKeyを保存先から取得し、トークンを生成した上で、渡されたトークン(Google Authenticatorで取得したもの)と一致しているかチェックできます。

GoogleAuthenticator gAuth = new GoogleAuthenticator();
boolean isCodeValid = gAuth.authorizeUser("user_id", token);

このとき、GoogleAuthはsecretKeyを保存したときと同じようにICredentialRepositoryの実装クラスのgetSecretKeyメソッドを呼び出してsecretKeyの取得処理をおこないます。
※呼び出されるICredentialRepositoryの実装クラスの指定方法はJava ServiceLoader用の準備を参照

public class MyCredentialRepository implements ICredentialRepository {
    // ...
    @Override
    public String getSecretKey(String userId) {
        // userIdをキーにテーブルからsecretKeyを取得し、返却する処理を実装
    }
}

あとは、GoogleAuthが取得したsecretkeyと現在のtimestampをもとにトークンを生成し、GoogleAuthenticator#authorizeUserの第二引数に渡されたトークンと比較してくれます。

まとめ

というわけで、比較的簡単に二段階認証の実装を行うことができました。

今回はサーバサイドのライブラリにGoogleAuthを使用していますが、もちろんライブラリを使わずに自分で実装することも可能です。

その場合は以下のRFCなどを参照しながらトークンTOTPの生成処理を実装すればいけると思います(サンプルコードもあります)。
https://tools.ietf.org/html/rfc6238

38
45
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
45