Spring Boot認証監査サポート

Spring Boot認証監査サポート

1. 概要

この短い記事では、Spring Boot Actuatorモジュールと、SpringSecurityと組み合わせて認証および承認イベントを公開するためのサポートについて説明します。

2. Mavenの依存関係

まず、spring-boot-starter-actuatorpom.xml:に追加する必要があります


    org.springframework.boot
    spring-boot-starter-actuator
    2.1.7.RELEASE

最新バージョンは、Maven Centralリポジトリで入手できます。

3. 認証および承認イベントのリッスン

Spring Bootアプリケーションでのすべての認証および承認の試行を記録するには、リスナーメソッドでBeanを定義するだけです。

@Component
public class LoginAttemptsLogger {

    @EventListener
    public void auditEventHappened(
      AuditApplicationEvent auditApplicationEvent) {

        AuditEvent auditEvent = auditApplicationEvent.getAuditEvent();
        System.out.println("Principal " + auditEvent.getPrincipal()
          + " - " + auditEvent.getType());

        WebAuthenticationDetails details =
          (WebAuthenticationDetails) auditEvent.getData().get("details");
        System.out.println("Remote IP address: "
          + details.getRemoteAddress());
        System.out.println("  Session Id: " + details.getSessionId());
    }
}

利用可能な情報を示すために、AuditApplicationEventで利用可能なものの一部を出力しているだけであることに注意してください。 実際のアプリケーションでは、その情報をさらに処理するために、その情報をリポジトリまたはキャッシュに保存することができます。

Spring Beanはすべて機能することに注意してください。新しいSpringイベントサポートの基本は非常に簡単です。

  • メソッドに@EventListenerアノテーションを付けます

  • メソッドの唯一の引数としてAuditApplicationEventを追加します

アプリケーション実行の出力は次のようになります。

Principal anonymousUser - AUTHORIZATION_FAILURE
  Remote IP address: 0:0:0:0:0:0:0:1
  Session Id: null
Principal user - AUTHENTICATION_FAILURE
  Remote IP address: 0:0:0:0:0:0:0:1
  Session Id: BD41692232875A5A65C5E35E63D784F6
Principal user - AUTHENTICATION_SUCCESS
  Remote IP address: 0:0:0:0:0:0:0:1
  Session Id: BD41692232875A5A65C5E35E63D784F6

この例では、3つのAuditApplicationEventsがリスナーによって受信されています。

  1. ログオンせずに、制限されたページへのアクセスが要求されました

  2. ログオン中に間違ったパスワードが使用されました

  3. 2回目に正しいパスワードが使用された

4. 認証監査リスナー

Spring BootのAuthorizationAuditListenerによって公開された情報が十分でない場合は、create your own bean to expose more information.を実行できます。

例を見てみましょう。ここでは、承認が失敗したときにアクセスされたリクエストURLも公開しています。

@Component
public class ExposeAttemptedPathAuthorizationAuditListener
  extends AbstractAuthorizationAuditListener {

    public static final String AUTHORIZATION_FAILURE
      = "AUTHORIZATION_FAILURE";

    @Override
    public void onApplicationEvent(AbstractAuthorizationEvent event) {
        if (event instanceof AuthorizationFailureEvent) {
            onAuthorizationFailureEvent((AuthorizationFailureEvent) event);
        }
    }

    private void onAuthorizationFailureEvent(
      AuthorizationFailureEvent event) {
        Map data = new HashMap<>();
        data.put(
          "type", event.getAccessDeniedException().getClass().getName());
        data.put("message", event.getAccessDeniedException().getMessage());
        data.put(
          "requestUrl", ((FilterInvocation)event.getSource()).getRequestUrl() );

        if (event.getAuthentication().getDetails() != null) {
            data.put("details",
              event.getAuthentication().getDetails());
        }
        publish(new AuditEvent(event.getAuthentication().getName(),
          AUTHORIZATION_FAILURE, data));
    }
}

これで、リスナーにリクエストURLを記録できます。

@Component
public class LoginAttemptsLogger {

    @EventListener
    public void auditEventHappened(
      AuditApplicationEvent auditApplicationEvent) {
        AuditEvent auditEvent = auditApplicationEvent.getAuditEvent();

        System.out.println("Principal " + auditEvent.getPrincipal()
          + " - " + auditEvent.getType());

        WebAuthenticationDetails details
          = (WebAuthenticationDetails) auditEvent.getData().get("details");

        System.out.println("  Remote IP address: "
          + details.getRemoteAddress());
        System.out.println("  Session Id: " + details.getSessionId());
        System.out.println("  Request URL: "
          + auditEvent.getData().get("requestUrl"));
    }
}

その結果、出力には要求されたURLが含まれるようになりました。

Principal anonymousUser - AUTHORIZATION_FAILURE
  Remote IP address: 0:0:0:0:0:0:0:1
  Session Id: null
  Request URL: /hello

この例では抽象AbstractAuthorizationAuditListenerから拡張したので、実装ではその基本クラスのpublishメソッドを使用できることに注意してください。

テストする場合は、ソースコードをチェックアウトして実行します。

mvn clean spring-boot:run

その後、ブラウザでhttp://localhost:8080/を指定できます。

5. 監査イベントの保存

デフォルトでは、Spring Bootは監査イベントをAuditEventRepositoryに格納します。 独自の実装でBeanを作成しない場合は、InMemoryAuditEventRepositoryが配線されます。

InMemoryAuditEventRepositoryは、最後の4000の監査イベントをメモリに格納する一種の循環バッファです。 これらのイベントには、管理エンドポイントhttp://localhost:8080/auditeventsを介してアクセスできます。

これにより、監査イベントのJSON表現が返されます。

{
  "events": [
    {
      "timestamp": "2017-03-09T19:21:59+0000",
      "principal": "anonymousUser",
      "type": "AUTHORIZATION_FAILURE",
      "data": {
        "requestUrl": "/auditevents",
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1",
          "sessionId": null
        },
        "type": "org.springframework.security.access.AccessDeniedException",
        "message": "Access is denied"
      }
    },
    {
      "timestamp": "2017-03-09T19:22:00+0000",
      "principal": "anonymousUser",
      "type": "AUTHORIZATION_FAILURE",
      "data": {
        "requestUrl": "/favicon.ico",
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1",
          "sessionId": "18FA15865F80760521BBB736D3036901"
        },
        "type": "org.springframework.security.access.AccessDeniedException",
        "message": "Access is denied"
      }
    },
    {
      "timestamp": "2017-03-09T19:22:03+0000",
      "principal": "user",
      "type": "AUTHENTICATION_SUCCESS",
      "data": {
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1",
          "sessionId": "18FA15865F80760521BBB736D3036901"
        }
      }
    }
  ]
}

6. 結論

Spring Bootのアクチュエータサポートにより、ユーザーからの認証および承認の試行を記録するのは簡単です。 読者は、いくつかの追加情報についてもproduction ready auditingを参照しています。

この記事のコードはover on GitHubにあります。