Рубрики
Без рубрики

Руководство к весенней сессии

В статье представлен проект Spring Session, который отделяет управление сеансами от контейнера сервера.

Автор оригинала: 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() {
    Set result = jedis.keys("*");
    assertEquals(0, result.size());
}

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

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

Затем мы проверим, что Весенняя сессия управляет нашим токеном аутентификации:

@Test
public void testRedisControlsSession() {
    ResponseEntity result = 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 .