1년 전 진행했던 마실가실 프로젝트를 🛠️리팩토링하며 정리한 내용입니다.
마실가실의 숨겨진 비밀..
한 번 로그인 한 고객님은.. 서버를 내리기 전까지 로그아웃할 수 없습니다.
이제는 보내드리려고 합니다.
안녕히 가세요, 고객님..
JWT, Token 방식으로 Session과 동작 과정을 비교해 볼 수 있습니다.
로그인
세션 방식
* 서버는 세션 DB에 사용자의 정보를 저장 → 생성된 세션 ID를 (일반적으로) 쿠키에 넣어서 클라이언트에 반환
JWT 방식
* 서버는 사용자의 정보 가운데 민감 정보가 아닌 정보로 토큰을 생성해 클라이언트에 반환
로그인 이후 요청
세션 방식
* 클라이언트는 세션 ID를 포함한 쿠키를 서버에 전달 → 서버는 쿠키에서 세션 ID 추출 → 세션 DB에서 해당 아이디에 해당하는 레코드가 존재하는지 조회
JWT 방식
* 서버는 서명 부분을 확인해 해당 토큰이 조작되었는지 판별 → 조작되지 않았다면 토큰으로부터 사용자의 아이디와 같이 식별할 수 있는 정보를 꺼내서 사용
JWT와 로그아웃
순수 JWT 방식을 사용할 때, 로그아웃은 클라이언트 측에서 쿠키나 로컬 스토리지에서 JWT 토큰을 삭제하거나 무효화하는 방식으로 구현할 수 있습니다. 그러나, 토큰이 유출될 경우 피해를 막을 수 있는 방법이 없다는 한계가 존재합니다.
그래서 토큰 유출에 대비해 Access Token의 유효 기간은 짧게, Refresh Token으로 Access Token을 재발급해주는 방법을 대안으로 사용합니다. 이 과정을 위해 Refresh Token은 서버에 저장되어야 하며, 이 때 데이터 베이스를 필요로 합니다. 그래서 Redis를 설정하고자 합니다.
또한 반대로, 로그아웃 시에는 Refresh Token으로 Access Token을 발급받을 수 없게 해야합니다. 따라서 Refresh Token용 데이터 베이스를 사용하되, 로그아웃한 사용자의 Refresh Token을 데이터베이스에 저장하여 해당 데이터 베이스에 정보가 있을 경우 Access Token 발급을 거절할 수 있도록 합니다. 이를 블랙리스트 방식이라 합니다.
왜 Redis인가
1. Redis는 메모리 기반의 NoSQL 데이터베이스로, 기존의 관계형 데이터베이스(MySQL 등)보다 훨씬 빠르게 데이터를 처리할 수 있음
* 디스크 기반 데이터베이스이며, 많은 트래픽이 발생할 경우 성능에 문제가 생길 수 있음
2. 데이터에 TTL(Time-To-Live)을 설정할 수 있어, 만료된 JWT 토큰을 자동으로 삭제할 수 있음
* 기존 RDB에 저장 시, 주기적으로 삭제해야하는 번거러움 발생
Springboot에 Redis 설정
1. build.gradle
implementation 'org.springframework.boot:spring-boot-starter
2. application.yml
spring:
redis:
pool:
min-idle: 0
max-idle: 8
max-active: 8
port: 6379
host: 127.0.0.1
3. RedisProperties.java
@Component
@ConfigurationProperties(prefix = "spring.redis")
// application.yml 또는 application.properties 파일에서 spring.redis로 시작하는 설정 값을 이 클래스의 필드에 바인딩
@Getter @Setter
public class RedisProperties {
private int port;
private String host;
}
4. RedisConfig.java
@RequiredArgsConstructor
@Configuration
@EnableRedisRepositories
public class RedisConfig {
private final RedisProperties redisProperties;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
5. RedisUtil.java
@Component
@RequiredArgsConstructor
public class RedisUtil {
private final RedisTemplate<String, Object> redisTemplate;
private final RedisTemplate<String, Object> redisBlackListTemplate;
public void set(String key, Object o, int minutes) {
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(o.getClass()));
redisTemplate.opsForValue().set(key, o, minutes, TimeUnit.MINUTES);
}
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
public boolean delete(String key) {
return Boolean.TRUE.equals(redisTemplate.delete(key));
}
public boolean hasKey(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
public void setBlackList(String key, Object o, int minutes) {
redisBlackListTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(o.getClass()));
redisBlackListTemplate.opsForValue().set(key, o, minutes, TimeUnit.MINUTES);
}
public Object getBlackList(String key) {
return redisBlackListTemplate.opsForValue().get(key);
}
public boolean deleteBlackList(String key) {
return Boolean.TRUE.equals(redisBlackListTemplate.delete(key));
}
public boolean hasKeyBlackList(String key) {
return Boolean.TRUE.equals(redisBlackListTemplate.hasKey(key));
}
}
+ 블랙 리스트 외 다른 로그아웃 방법
프론트에서 토큰 삭제
* storage에 있는 토큰을 삭제하는 방법
* 장점: 간단
* 단점: 토큰이 유출되었을 경우, 보안상 위험
++ 추가적인 보완책
IP 주소나 기기 정보를 토큰에 포함시켜 토큰을 사용하는 클라이언트 신원 확인
다른 기기나 IP 주소에서 사용되는 것을 방지
🙋♀️
본 포스트는 공부 목적으로 작성하였습니다.
보시는 도중 잘못된 부분이나 개선할 부분이 있다면 댓글로 알려주시면 수정하도록 하겠습니다.
📑
참고 자료
'PlayGround > 마실가실 리팩토링' 카테고리의 다른 글
[1년 후 마실가실] 패키지 구조 (3) | 2024.09.03 |
---|---|
[1년 후 마실가실] 쉬어가는 마실가실 - 디버깅 (0) | 2024.08.26 |
[1년 후 마실가실] Spring Security, JWT 공부 (0) | 2024.08.11 |
[1년 후 마실가실] Custom Exception (0) | 2024.08.10 |
[1년 후 마실가실] Test Code 작성 (0) | 2024.08.05 |