Spring Session and Spring Security with Hazelcast

本指南描述了使用 Hazelcast 作为数据存储时,如何将 Spring 会话与 Spring 安全性配合使用。假设您已向应用程序应用了 Spring 安全性。

你可以在Hazelcast Spring Security sample application中找到已完成的指南。

Updating Dependencies

在使用 Spring 会话之前,您必须更新依赖。如果您使用 Maven,您必须添加以下依赖:

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

	<dependency>
		<groupId>com.hazelcast</groupId>
		<artifactId>hazelcast</artifactId>
		<version>{hazelcast-version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-web</artifactId>
		<version>{spring-core-version}</version>
	</dependency>
</dependencies>

Spring Configuration

在添加所需的依赖项后,我们可以创建我们的 Spring 配置。Spring 配置负责创建一个 Servlet 过滤器,该过滤器将 HttpSession 实现替换为由 Spring Session 支持的实现。为此,请添加以下 Spring 配置:

/*
 * Copyright 2014-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package docs.http;

import com.hazelcast.config.AttributeConfig;
import com.hazelcast.config.Config;
import com.hazelcast.config.IndexConfig;
import com.hazelcast.config.IndexType;
import com.hazelcast.config.SerializerConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.MapSession;
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
import org.springframework.session.hazelcast.HazelcastSessionSerializer;
import org.springframework.session.hazelcast.PrincipalNameExtractor;
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;

// tag::config[]
@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)
	}

}
// end::config[]
1 @EnableHazelcastHttpSession 注释会创建一个 Spring bean,其名称为 springSessionRepositoryFilter`并会实现 `Filter。该筛选器负责替换 HttpSession 实现,以便由 Spring Session 支持。在这种情况中,Spring Session 由 Hazelcast 支持。
2 为了支持按负责人姓名索引检索会话,需要注册一个适当的 ValueExtractor。Spring Session为此提供了 PrincipalNameExtractor
3 为了高效序列化 MapSession 对象,需要注册 HazelcastSessionSerializer。如果没有设置该属性,Hazelcast 将使用本机 Java 序列化来序列化会话。
4 创建一个 HazelcastInstance,将它连接到 Spring Session 的 Hazelcast。默认情况下,应用程序会启动并连接到一个嵌入式 Hazelcast 实例。有关配置 Hazelcast 的更多信息,请参阅 reference documentation

如果选择`HazelcastSessionSerializer`,则必须在所有 Hazelcast 集群成员开始之前对所有 Hazelcast 集群成员进行配置。在 Hazelcast 集群中,所有成员都应使用用于会话的相同序列化方法。此外,如果使用 Hazelcast 客户端/服务器拓扑结构,则成员和客户端都必须使用相同序列化方法。可以使用`ClientConfig`注册序列化程序并使用与成员相同的`SerializerConfiguration`。

Servlet Container Initialization

我们的 Spring Configuration 创建了一个名为 springSessionRepositoryFilter 的 Spring bean,它实现 FilterspringSessionRepositoryFilter bean 负责用由 Spring Session 支持的自定义实现替换 HttpSession

为了让我们的 Filter`发挥作用,Spring 需要加载我们的 `SessionConfig 类。由于我们的应用程序已经通过使用我们的 SecurityInitializer 类加载了 Spring 配置,我们可以将我们的 SessionConfig 添加到该类。以下清单显示了如何执行此操作:

src/main/java/sample/SecurityInitializer.java
Unresolved include directive in modules/ROOT/pages/guides/java-hazelcast.adoc - include::example$spring-session-samples/spring-session-sample-javaconfig-hazelcast/src/main/java/sample/SecurityInitializer.java[]

最后,我们需要确保我们的 Servlet 容器(即 Tomcat)对每个请求使用我们的 springSessionRepositoryFilter。Spring 会话的 springSessionRepositoryFilter 在 Spring Security 的 springSecurityFilterChain 之前调用非常重要。这样做可以确保 Spring Security 使用的 HttpSession 由 Spring 会话支持。幸运的是,Spring 会话提供了一个名为 AbstractHttpSessionApplicationInitializer 的实用程序类,使此操作变得简单。以下示例显示了如何执行此操作:

src/main/java/sample/Initializer.java
Unresolved include directive in modules/ROOT/pages/guides/java-hazelcast.adoc - include::example$spring-session-samples/spring-session-sample-javaconfig-hazelcast/src/main/java/sample/Initializer.java[]

我们的类 (Initializer) 的名称并不重要,重要的是我们必须延伸`AbstractHttpSessionApplicationInitializer`。

通过扩展 AbstractHttpSessionApplicationInitializer,我们确保为 Spring 安全的 springSecurityFilterChain 注册名为 springSessionRepositoryFilter 的 Spring Bean 以便在对每个请求进行之前。

Hazelcast Spring Security Sample Application

本节描述如何使用 Hazelcast Spring 安全性示例应用程序。

Running the Sample Application

您可以获取 源代码 并调用以下命令运行示例:

$ ./gradlew :spring-session-sample-javaconfig-hazelcast:tomcatRun

默认情况下,Hazelcast 以嵌入模式在你的应用程序中运行。但是,如果你希望连接到独立实例,则可以通过按照 reference documentation 中的说明进行配置。

您现在应该能够访问 [role="bare"][role="bare"]http://localhost:8080/ 中的应用程序。

Exploring the Security Sample Application

您现在可以尝试使用该应用程序。为此,请输入以下内容进行登录:

  • Username user

  • Password password

单击 Login 按钮。您现在应该看到一条消息,指示您已使用之前输入的用户登录。用户的信息存储在 Hazelcast 中,而不是 Tomcat HttpSession 实现中。

How Does It Work?

我们不会使用 Tomcat 的 HttpSession,而是将这些值持久保存到 Hazelcast 中。Spring 会话将 HttpSession 替换为由 Hazelcast 中的 Map 支持的实现。当 Spring 安全性的 SecurityContextPersistenceFilterSecurityContext 保存到 HttpSession 时,它就被持久保存到 Hazelcast 中。

当创建新的 HttpSession 时,Spring Session 在浏览器中创建一个名为 SESSION 的 cookie。该 cookie 包含会话 ID。您可以查看 cookie(使用 ChromeFirefox)。

Interacting with the Data Store

您可以使用 a Java client one of the other clientsmanagement center 来移除会话。

Using the Console

例如,要连接到 Hazelcast 节点后使用管理中心控制台删除会话,请运行以下命令:

	default> ns spring:session:sessions
	spring:session:sessions> m.clear

Hazelcast 文档包含有关 the console 的说明。

或者,您也可以删除显式密钥。在控制台输入以下内容,务必将 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e 替换为 SESSION cookie 的值:

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

现在,再次访问 [role="bare"][role="bare"]http://localhost:8080/ 中的应用程序,并观察不再对我们进行身份验证的情况。

Using the 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`。

现在,您可以看到,您不再通过该会话进行验证。