기록하며 성장한다 - 개발, 회고

개발 회고록

[ 개발 회고록 ] Redis Session으로 분산 환경에서 세션 관리하기

전대홍 2024. 1. 24. 00:31

어라? 분명 로그인을 했는데 . . . 왜 세션이 해제되어있지 ???

 

 


서론

현재 배달과 관련 된 토이프로젝트를 진행하고 있다.

토이 프로젝트 링크 <<<

기존에 필자가 Session 로그인을 개발했던 방식은 아래 코드와 같았다.

@PostMapping("/user/login")
public String login(@RequestBody @Valid UserLoginDto userDto,
                    HttpServletRequest request) {


    User user = userService.login(userDto);
    if ( user == null ) return ResponseCode.LOGIN_ERROR_CODE;

    HttpSession session = request.getSession();
    session.setAttribute("loginUser", user);
    session.setMaxInactiveInterval(60 * 30);

    return ResponseCode.SUCCESS_CODE;
}

@GetMapping("/userInfoPage")
public String getUserInfoPage(HttpSession session, Model model) {

    // Session 끊킬 시 redirect
    User user = (User) session.getAttribute("loginUser");
    boolean loginSession = user != null;
    if ( !loginSession ) return "redirect:/loginPage";

    return ResponseCode.SUCCESS_CODE;
}

이런 방식으로, 항상 Session 을 가지고 다니면서, 컨트롤러를 부를 때마다 그 세션 정보를 활용하였다.

물론 이 방법이 틀린 방법은 아니다. 레거시한 코드들을 보면 대부분 이렇게 세션을 가지고 다닌다.

그러나, 분산 서버를 사용하게 되면서 이제 이런 방식은 잘 사용하지 않게되었다.

 


본론

1. 왜 세션을 가지고 다니면 안될까?

이 이유는 간단하다. 분산 서버이므로, A 서버에서 로그인을 하여 세션 정보를 가지고 다녀도, B 서버 응답이 오면 그 세션 정보를 가지고 올 수가 없다.

즉, 메인페이지에서 로그인을 하였는데, 게시판에 가보니 로그인이 풀려있는 상태가 될 수 있다는 것이다.

 

 

2. 어떻게 해결할 수 있을까?

해결 방법은 여러가지가 있지만, 필자는 크게 2가지를 생각하였다.

먼저 하나는 스티키 세션 이라는 로드 밸런싱 방식을 사용하는 것이다. 이는 처음 접속한 서버로만 계속 전송하는 방법이다. 즉, 처음에 로그인한 서버가 A 서버라면, 그 유저에게는 계속 A서버만 연결해주는 것이다.

그러나 이런 방법은 자칫, 하나의 서버에 유저가 몰릴 수 있기 때문에, 서버를 분산시킨 의미가 퇴색 될 수 있으므로 과부하가 생길 수 있으며, 해당 서버에 장애가 생길 시 모든 세션들이 소실 될 수 있는 문제가 있다.

그래서 긴급한 상황에만 사용하는 것이 좋다.

다른 방법은 바로 Redis Session 을 이용하는 방법이다.

이는 세션 클러스터링 방식 중 하나이며, Redis 를 이용하는 방법이다.

 

 

3. Redis Session

레디스 세션을 사용하려면 먼저 gradle 에 아래 내용을 추가해주어야 한다.

implementation 'org.springframework.session:spring-session-data-redis'

 

그 다음은 properties 에 아래 내용을 추가해준다.

spring.data.session.store-type = redis
spring.data.seesion.redis.flush-mode = on_save

 

이후 기본적인 RedisConfig 를 만들어준다.

@Configuration
public class RedisConfig {
	@Value("${spring.redis.host}")
	public String host;

	@Value("${spring.redis.port}")
	public int port;


	@Bean
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
		redisTemplate.setConnectionFactory(connectionFactory);
		return redisTemplate;
        }

	@Bean
	public RedisConnectionFactory redisConnectionFactory() {
		RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
		configuration.setHostName(host);
		configuration.setPort(port);
		return new LettuceConnectionFactory(configuration);
	}
}

 

그리고 로그인 관련 서비스를 만들어서, 해당 서비스에 private final HttpSession session; 으로 공통 된 session 을 만들고, 해당 서비스에서 로그인 관련 로직을 만들면 된다.

마지막으로, main 메서드가 위치하는 클래스 위에 @EnableRedisHttpSession 어노테이션을 추가해주면 된다.

@EnableRedisHttpSession
@SpringBootApplication
public class RedisSessionStorageTest {
	public static void main(String[] args) {
		//...
	}
}

 

이후 테스트를 진행해보았다.

 

Redis 에 정상적으로 Session 정보가 저장되며,

로그아웃시에도 정상적으로 로그아웃이 된다.

이로써 분산된 서버에서도 로그인을 유지할 수 있게 클러스터링이 되었다.

 

 


결론

성능, 트래픽 등을 고려하였을 때 분산 서버는 빠질 수 없다.

이전에 분산 서버를 고려하여 AWS S3 를 활용하여 이미지를 저장할 수 있게 하였는데, Session 을 이용한 로그인 기능도 마찬가지이다.

물론 스프링 시큐리티를 활용하여 JWT 를 하는 방법도 있다고는 하는데, 필자는 세션 밖에 안써봐서 이부분은 아직 잘 모르겠다. 다음에는 JWT 를 꼭 공부해보아야 겠다.