Автор оригинала: Tim Schimandle.
1. Обзор
Spring Session имеет простую цель освободить управление сеансами от ограничений HTTP-сеанса, хранящегося на сервере.
Это решение позволяет легко обмениваться данными сеанса между службами в облаке, не привязываясь к одному контейнеру (например, Tomcat). Кроме того, он поддерживает несколько сеансов в одном браузере и отправку сеансов в заголовке.
В этой статье мы будем использовать Spring Session для управления информацией аутентификации в веб-приложении. В то время как Spring Session может сохранять данные с помощью JDBC, Gemfire или MongoDB, мы будем использовать Redis .
Для ознакомления с Redis ознакомьтесь с этой статьей.
2. Простой Проект
Давайте сначала создадим простой проект Spring Boot , который будет использоваться в качестве основы для наших примеров сеансов позже:
org.springframework.boot spring-boot-starter-parent 2.4.0 org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test
Наше приложение работает с 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. Конфигурация пружинной Загрузки
Для весенней загрузки достаточно добавить следующие зависимости , а об остальном позаботится автоконфигурация:
org.springframework.boot spring-boot-starter-data-redis org.springframework.session spring-session-data-redis
Мы используем загрузочный родитель pom для установки версий здесь, поэтому они гарантированно будут работать с другими нашими зависимостями. Последнюю версию каждой зависимости можно найти здесь: spring-boot-starter-data-redis , spring-session .
4. Стандартная конфигурация пружины (без загрузки)
Давайте также рассмотрим интеграцию и настройку spring-session без загрузки Spring – просто с помощью обычной пружины.
4.1. Зависимости
Во-первых, если мы добавляем spring-session в стандартный проект Spring, нам нужно будет явно определить:
org.springframework.session spring-session 1.2.2.RELEASE org.springframework.data spring-data-redis 1.5.0.RELEASE
Последние версии этих модулей можно найти здесь: spring-session , spring-data-redis .
4.2. Конфигурация Весенней сессии
Теперь давайте добавим класс конфигурации для Весенней сессии :
@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() { Setresult = jedis.keys("*"); assertEquals(0, result.size()); }
Теперь проверьте, что наша система безопасности возвращает 401 для запросов, не прошедших проверку подлинности:
@Test public void testUnauthenticatedCantAccess() { ResponseEntityresult = testRestTemplate.getForEntity(testUrl, String.class); assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode()); }
Затем мы проверим, что Весенняя сессия управляет нашим токеном аутентификации:
@Test public void testRedisControlsSession() { ResponseEntityresult = testRestTemplateWithAuth.getForEntity(testUrl, String.class); assertEquals("hello admin", result.getBody()); //login worked Set 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 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 сеанса, и подтверждаем, что мы вышли из системы. Это подтверждает, что Весенняя сессия управляет нашими сессиями.
7. Заключение
Spring Session – это мощный инструмент для управления HTTP-сессиями. Теперь, когда наше хранилище сеансов упрощено до класса конфигурации и нескольких зависимостей Maven, мы можем подключить несколько приложений к одному и тому же экземпляру Redis и обмениваться информацией об аутентификации.
Как всегда, все примеры доступны на Github .