Spring Session および Spring Security と Hazelcast

このガイドでは、Hazelcast をデータストアとして使用するときに Spring Session を Spring Security と一緒に使用する方法について説明します。Spring Security がすでにアプリケーションに適用されていることを前提としています。

完成したガイドは Hazelcast Spring Security サンプルアプリケーションにあります。

依存関係の更新

Spring Session を使用する前に、依存関係を更新する必要があります。Maven を使用する場合は、次の依存関係を追加する必要があります。

pom.xml
<dependencies>
	<!-- ... -->

	<dependency>
		<groupId>com.hazelcast</groupId>
		<artifactId>hazelcast</artifactId>
		<version>5.4.0</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-web</artifactId>
		<version>6.1.9</version>
	</dependency>
</dependencies>

Spring の設定

必要な依存関係を追加した後、Spring 構成を作成できます。Spring 構成は、HttpSession 実装を Spring Session による実装に置き換えるサーブレットフィルターの作成を担当します。これを行うには、次の Spring 構成を追加します。

@EnableHazelcastHttpSession (1)
@Configuration
public class HazelcastHttpSessionConfig {

	@Bean
	public HazelcastInstance hazelcastInstance() {
		Config config = new Config();
		AttributeConfig attributeConfig = new AttributeConfig()
			.setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
			.setExtractorClassName(PrincipalNameExtractor.class.getName());
		config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) (2)
			.addAttributeConfig(attributeConfig)
			.addIndexConfig(
					new IndexConfig(IndexType.HASH, HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
		SerializerConfig serializerConfig = new SerializerConfig();
		serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
		config.getSerializationConfig().addSerializerConfig(serializerConfig); (3)
		return Hazelcast.newHazelcastInstance(config); (4)
	}

}
1@EnableHazelcastHttpSession アノテーションは、Filter を実装する springSessionRepositoryFilter という名前の Spring Bean を作成します。このフィルターは、Spring Session によってサポートされる HttpSession 実装の置き換えを担当します。この場合、Spring Session は Hazelcast によってサポートされています。
2 プリンシパル名インデックスによるセッションの取得をサポートするには、適切な ValueExtractor を登録する必要があります。Spring Session は、この目的のために PrincipalNameExtractor を提供します。
3MapSession オブジェクトを効率的に直列化するには、HazelcastSessionSerializer を登録する必要があります。これが設定されていない場合、Hazelcast はネイティブ Java 直列化を使用してセッションを直列化します。
4Spring Session を Hazelcast に接続する HazelcastInstance を作成します。デフォルトでは、アプリケーションが起動し、Hazelcast の組み込みインスタンスに接続します。Hazelcast の構成の詳細については、リファレンスドキュメント (英語) を参照してください (英語)
HazelcastSessionSerializer が優先される場合は、開始する前に、すべての Hazelcast クラスターメンバーに対して構成する必要があります。Hazelcast クラスターでは、すべてのメンバーがセッションに同じ直列化方法を使用する必要があります。また、Hazelcast クライアント / サーバートポロジを使用する場合は、メンバーとクライアントの両方で同じ直列化方法を使用する必要があります。シリアライザーは、同じ SerializerConfiguration のメンバーに ClientConfig を介して登録できます。

サーブレットコンテナーの初期化

Spring の設定は、Filter を実装する springSessionRepositoryFilter という名前の Spring Bean を作成しました。springSessionRepositoryFilter Bean は、HttpSession を Spring Session によるカスタム実装に置き換えるロールを果たします。

Filter がその魔法を実行するために、Spring は SessionConfig クラスをロードする必要があります。このアプリケーションは、SecurityInitializer クラスを使用して Spring 構成をすでにロードしているため、SessionConfig クラスをアプリケーションに追加できます。次のリストは、その方法を示しています。

src/main/java/sample/SecurityInitializer.java
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {

	public SecurityInitializer() {
		super(SecurityConfig.class, SessionConfig.class);
	}

}

最後に、サーブレットコンテナー(つまり、Tomcat)がすべてのリクエストに springSessionRepositoryFilter を使用するようにする必要があります。Spring Session の springSessionRepositoryFilter は、Spring Security の springSecurityFilterChain の前に呼び出されることが非常に重要です。そうすることで、Spring Security が使用する HttpSession が Spring Session によって確実にサポートされます。幸い、Spring Session には、これを簡単に実行できる AbstractHttpSessionApplicationInitializer という名前のユーティリティクラスが用意されています。次の例は、その方法を示しています。

src/main/java/sample/Initializer.java
public class Initializer extends AbstractHttpSessionApplicationInitializer {

}
クラスの名前(Initializer)は関係ありません。重要なのは、AbstractHttpSessionApplicationInitializer を継承することです。

AbstractHttpSessionApplicationInitializer を継承することにより、springSessionRepositoryFilter という名前の Spring Bean が、Spring Security の springSecurityFilterChain の前のすべてのリクエストに対してサーブレットコンテナーに登録されるようにします。

Hazelcast Spring Security サンプルアプリケーション

このセクションでは、Hazelcast Spring Security サンプルアプリケーションの操作方法について説明します。

サンプルアプリケーションの実行

サンプルを実行するには、ソースコードを取得し、次のコマンドを呼び出します。

$ ./gradlew :spring-session-sample-javaconfig-hazelcast:tomcatRun
デフォルトでは、Hazelcast はアプリケーションで組み込みモードで実行されます。ただし、代わりにスタンドアロンインスタンスに接続する場合は、リファレンスドキュメント (英語) の手順に従って構成できます。

これで、localhost:8080/ でアプリケーションにアクセスできるようになります。

セキュリティサンプルアプリケーションの調査

これで、アプリケーションを使用してみることができます。これを行うには、以下を入力してログインします。

