Spring Session이 필요한 이유
단일 서버에서는 HttpSession이 메모리에 저장되어 문제가 없지만, 다중 인스턴스 환경에서는 사용자가 다른 인스턴스로 라우팅되면 세션이 사라집니다. Spring Session은 세션 저장소를 외부(Redis, JDBC 등)로 분리하여 이 문제를 해결합니다.
Spring Session Redis 설정
의존성 추가
// build.gradle.kts
dependencies {
implementation("org.springframework.session:spring-session-data-redis")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
}
application.yml
spring:
session:
store-type: redis
timeout: 30m # 세션 만료 시간
redis:
namespace: myapp:session # Redis 키 접두사
flush-mode: on-save # 변경 시에만 저장 (immediate는 매 요청)
save-mode: on-set-attribute # 속성 변경 시에만 저장
data:
redis:
host: redis.internal
port: 6379
password: ${REDIS_PASSWORD}
timeout: 3s
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
이 설정만으로 HttpSession이 자동으로 Redis에 저장됩니다. 코드 변경 없이 기존 session.setAttribute()가 Redis로 투명하게 전환됩니다.
세션 직렬화 최적화
기본 Java 직렬화는 느리고 크기가 큽니다. JSON 직렬화로 전환하면 성능과 디버깅 편의성이 향상됩니다.
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
);
return new GenericJackson2JsonRedisSerializer(mapper);
}
}
| 직렬화 방식 | 크기 | 속도 | 가독성 |
|---|---|---|---|
| JdkSerializationRedisSerializer | 큼 | 느림 | ❌ |
| GenericJackson2JsonRedisSerializer | 중간 | 빠름 | ✅ |
| Kryo/Protobuf | 작음 | 매우 빠름 | ❌ |
동시 세션 제어
같은 계정으로 동시 로그인 수를 제한하는 설정입니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.sessionManagement(session -> session
.maximumSessions(1) // 최대 1개 세션
.maxSessionsPreventsLogin(false) // false: 이전 세션 만료 (기본)
// true: 새 로그인 차단
.expiredUrl("/login?expired=true")
.sessionRegistry(sessionRegistry())
)
.sessionManagement(session -> session
.sessionFixation().changeSessionId() // 세션 고정 공격 방지
.invalidSessionUrl("/login?invalid=true")
)
.build();
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
// Spring Session Redis 환경에서는 아래 사용
@Bean
public FindByIndexNameSessionRepository<?> sessionRepository(
RedisIndexedSessionRepository repository) {
return repository;
}
@Bean
public SpringSessionBackedSessionRegistry<?> springSessionRegistry(
FindByIndexNameSessionRepository<?> sessionRepository) {
return new SpringSessionBackedSessionRegistry<>(sessionRepository);
}
}
특정 사용자의 세션 강제 만료
@Service
@RequiredArgsConstructor
public class SessionManagementService {
private final FindByIndexNameSessionRepository<?> sessionRepository;
// 특정 사용자의 모든 세션 조회
public Map<String, ?> getUserSessions(String username) {
return sessionRepository
.findByPrincipalName(username);
}
// 특정 사용자의 모든 세션 만료
public void expireUserSessions(String username) {
Map<String, ?> sessions =
sessionRepository.findByPrincipalName(username);
sessions.keySet().forEach(sessionRepository::deleteById);
}
// 비밀번호 변경 시 다른 세션 만료
public void expireOtherSessions(String username, String currentSessionId) {
sessionRepository.findByPrincipalName(username)
.entrySet().stream()
.filter(e -> !e.getKey().equals(currentSessionId))
.forEach(e -> sessionRepository.deleteById(e.getKey()));
}
}
세션 보안 강화
@Configuration
public class SessionSecurityConfig {
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("SESSIONID");
serializer.setDomainName("example.com");
serializer.setCookiePath("/");
serializer.setUseHttpOnlyCookie(true); // JS 접근 차단
serializer.setUseSecureCookie(true); // HTTPS만
serializer.setSameSite("Lax"); // CSRF 방어
serializer.setCookieMaxAge(1800); // 30분
return serializer;
}
}
Spring Security CORS·CSRF 설정에서 다룬 CSRF 보호와 함께 사용하면 세션 기반 인증의 보안을 완성할 수 있습니다.
| 설정 | 기능 | 공격 방어 |
|---|---|---|
| HttpOnly | JS에서 쿠키 접근 차단 | XSS |
| Secure | HTTPS에서만 쿠키 전송 | MITM |
| SameSite=Lax | 크로스 사이트 요청 시 쿠키 제한 | CSRF |
| changeSessionId() | 로그인 시 세션 ID 변경 | Session Fixation |
Redis 세션 모니터링
# Redis에서 세션 키 확인
redis-cli KEYS "myapp:session:sessions:*"
# 특정 세션 조회
redis-cli HGETALL "myapp:session:sessions:abc123-def456"
# 세션 수 카운트
redis-cli DBSIZE
# TTL 확인
redis-cli TTL "myapp:session:sessions:abc123-def456"
Spring Data Redis Repository와 연동하면 세션 외 캐시 데이터도 동일한 Redis 인스턴스에서 관리할 수 있습니다.
정리
Spring Session Redis는 다중 인스턴스 환경에서 세션 클러스터링을 투명하게 해결합니다. JSON 직렬화로 성능과 디버깅 편의성을 확보하고, 동시 세션 제어로 보안을 강화하며, CookieSerializer로 세션 쿠키 보안을 완성하세요. 비밀번호 변경 시 다른 세션을 강제 만료하는 패턴은 실무에서 반드시 구현해야 할 보안 요구사항입니다.