Перейти к основному содержимому

Путеводитель по весенней сессии

· 5 мин. чтения

1. Обзор

Spring Session имеет простую цель освободить управление сеансом от ограничений сеанса HTTP, хранящегося на сервере.

Решение упрощает обмен данными сеанса между службами в облаке без привязки к одному контейнеру (например, Tomcat). Кроме того, он поддерживает несколько сеансов в одном браузере и отправку сеансов в заголовке.

В этой статье мы будем использовать Spring Session для управления данными аутентификации в веб-приложении. Хотя Spring Session может сохранять данные с помощью JDBC, Gemfire или MongoDB, мы будем использовать Redis .

Для ознакомления с Redis ознакомьтесь с этой статьей.

2. Простой проект

Давайте сначала создадим простой проект Spring Boot , чтобы использовать его в качестве основы для наших примеров сеансов позже:

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</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>

Наше приложение работает с Spring Boot , а родительский pom предоставляет версии для каждой записи. Последнюю версию каждой зависимости можно найти здесь: spring-boot-starter-security , spring-boot-starter-web , spring-boot-starter-test .

Давайте также добавим некоторые свойства конфигурации для нашего сервера Redis в application.properties :

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

3. Конфигурация весенней загрузки

Для 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>

Мы используем загрузочный родительский pom для установки версий здесь, поэтому они гарантированно будут работать с другими нашими зависимостями. Последнюю версию каждой зависимости можно найти здесь: spring-boot-starter-data-redis , spring-session .

4. Стандартная конфигурация Spring (без загрузки)

Давайте также взглянем на интеграцию и настройку spring-session без Spring Boot — просто с помощью простого Spring.

4.1. Зависимости

Во- первых, если мы добавляем spring-session в стандартный проект Spring, нам нужно явно определить:

<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>

Последние версии этих модулей можно найти здесь: spring-session , spring-data-redis .

4.2. Конфигурация весеннего сеанса

Теперь давайте добавим класс конфигурации для Spring Session :

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

@EnableRedisHttpSession и расширение AbstractHttpSessionApplicationInitializer создадут и подключит фильтр перед всей нашей инфраструктурой безопасности для поиска активных сеансов и заполнения контекста безопасности значениями, хранящимися в Redis .

Давайте теперь завершим это приложение контроллером и конфигурацией безопасности.

5. Конфигурация приложения

Перейдите к нашему основному файлу приложения и добавьте контроллер:

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

Это даст нам конечную точку для тестирования.

Затем добавьте наш класс конфигурации безопасности:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("password"))
.roles("ADMIN");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.authorizeRequests()
.antMatchers("/").hasRole("ADMIN")
.anyRequest().authenticated();
}

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

Это защищает наши конечные точки с помощью базовой аутентификации и настраивает пользователя для тестирования.

6. Тест

Наконец, давайте все проверим — здесь мы определим простой тест, который позволит нам сделать 2 вещи:

  • использовать живое веб-приложение
  • поговорить с Редисом

Давайте сначала настроим вещи:

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();
}
}

Обратите внимание, как мы настраиваем оба этих клиента — клиент HTTP и клиент Redis. Конечно, на данный момент сервер (и Redis) должны быть запущены и работать, чтобы мы могли общаться с ними через эти тесты.

Давайте начнем с проверки того, что Redis пуст:

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

Теперь проверьте, что наша система безопасности возвращает 401 для неаутентифицированных запросов:

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

Затем мы проверяем, что 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
}

Во-первых, наш тест подтверждает, что наш запрос был успешным с использованием учетных данных для аутентификации администратора.

Затем мы извлекаем значение сеанса из заголовков ответа и используем его в качестве аутентификации во втором запросе. Мы проверяем это, а затем очищаем все данные в Redis .

Наконец, мы делаем еще один запрос, используя файл cookie сеанса, и подтверждаем, что вышли из системы. Это подтверждает, что Spring Session управляет нашими сеансами.

7. Заключение

Spring Session — мощный инструмент для управления HTTP-сессиями. С нашим хранилищем сеансов, упрощенным до класса конфигурации и нескольких зависимостей Maven, мы теперь можем подключить несколько приложений к одному и тому же экземпляру Redis и обмениваться информацией для аутентификации.

Как всегда, все примеры доступны на Github .