Guide to Spring Session – 春季会议指南

最后修改: 2016年 12月 9日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Spring Session has the simple goal of free up session management from the limitations of the HTTP session stored in the server.

Spring Session的简单目标是将会话管理从存储在服务器中的HTTP会话的限制中解放出来。

The solution makes it easy to share session data between services in the cloud without being tied to a single container (i.e. Tomcat). Additionally, it supports multiple sessions in the same browser and sending sessions in a header.

该解决方案使得在云端的服务之间共享会话数据变得很容易,而不必被束缚在一个单一的容器(即Tomcat)上。此外,它还支持在同一浏览器中的多个会话,并在一个头中发送会话。

In this article, we’ll use Spring Session to manage authentication information in a web app. While Spring Session can persist data using JDBC, Gemfire, or MongoDB, we will use Redis.

在本文中,我们将使用Spring Session来管理Web应用中的认证信息。虽然Spring Session可以使用JDBC、Gemfire或MongoDB来持久化数据,但我们将使用Redis

For an introduction to Redis check out this article.

关于Redis的介绍,请查看篇文章。

2. A Simple Project

2.一个简单的项目

Let’s first create a simple Spring Boot project to use as a base for our session examples later on:

让我们首先创建一个简单的Spring Boot项目,作为我们以后的会话例子的基础。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.2</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Our application runs with Spring Boot and the parent pom provides versions for each entry. The latest version of each dependency can be found here: spring-boot-starter-security, spring-boot-starter-web, spring-boot-starter-test.

我们的应用程序使用Spring Boot运行,父pom为每个条目提供版本。每个依赖项的最新版本可以在这里找到。spring-boot-starter-securityspring-boot-starter-webspring-boot-starter-test

Let’s also add some configuration properties for our Redis server in application.properties:

让我们也在application.properties中为我们的Redis服务器添加一些配置属性。

spring.redis.host=localhost
spring.redis.port=6379

3. Spring Boot Configuration

3.Spring Boot配置

For Spring Boot, it’s enough to add the following dependencies, and the auto-configuration will take care of the rest:

对于Spring Boot来说,添加以下依赖项就足够了,自动配置会处理剩下的问题。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

We are using the boot parent pom to set the versions here, so these are guaranteed to work with our other dependencies. The latest version of each dependency can be found here: spring-boot-starter-data-redis, spring-session.

我们在这里使用boot parent pom来设置版本,所以这些版本可以保证与我们的其他依赖项一起工作。每个依赖关系的最新版本可以在这里找到。spring-boot-starter-data-redisspring-session

4. Standard Spring Config (no Boot)

4.标准Spring配置(无Boot)

Let’s also have a look at the integrating and configuring spring-session without Spring Boot – just with plain Spring.

我们也来看看在没有Spring Boot的情况下整合和配置spring-session的情况–只是用普通的Spring。

4.1. Dependencies

4.1.依赖性

First, if we’re adding spring-session to a standard Spring project, we’ll need to explicitly define:

首先,如果我们在一个标准的Spring项目中加入spring-session,我们需要明确定义。

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session</artifactId>
    <version>1.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.5.0.RELEASE</version>
</dependency>

The latest versions of the these modules can be found here: spring-session, spring-data-redis.

这些模块的最新版本可以在这里找到。spring-session, spring-data-redis

4.2. Spring Session Configuration

4.2.Spring会话配置

Now let’s add a configuration class for Spring Session:

现在让我们为Spring Session添加一个配置类。

@Configuration
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
    @Bean
    public JedisConnectionFactory connectionFactory() {
        return new JedisConnectionFactory();
    }
}

@EnableRedisHttpSession and the extension of AbstractHttpSessionApplicationInitializer will create and wire up a filter in front of all our security infrastructure to look for active sessions and populate the security context from values stored in Redis.

@EnableRedisHttpSessionAbstractHttpSessionApplicationInitializer的扩展将在我们所有的安全基础设施前创建并连接一个过滤器,以寻找活动会话,并从存储在Redis中的值填充安全上下文。

Let’s now complete this application with a controller and the security config.

现在让我们用一个控制器和安全配置来完成这个应用程序。

5. Application Configuration

5.应用配置

Navigate to our main application file and add a controller:

导航到我们的主应用程序文件,添加一个控制器。

@RestController
public class SessionController {
    @RequestMapping("/")
    public String helloAdmin() {
        return "hello admin";
    }
}

This will give us an endpoint to test.

这将给我们一个测试的端点。

Next, add our security configuration class:

