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 .