  • ユーザー名 user

  • パスワード password

次に、ログインボタンをクリックします。以前に入力したユーザーでログインしていることを示すメッセージが表示されます。ユーザーの情報は、Tomcat の HttpSession 実装ではなく Hazelcast に保存されます。

それはどのように機能しますか?

Tomcat の HttpSession を使用する代わりに、Hazelcast で値を保持します。Spring Session は、HttpSession を、Hazelcast の Map による実装に置き換えます。Spring Security の SecurityContextPersistenceFilter が SecurityContext を HttpSession に保存すると、Hazelcast に永続化されます。

新しい HttpSession が作成されると、Spring Session はブラウザーに SESSION という名前の Cookie を作成します。その Cookie には、セッションの ID が含まれています。Cookie を表示できます(Chrome (英語) または Firefox [Mozilla] (英語) を使用)。

データストアとのやり取り

Java クライアント (英語) 他のクライアントの 1 つ (英語) 、または管理センター (英語) を使用して、セッションを削除できます。

コンソールの使用

例: Hazelcast ノードに接続した後に管理センターコンソールを使用してセッションを削除するには、次のコマンドを実行します。

	default> ns spring:session:sessions
	spring:session:sessions> m.clear
Hazelcast のドキュメントには、コンソール (英語) の説明があります。

または、明示的なキーを削除することもできます。コンソールに次のように入力します。必ず 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e を SESSION Cookie の値に置き換えてください。

	spring:session:sessions> m.remove 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e

次に、localhost:8080/ のアプリケーションにアクセスして、認証されなくなったことを確認します。

REST API の使用

他のクライアントをカバーするドキュメントのセクションに従って、Hazelcast ノードによって提供される REST API (英語) があります。

例: 次のように個々のキーを削除できます(7e8383a4-082c-4ffe-a4bc-c40fd3363c5e を必ず SESSION Cookie の値に置き換えてください)。

	$ curl -v -X DELETE http://localhost:xxxxx/hazelcast/rest/maps/spring:session:sessions/7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
Hazelcast ノードのポート番号は、起動時にコンソールに出力されます。xxxxx をポート番号に置き換えます。

これで、このセッションで認証されなくなったことがわかります。