接下来,添加我们的安全配置类。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
        UserDetails user = User.withUsername("admin")
            .password(passwordEncoder.encode("password"))
            .roles("ADMIN")
            .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.httpBasic()
            .and()
            .authorizeRequests()
            .antMatchers("/")
            .hasRole("ADMIN")
            .anyRequest()
            .authenticated();
        return http.build();
    }

    @Bean 
    public PasswordEncoder passwordEncoder() { 
        return new BCryptPasswordEncoder(); 
    } 
}

This protects our endpoints with basic authentication and sets up a user to test with.

这就用基本认证来保护我们的端点,并设置了一个用户来测试。

6. Test

6.测试

Finally, let’s test everything out – we’ll define a simple test here that’s going to allow us to do 2 things:

最后,让我们测试一下一切 – 我们将在这里定义一个简单的测试,它将允许我们做两件事。

  • consume the live web application
  • talk to Redis

Let’s first set things up:

首先让我们把事情安排好。

public class SessionControllerTest {

    private Jedis jedis;
    private TestRestTemplate testRestTemplate;
    private TestRestTemplate testRestTemplateWithAuth;
    private String testUrl = "http://localhost:8080/";

    @Before
    public void clearRedisData() {
        testRestTemplate = new TestRestTemplate();
        testRestTemplateWithAuth = new TestRestTemplate("admin", "password", null);

        jedis = new Jedis("localhost", 6379);
        jedis.flushAll();
    }
}

Notice how we’re setting up both of these clients – the HTTP client and the Redis one. Of course, at this point the server (and Redis) should be up and running – so that we can communicate with them via these tests.

注意我们是如何设置这两个客户端的–HTTP客户端和Redis客户端。当然,在这一点上,服务器(和Redis)应该已经启动并运行–这样我们就可以通过这些测试与它们进行通信。

Let’s begin by testing that Redis is empty:

让我们首先测试一下Redis是否为空。

@Test
public void testRedisIsEmpty() {
    Set<String> result = jedis.keys("*");
    assertEquals(0, result.size());
}

Now test that our security returns a 401 for unauthenticated requests:

现在测试一下,我们的安全系统对未经认证的请求返回401。

@Test
public void testUnauthenticatedCantAccess() {
    ResponseEntity<String> result = testRestTemplate.getForEntity(testUrl, String.class);
    assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode());
}

Next, we test that Spring Session is managing our authentication token:

接下来,我们测试Spring Session正在管理我们的认证令牌。

@Test
public void testRedisControlsSession() {
    ResponseEntity<String> result = testRestTemplateWithAuth.getForEntity(testUrl, String.class);
    assertEquals("hello admin", result.getBody()); //login worked

    Set<String> redisResult = jedis.keys("*");
    assertTrue(redisResult.size() > 0); //redis is populated with session data

    String sessionCookie = result.getHeaders().get("Set-Cookie").get(0).split(";")[0];
    HttpHeaders headers = new HttpHeaders();
    headers.add("Cookie", sessionCookie);
    HttpEntity<String> httpEntity = new HttpEntity<>(headers);

    result = testRestTemplate.exchange(testUrl, HttpMethod.GET, httpEntity, String.class);
    assertEquals("hello admin", result.getBody()); //access with session works worked

    jedis.flushAll(); //clear all keys in redis

    result = testRestTemplate.exchange(testUrl, HttpMethod.GET, httpEntity, String.class);
    assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode());
    //access denied after sessions are removed in redis
}

First, our test confirms that our request was successful using the admin authentication credentials.

首先,我们的测试确认了我们的请求是成功的,使用的是管理员认证凭证。

Then we extract the session value from the response headers and use it as our authentication in our second request. We validate that, and then clear all the data in Redis.

然后我们从响应头文件中提取会话值,并在第二个请求中使用它作为我们的认证。我们对其进行验证,然后清除Redis中的所有数据。

Finally, we make another request using the session cookie and confirm that we are logged out. This confirms that Spring Session is managing our sessions.

最后,我们使用会话cookie发出另一个请求,并确认我们已经登出。这证实了Spring Session正在管理我们的会话。

7. Conclusion

7.结论

Spring Session is a powerful tool for managing HTTP sessions. With our session storage simplified to a configuration class and a few Maven dependencies, we can now wire up multiple applications to the same Redis instance and share authentication information.

Spring Session是一个管理HTTP会话的强大工具。随着我们的会话存储被简化为一个配置类和几个Maven依赖项,我们现在可以将多个应用程序连接到同一个Redis实例,并共享认证信息。

As always all the examples are available over on Github.

像往常一样,所有的例子都可以在Github上找到