Guia para a Sessão da Primavera

Guia para a Sessão da Primavera

1. Visão geral

Spring Session tem o objetivo simples de liberar o gerenciamento de sessão das limitações da sessão HTTP armazenada no servidor.

A solução facilita o compartilhamento de dados da sessão entre serviços na nuvem sem estar vinculado a um único contêiner (ou seja, Tomcat). Além disso, ele suporta várias sessões no mesmo navegador e o envio de sessões em um cabeçalho.

Neste artigo, usaremosSpring Session para gerenciar informações de autenticação em um aplicativo da web. EnquantoSpring Session pode persistir os dados usando JDBC, Gemfire ou MongoDB, usaremosRedis.

Para obter uma introdução aRedis, verifique o artigothis.

2. Um Projeto Simples

Vamos primeiro criar um projetoSpring Boot simples para usar como base para nossos exemplos de sessão mais tarde:


    org.springframework.boot
    spring-boot-starter-parent
    1.4.0.RELEASE
    



    
        org.springframework.boot
        spring-boot-starter-security
    
    
        org.springframework.boot
        spring-boot-starter-web
    
    
        org.springframework.boot
        spring-boot-starter-test
        test
    

Nosso aplicativo é executado comSpring Boote o pom pai fornece versões para cada entrada. A versão mais recente de cada dependência pode ser encontrada aqui:spring-boot-starter-security,spring-boot-starter-web,spring-boot-starter-test.

Vamos também adicionar algumas propriedades de configuração para nosso servidor Redis emapplication.properties:

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

3. Configuração de inicialização do Spring

Primeiro, vamos demonstrar a configuração deSpring Session com Boot.

Nota: Você não precisa concluir as seções 3 e 4. Apenas escolha um dependendo se você está ou não usandoSpring Boot para configurarSpring Session.

3.1. Dependências

Adicione estas dependências ao nosso projeto:


    org.springframework.boot
    spring-boot-starter-data-redis


    org.springframework.session
    spring-session

Estamos usando o pom pai de inicialização para definir as versões aqui, portanto elas garantem que funcionem com nossas outras dependências. A versão mais recente de cada dependência pode ser encontrada aqui:spring-boot-starter-data-redis,spring-session.

3.2. Configuração da Sessão Spring

Agora vamos adicionar uma classe de configuração paraSpring Session:

@Configuration
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
}

4. Configuração padrão do Spring (sem inicialização)

Vamos também dar uma olhada na integração e configuração despring-session sem Spring Boot - apenas com Spring simples.

4.1. Dependências

Primeiro, se estivermos adicionandospring-session a um projeto Spring padrão, precisaremos definir explicitamente:


    org.springframework.session
    spring-session
    1.2.2.RELEASE


    org.springframework.data
    spring-data-redis
    1.5.0.RELEASE

As versões mais recentes desses módulos podem ser encontradas aqui:spring-session,spring-data-redis

4.2. Configuração da Sessão Spring

Agora vamos adicionar uma classe de configuração paraSpring Session:

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

Como você pode ver, as diferenças são mínimas - agora só temos que definir nosso beanJedisConnectionFactory explicitamente - o Boot faz isso por nós.

Em ambos os tipos@EnableRedisHttpSessione a extensão deAbstractHttpSessionApplicationInitializer criará e conectará um filtro na frente de toda a nossa infraestrutura de segurança para procurar sessões ativas e preencher o contexto de segurança a partir dos valores armazenados emRedis .

Agora vamos concluir este aplicativo com um controlador e a configuração de segurança.

5. Configuração da Aplicação

Navegue até o arquivo principal do aplicativo e adicione um controlador:

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

Isso nos dará um ponto final para testar.

Em seguida, adicione nossa classe de configuração de segurança:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

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

Isso protege nossos terminais com autenticação básica e configura um usuário para testar.

6. Test

Finalmente, vamos testar tudo - definiremos um teste simples aqui que nos permitirá fazer 2 coisas:

  • consumir o aplicativo da web ao vivo

  • fale com Redis

Vamos primeiro configurar as coisas:

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

Observe como estamos configurando esses dois clientes - o cliente HTTP e o cliente Redis. Obviamente, nesse ponto, o servidor (e Redis) devem estar em funcionamento - para que possamos nos comunicar com eles através desses testes.

Vamos começar testando seRedis está vazio:

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

Agora teste se nossa segurança retorna um 401 para solicitações não autenticadas:

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

Em seguida, testamos seSpring Session está gerenciando nosso token de autenticação:

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

Primeiro, nosso teste confirma que nossa solicitação foi bem-sucedida usando as credenciais de autenticação de administrador.

Em seguida, extraímos o valor da sessão dos cabeçalhos de resposta e o usamos como autenticação na segunda solicitação. Validamos isso e, em seguida, limpamos todos os dados emRedis.

Por fim, fazemos outra solicitação usando o cookie da sessão e confirmamos que estamos desconectados. Isso confirma queSpring Session está gerenciando nossas sessões.

7. Conclusão

Spring Session é uma ferramenta poderosa para gerenciar sessões HTTP. Com nosso armazenamento de sessão simplificado para uma classe de configuração e algumas dependências Maven, agora podemos conectar vários aplicativos à mesma instânciaRedise compartilhar informações de autenticação.

Como sempre, você pode encontrar o código-fonteover on Github.