Micronaut ことはじめ - Session Authentication を添えて(2)

f:id:nemuzuka:20200611200142p:plain

前回 は Thymeleaf を使ってログイン画面をレスポンスしました。次は認証機能を組み込んでみましょう。

コードはこちら

Session Authentication

自前で作るのも良いですけど、用意してくれたやつに乗っかりましょう。今回は、Session Authentication を組み込みます。

認証成功時にサーバサイドの Session に認証情報を入れて、かつ、それを紐付ける ID をブラウザの Cookie に入れておき、サーバにアクセスする度に Cookie も送って「このリクエストは認証済みのやつか?」を判断する奴です。昔ながらの安心する奴。

参考にしたのは、こちら

build.gradle

これを追加します。

annotationProcessor "io.micronaut:micronaut-security"
compile "io.micronaut:micronaut-security"
compile "io.micronaut:micronaut-security-session"

application.yml

ちょっと長いです

micronaut:
  security:
    enabled: true
    intercept-url-map:
      -
        pattern: /login   -> 1
        access:
          - isAnonymous()
      -
        pattern: /public/**   -> 2
        access:
          - isAnonymous()
      -
        pattern: /tasks/**   -> 3
        access:
          - SYSTEM_ADMIN
      -
        pattern: /**   -> 4
        access:
          - isAuthenticated()
    endpoints:
      login:
        enabled: true -> 5
    session:
      enabled: true  -> 6
      login-success-target-url: /tasks  -> 7
      unauthorized-target-url: /login  -> 8

この設定は以下を示します。

  1. /login は未ログイン状態でもアクセスできるように isAnonymous()
  2. /public/** も未ログイン状態でもアクセスできるように isAnonymous()
  3. /tasks/** は、ログイン状態で、かつ role に SYSTEM_ADMIN を持つ
  4. その他の path はログイン状態の必要がある
  5. true にすると Micronaut の LoginController が有効になります
  6. true にすると Session Authentication が有効になります
  7. ログイン成功時に遷移する URL を指定します
  8. 未ログイン状態、ログイン状態でもrole を持っていないユーザがアクセスした時に遷移する URL を指定します

ログイン画面

Micronaut の LoginController を使う時、

  • POST method
  • Content-Type が application/x-www-form-urlencoded or application/json
  • パラメータ名は username / password

でなければなりません。なので、ログイン画面では form タグの methodPOST を設定し、Login ボタン click 時に submit します。input タグの name 属性も合わせてください。

認証処理

AuthenticationProviderUserPassword

今回のケースでは認証処理は AuthenticationProvider を implements します。

@Singleton // 1
@RequiredArgsConstructor // lombok
public class AuthenticationProviderUserPassword implements AuthenticationProvider {

  private final SystemConfigurationProperties systemConfigurationProperties;  // 2

  @Override
  public Publisher<AuthenticationResponse> authenticate(
      @Nullable HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
    return Flowable.create(
        emitter -> {
          if (Objects.equals(
                  authenticationRequest.getIdentity(), systemConfigurationProperties.getIdentity())
              && Objects.equals(
                  authenticationRequest.getSecret(), systemConfigurationProperties.getSecret())) {
            // 認証成功時、UserDetails を設定する  3
            var userDetails =
                new UserDetails(
                    (String) authenticationRequest.getIdentity(),
                    Collections.singletonList(Role.SYSTEM_ADMIN.name())); // 4
            emitter.onNext(userDetails);
          } else {
            emitter.onError(new AuthenticationException(new AuthenticationFailed()));
          }
          emitter.onComplete();
        },
        BackpressureStrategy.ERROR);
  }
}

何となく Spring っぽい感じがしませんか。

  1. Spring で言うところの @Bean とか @Service とかに相当します。@Singleton だとインスタンスは1つだけ作られます。Thread Safe でお願いします
  2. プロパティファイルの情報を保持する為の class として SystemConfigurationProperties を別途定義しました
  3. RDBMS に保存していた情報と付き合わせるのが一般的でしょうけど、今回はプロパティに定義した値と比較するようにしました
  4. この認証に成功した時は role に SYSTEM_ADMIN を設定しました。application.yml の設定が生きてきます。

これで認証成功時は UserDetails インスタンスが Session 上に格納されるようになります。

SystemConfigurationProperties

プロパティファイルの情報を保持する class です。

@Data // lombok
@ConfigurationProperties("system.admin") // 1
public class SystemConfigurationProperties {

  /** システム管理者ID. */
  private String identity;

  /** システム管理者パスワード. */
  private String secret;
}

Spring っぽいですね。

  1. プロパティの prefix を指定します

yml をみてもらうとわかると思いますが、プロパティの名前を合わせることで Micronaut がこのインスタンス生成時に良い感じに値を設定してくれます。この class も Bean 登録されるので、AuthenticationProviderUserPassword のコンストラクタに含めておくと Injection されるのです。

認証後に遷移する画面

認証後に遷移するのは /tasks と設定したので、レスポンスを返すように Controller と html を作っておきます。 Micronaut を起動して http://localhost:8080/tasks に直接アクセスするとログイン画面に戻ってしまうのが確認できると思います。

Application Configuration & 認証処理

起動時のパラメータでプロパティファイルを変更する仕組みが Micronaut にありますSpringBoot にもあります。

例えば、ローカル環境 / CI 環境 / 本番環境で振る舞いを変えたい時に使用します。*1

今回はapplication-local.yml という名前のローカル環境のプロパティファイルを作ります。

system:
  admin:
    identity: scott
    secret: tiger

認証情報を新しく設定しました。 これを SystemConfigurationProperties にマッピングさせるには、MICRONAUT_ENVIRONMENTS を使用して起動します。

$ MICRONAUT_ENVIRONMENTS=local ./gradlew run

http://localhost:8080 にアクセスすると、ログイン画面に遷移しましたね。 そこで application-local.yml に指定した認証情報を入力して、Login ボタンをクリックすると...

f:id:nemuzuka:20200611220651p:plain

/tasks が表示できました。また、ブラウザの開発ツールで SESSION Cookie の存在を確認できるはずです。

まとめ

今回は、認証を組み込んでみました。起動パラメータによってプロパティの認証情報も application.yml の値が上書きされていることが確認できたと思います。

Spring 使ったことがある方は使えそうな気がしませんか?

*1:大体私は環境変数でプロパティを上書きするので CI 用の設定ファイルとか本番用の設定ファイルを作りませんが、個人の開発環境用にプロパティを作ります