1概要

この短い記事では、Spring Boot Actuatorモジュールと、Spring Securityと連携した認証および承認イベントの発行のサポートについて説明します。


2 Mavenの依存関係

まず、

spring-boot-starter-actuate

を__pom.xmlに追加する必要があります。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>1.5.2.RELEASE</version>
</dependency>

最新版はhttps://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org.springframework.boot%22%20AND%20a%3A%22spring-boot-starter-で入手できます。アクチュエータ%22[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

    を追加する

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

……
プリンシパルanonymousUser – AUTHORIZATION

FAILURE
  リモートIPアドレス:0:0:0:0:0:0:0:1
  セッションID:null
プリンシパルユーザー – AUTHENTICATION

FAILURE
  リモートIPアドレス:0:0:0:0:0:0:0:1
  セッションID:BD41692232875A5A65C5E35E63D784F6
プリンシパルユーザー – AUTHENTICATION__SUCCESS
  リモートIPアドレス:0:0:0:0:0:0:0:1
  セッションID:BD41692232875A5A65C5E35E63D784F6
……

この例では、リスナーは3つの

__AuditApplicationEvent

__を受け取りました。

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

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

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


4認証監査リスナー

Spring Bootの

AuthorizationAuditListener

によって公開された情報が十分でない場合は、より多くの情報を公開するために

独自のBeanを作成することができます。

認証が失敗したときにアクセスされたリクエスト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<String, Object> 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が含まれるようになりました。

……
プリンシパルanonymousUser – AUTHORIZATION__FAILURE
  リモートIPアドレス:0:0:0:0:0:0:0:1
  セッションID:null
  リクエスト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のアクチュエータサポートにより、ユーザーからの認証と承認の試みを記録することは簡単になります。いくつかの追加情報については、読者はhttp://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-auditing.html[production ready auditing]も参照してください。

この記事のコードはhttps://github.com/eugenp/tutorials/tree/master/spring-security-core[over on GitHub]にあります。