Spring MVC에서 Spring Session과 Redis를 활용한 분산 세션 구현

다수의 서버에 애플리케이션을 배포하고 Nginx를 통해 요청을 전달하는 환경에서, 사용자가 한 서버에 로그인하면 다른 서버에서도 재로그인할 필요 없이 세션이 유지되도록 하는 기술이 필요합니다. 본 문서에서는 Spring Session과 Redis를 사용하여 이러한 분산 세션 환경을 구축하는 방법을 설명합니다.

1. Maven 의존성 추가

먼저, 프로젝트의 pom.xml 파일에 다음과 같은 Maven 의존성을 추가합니다.


<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    <version>2.7.0</version> <!-- 최신 버전 사용 권장 -->
    <type>pom</type>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-core</artifactId>
    <version>2.7.0</version> <!-- 최신 버전 사용 권장 -->
</dependency>

2. Spring 설정 파일 구성 (redis-config.xml)

Spring 설정 파일에 Redis를 세션 저장소로 사용하도록 다음과 같이 빈(Bean)을 등록합니다. 사용자 정의 Redis 직렬화 방식을 적용할 수 있으며, 세션 만료 시간 및 Redis 네임스페이스를 설정할 수 있습니다. 쿠키 이름과 경로도 필요에 맞게 설정합니다.


<!-- Spring Session Configuration -->
<!-- RedisSerializer 설정 (필요에 따라 사용자 정의) -->
<bean id="redisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="genericRedisSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>

<!-- Spring Session Redis Configuration -->
<bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
    <!-- 세션 비활성 최대 유지 시간 (초 단위, 예: 8시간) -->
    <property name="maxInactiveIntervalInSeconds" value="28800"/>
    <!-- Redis에 저장될 세션 키의 네임스페이스 (프로젝트별로 고유하게 설정) -->
    <property name="redisNamespace" value="my_app:sessions"/>
    <!-- RedisTemplate Bean 참조 (RedisTemplate 빈이 정의되어 있어야 함) -->
    <property name="redisTemplate" ref="sessionRedisTemplate"/>
</bean>

<!-- 사용자 정의 RedisTemplate Bean (Jackson 직렬화 사용) -->
<bean id="sessionRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="redisConnectionFactory"/> <!-- RedisConnectionFactory 빈 참조 -->
    <property name="keySerializer" ref="redisSerializer"/>
    <property name="valueSerializer" ref="genericRedisSerializer"/>
    <property name="hashKeySerializer" ref="redisSerializer"/>
    <property name="hashValueSerializer" ref="genericRedisSerializer"/>
</bean>

<!-- Cookie Serializer Configuration -->
<bean id="cookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
    <!-- 클라이언트에서 사용될 세션 쿠키의 이름 (고유하게 설정) -->
    <property name="cookieName" value="MYAPP_SESSION_ID"/>
    <!-- 쿠키의 유효 경로 -->
    <property name="cookiePath" value="/"/>
    <!-- 쿠키 도메인 설정 (필요시) -->
    <!-- <property name="domainName" value=".example.com"/> -->
    <!-- 쿠키의 Max-Age 설정 (초 단위, 세션 만료 시간과 일치시키는 것이 좋음) -->
    <property name="cookieMaxAge" value="28800"/>
</bean>

<!-- HttpSession Strategy Configuration -->
<bean id="httpSessionStrategy" class="org.springframework.session.web.http.CookieHttpSessionStrategy">
    <property name="cookieSerializer" ref="cookieSerializer"/>
</bean>

3. 세션 데이터 관리

로그인 성공 후 세션에 데이터를 저장하고 조회하는 과정은 일반적인 HttpSession 사용 방식과 동일합니다. Spring Session은 내부적으로 이 과정을 Redis에 연결하여 처리합니다. 다음은 세션 데이터를 관리하는 예시 유틸리티 클래스입니다.


