전체 소스에 대한 깃헙 링크는 여기에 있습니다.
Session을 저장하면 일반적으로 서버 메모리에 저장됩니다.
이런 경우 서버 이중화 시에 서버마다 서로 다른 세션을 사용하므로 세션을 클러스터링(군집화)해서 하나로 묶어줘야 합니다.
Session 값을 DB에 저장해서 세션 클러스터링을 구축할 수도 있지만 DB에 저장하면 그렇게 되면 매번 하드디스크에 접근하게 되는 것이라 디스크 I/O가 지속적으로 일어나 속도가 느리다는 단점이 있습니다.
WAS 기능을 이용한 세션 클러스터링 기능을 이용할 수도 있지만 이렇게 하는 경우 서버의 스케일이 커질 때 관리가 어려워집니다.
하지만 인메모리 DB인 Redis를 이용해서 세션 클러스터링을 해준다면 위의 단점들을 극복할 수 있습니다.
JWT를 이용한다면 세션을 이용하지 않기 때문에 이 모든 문제를 해결할 수 있지만 세션을 이용한다면 위와 같은 문제가 발생할 수 밖에 없으니 가장 Best 방법인 Redis에 세션을 저장하여 세션 클러스터링을 구현해봅시다.
환경 준비
1. Redis 설치
첫번째로 Redis를 설치해줘야 합니다.
Error creating bean with name 'enableRedisKeyspaceNotificationsInitializer'
설치하지 않으면 위의 Github 프로젝트 시작 시 위와 같은 에러 발생.
2. Gradle 의존성 설정
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.session:spring-session-data-redis'
spring-session 의존성을 추가해서 세션을 저장할 코드인 session.setAttribute의 기능을 spring-session의 기능으로 바꾸게 됩니다.
3. application.yml 설정
spring:
redis:
host: 127.0.0.1
password: 1234
port: 6379
session:
store-type: redis
Redis port는 기본을 6379로 사용합니다. Redis가 설치된 호스트와 비밀번호, 포트를 입력합니다.
store-type을 redis로 지정해서 redis에 session이 저장되도록 설정합니다.
4. 시작 Class에서 EnableRedisHttpSession 어노테이션 추가
5. Redis 세팅
config Class 생성 후 아래와 같이 작성
lettuceConnectionFactory - lettuce를 이용해서 Redis에 접속
redisTemplate - redis에서 값을 가져올 때 직렬화하는 방식(코드에서는 StringRedisSerializer 사용). 세션을 구현할 때 꼭 필요한 설정은 아니지만 redis를 세팅할 때 적어주는 코드이므로 기재했습니다.
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.port}")
private int port;
@Bean
public LettuceConnectionFactory lettuceConnectionFactory() {
final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host, port);
redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
final RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(lettuceConnectionFactory());
template.setDefaultSerializer(new StringRedisSerializer());
return template;
}
참고 - 직렬화 방식 종류
Jackson2JsonRedisSerializer - 하나의 Model에 한해서 Json으로 파싱해줌.
GenericJackson2JsonRedisSerializer - 여러 Model을 파싱. 다만 결과 값에 클래스 값이 포함됨.
StringRedisSerializer - String으로 직렬화. JSON 파싱이 필요하다면 ObjectMapper.readValue로 파싱해주면 됨.
간단한 세션 구현
@PostMapping("/login")
public ResponseEntity<?> login(HttpSession session){
UUID uid = Optional.ofNullable(UUID.class.cast(session.getAttribute("uid")))
.orElse(UUID.randomUUID());
session.setAttribute("uid", uid);
return new ResponseEntity<>("로그인 성공", HttpStatus.OK);
}
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpSession session){
session.invalidate();
return new ResponseEntity<>("로그아웃 성공", HttpStatus.OK);
}
@SpringBootTest
@AutoConfigureMockMvc
public class RedisControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testLoginEndpoint() throws Exception {
mockMvc.perform(post("/login"))
.andExpect(status().isOk());
}
}
Postman이나 브라우저로 테스트해도 되지만 Junit 코드로 테스트하면 더 간편합니다.
클라이언트에서 /login 요청을 하면 초기엔 서버에 저장된 session 정보가 없으니 random 값을 부여받아서 로그인을 시킵니다.
세션에 대한 정보는 원래 서버 로컬 메모리에 저장되어야합니다.
implementation 'org.springframework.session:spring-session-data-redis'
하지만 위의 의존성으로 인해 session.setAttribute가 서버 로컬 메모리에 저장되지 않고 redis에 세션 값이 저장되게 됩니다.
redis-cli를 통해 keys * 로 확인하면 아래와 같이 세션 정보 확인 가능합니다.
지우고 다시 확인해보고 싶다면 FLUSHALL를 입력하면 key를 전부 삭제 할 수 있습니다.
- spring:session:sessions:expires:(session id) - 해당 세션의 만료 키(String 타입)
- spring:session:expirations:(expire time) - expire time에 삭제될 세션 정보(Set 타입)
- spring:session:sessions:(session id) - 세션 생성 시간, 마지막 세션 조회 시간, 최대 타임아웃, 해당 세션에 저장한 데이터(Hash 타입)
Redis라는 외부 저장소를 이용했기 때문에 서버를 몇대를 더 돌리든 redis 저장소에 있는 Session을 가져와서 사용할 수 있으므로 세션 클러스터링이 가능해집니다.
'🍃 Spring' 카테고리의 다른 글
Spring Boot로 만드는 Spring Security 로그인 구현 - Session(1) (2) | 2022.10.10 |
---|---|
Spring에서 인터페이스가 실행이 되지? (0) | 2022.10.09 |
gradle build bootJar 차이 (0) | 2022.09.23 |
@PostConstruct와 bean 생명주기 (0) | 2022.09.19 |
Redirect와 Forward 차이점, 특징 및 실무 사용법 (0) | 2022.09.15 |