본문 바로가기

Backend/Spring

Spring Session With Redis 설정하기

사용 기술 스택

Redis

Spring Security

Spring Boot

Spring Session: provides an API and implementations for managing a user’s session information.

HttpSession: 문서에서 HttpSession을 Spring Session으로 통합할 수 있다(Spring Session provides transparent integration with HttpSession)고 하는데, 이게 모듈인지? 아니면 개념적 용어인지? 잘 모르겠다.

Redis를 Spring Session 저장소로 사용하기

Dependencies

dependencies {
    implementation 'org.springframework.session:spring-session-data-redis'
}

Configuration

server:
  servlet:
    session:
      timeout: 54000

spring:
  main:
    allow-bean-definition-overriding: true

  session:
    store-type: redis
    redis:
      flush-mode: on_save
      namespace: spring:session

    redis:
        lettuce:
          pool:
            max-active: 10
            max-idle: 10
            min-idle: 2
        host: localhost
        port: 6378

https://docs.spring.io/spring-session/reference/guides/boot-redis.html#boot-spring-configuration

Under the hood, Spring Boot applies configuration that is equivalent to manually adding `@EnableRedisHttpSession` annotation. This creates a Spring bean with the name of `springSessionRepositoryFilter` that implements `Filter`. The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.

이 글에 따르면 spring.session.store-type=redis를 작성해주면 @EnableRedisHttpSession 없이 configuration을 적용해준다고 하는데 왜인지 연결이 되지 않아 RedisConfig도 작성해주었다.

@EnableRedisHttpSessionspringSessionRepositoryFilter Bean을 작성해준다. 이 Bean이 HttpSession을 SpringSession과 통합해주는 역할을 한다.

@EnableSpringHttpSession
@Configuration
public class RedisConfig {

    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.host}")
    private String host;

    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    @Bean
    public HeaderHttpSessionIdResolver httpSessionIdResolver() {
        return HeaderHttpSessionIdResolver.xAuthToken();
    }
}

@EnableSpringHttpSession 사용시 sessionEventHttpSessionListenerAdapter Bean을 재정의한다.

⛔ The bean 'sessionEventHttpSessionListenerAdapter', defined in class path resource [org/springframework/boot/autoconfigure/session/RedisSessionConfiguration$SpringBootRedisHttpSessionConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/session/config/annotation/web/http/SpringHttpSessionConfiguration.class] and overriding is disabled.

해당 에러로 spring.main.allow-bean-definition-overriding=true 를 추가해주었다.

How it works?

@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final IbAuthenticationEntryPoint authenticationEntryPoint;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/account/logout").authenticated()
            .antMatchers("/account/**").permitAll()
            .antMatchers("/**").permitAll()
            .and().formLogin().disable().csrf().disable()
            .exceptionHandling()
            .authenticationEntryPoint(authenticationEntryPoint)
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        return http.build();
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }
}
@RequiredArgsConstructor
@RestController
@RequestMapping("/account")
public class AccountController {

    private final AccountService accountService;

    private final AccountMapper accountMapper;

    @PostMapping("/register")
    public ResponseEntity<RegisterResponse> register(@RequestBody RegisterRequest registerRequest) {
        return ResponseEntity.ok().body(accountService.registerByRegisterRequest(registerRequest));
    }

    @PostMapping("/login")
    public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest loginRequest) {
        Account account = accountService.loginByLoginRequest(loginRequest);

        Authentication authentication = new UsernamePasswordAuthenticationToken(
            new IbUserDetails(account.getEmail(), account.getAccountId(), account.getPassword()),
            account.getPassword(),
            null);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        return ResponseEntity.ok(accountMapper.accountToLoginResponse(account));
    }

    @PostMapping("/logout")
    public ResponseEntity<Object> logout(HttpSession session) {
        session.invalidate();
        return ResponseEntity.ok(null);
    }
}

Login Flow

  1. 클라이언트가 login api 를 요청한다.
  2. DB에서 정보를 확인한다.
    정보가 일치하지 않으면 Exception을 띄운다.
  3. SecurityContextHolder에 Authentication 객체를 넣어주고, authenticated 시킨다.
    필터에서 Redis에 Session을 저장시킨다.
  4. X-Auth-Token 헤더로 sessionId를 보내준다.

'Backend > Spring' 카테고리의 다른 글

@Transactional 전파 전략  (0) 2023.05.07
DI와 IoC  (0) 2023.04.07
Spring Container와 Bean의 Lifecycle  (0) 2023.04.07
Spring Boot Controller Test  (0) 2023.04.07
SpringFox 3.0.0 Server URL Error  (0) 2023.03.03