import org.springframework.session.ExpiringSession;
import org.springframework.session.SessionRepository;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import java.io.Serializable;

// LoginInfo 클래스는 Serializable을 구현해야 합니다.
class LoginInfo implements Serializable {
    private static final long serialVersionUID = 1L;
    private String userId;
    private String username;

    public LoginInfo(String userId, String username) {
        this.userId = userId;
        this.username = username;
    }

    // Getters and Setters
    public String getUserId() { return userId; }
    public void setUserId(String userId) { this.userId = userId; }
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

    @Override
    public String toString() {
        return "LoginInfo{" +
               "userId='" + userId + '\'' +
               ", username='" + username + '\'' +
               '}';
    }
}

public class SessionUtil {

    // Spring Session의 SessionRepository를 주입받습니다.
    @Resource(name = "sessionRepository") // Spring Data Redis 설정을 통해 자동으로 빈이 생성됩니다.
    private SessionRepository<? extends ExpiringSession> sessionRepository;

    private static final String LOGIN_INFO_ATTRIBUTE_NAME = "USER_LOGIN_DETAILS";

    /**
     * 현재 요청의 HttpSession 객체를 가져옵니다.
     * RequestContextHolder를 사용하여 현재 스레드의 요청 컨텍스트에 접근합니다.
     */
    private HttpServletRequest getCurrentRequest() {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        return attr.getRequest();
    }

    /**
     * 현재 세션 ID를 반환합니다.
     * @return 현재 세션 ID
     */
    public String getCurrentSessionId() {
        return getCurrentRequest().getSession().getId();
    }

    /**
     * 세션에서 로그인 정보를 가져옵니다.
     * @return LoginInfo 객체 또는 null
     */
    public LoginInfo getLoginInfo() {
        HttpServletRequest request = getCurrentRequest();
        // Spring Session은 HttpSession 인터페이스를 구현하므로 getAttribute를 그대로 사용합니다.
        return (LoginInfo) request.getSession().getAttribute(LOGIN_INFO_ATTRIBUTE_NAME);
    }

    /**
     * 세션에 로그인 정보를 설정합니다.
     * @param loginInfo 설정할 LoginInfo 객체
     * @return 설정된 세션 ID
     */
    public String setLoginInfo(LoginInfo loginInfo) {
        HttpServletRequest request = getCurrentRequest();
        request.getSession().setAttribute(LOGIN_INFO_ATTRIBUTE_NAME, loginInfo);
        return request.getSession().getId();
    }

    /**
     * 세션에서 로그인 정보를 제거하여 로그아웃 처리합니다.
     */
    public void logout() {
        HttpServletRequest request = getCurrentRequest();
        request.getSession().removeAttribute(LOGIN_INFO_ATTRIBUTE_NAME);
        // 세션을 만료시키려면 invalidate()를 호출할 수 있습니다.
        // request.getSession().invalidate();
    }

    /**
     * 특정 세션 ID를 사용하여 로그인 정보를 조회합니다.
     * @param sessionId 조회할 세션 ID
     * @return LoginInfo 객체 또는 null
     */
    public LoginInfo getLoginInfoBySessionId(String sessionId) {
        // SessionRepository를 사용하여 직접 세션에 접근합니다.
        ExpiringSession session = sessionRepository.getSession(sessionId);
        if (session != null) {
            return (LoginInfo) session.getAttribute(LOGIN_INFO_ATTRIBUTE_NAME);
        }
        return null;
    }
}

SessionUtil 클래스는 현재 요청의 HttpSession을 사용하여 사용자 정보를 저장하고 조회합니다. 또한 SessionRepository를 직접 사용하여 특정 세션 ID로도 사용자 정보를 조회할 수 있는 기능을 제공합니다. LoginInfo 클래스는 직렬화 가능하도록 Serializable 인터페이스를 구현해야 합니다.

태그: Spring MVC Spring Session Redis 분산 세션 HTTP 세션

7월 21일 00:59에 게시됨