사용자가 로그인할 때 발급되는 토큰
이 토큰은 사용자의 정보를 담고 있어서, 사용자가 API 호출할 때 매번 "나는 이 사람이 맞습니다!"라고 증명하는데 사용됨
이 JWT에는 짧은 시간 동안만 유효하고, 만료시 다시 로그인하거나 Refresh Token을 이용해 새로운 AccessToken이 필요
-> 기본적으로 JWT는 자체적으로 만료되기 전까지 유효함
근데 사용자가 로그아웃했거나, 보안상의 이유로 강제 로그아웃을 해야한다면 어떻게 처리할까?
그냥 토큰 지워버리면 되는거 아닌가요?
JWT는 클라이언트 측에 저장되기 때문에 서버가 JWT를 직접 삭제하거나 만료시킬 방법은 존재하지 않음
-> 여러 서비스에서 인증을 공유할 때, 한 서비스에서 로그아웃 시 다른 서비스에서도 즉시 반영되어야 함


각각의 서비스가 독립적으로 동작하면서도, 서로 데이터를 주고 받을 수 있도록 도와주는 메시지 중계소
JWT 인증을 구현한다!
JWT: 인증 토큰 발급/검증
Redis: 토큰 블랙리스트 관리, Refresh Token 저장
RabbitMQ: 분산 시스템 간 이벤트 통신 (로그아웃 알림 등)
Spring Cloud Gateway: API Gateway 역할
Spring Security: 인증/인가 처리
Eureka: 서비스 디스커버리
[Client]
│
▼ HTTPS
[API Gateway] ───▶ [Auth Service] ↔ Redis (Token Storage)
│ │
│ ▼
└──▶ [Blog Service] ←─ RabbitMQ (Logout Events)
brew install redis
redis-server --version

위와 같이 설치하기
redis-server
brew services start redis
brew services stop redis
redis-cli ping
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.password=1234
@Configuration
public class RedisConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(); // Lettuce는 비동기 방식으로 높은 성능 제공
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer()); // Key를 문자열로 직렬화
redisTemplate.setValueSerializer(new StringRedisSerializer()); // Value를 문자열로 직렬화
return redisTemplate;
}
}
💡 왜 직렬화가 필요할까?
레디스는 데이터를 네트워크를 통해 주고 받는데, 네트워크에서 데이터를 보내려면 객체를 문자열 혹은 바이트 형태로 바꿔야한다.
이 과정을 직렬화라고 한다.
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void useRedis() {
// 데이터 저장
redisTemplate.opsForValue().set("user:1", "John");
// 데이터 가져오기
String value = redisTemplate.opsForValue().get("user:1");
System.out.println("user:1의 값은 " + value); // 출력: user:1의 값은 Inho
}
@Component
@RequiredArgsConstructor
public class TokenRepository {
private final RedisTemplate<String, String> redisTemplate;
// 블랙리스트에 토큰 추가 (만료 시간 설정)
public void addToBlacklist(String token, long expiration) {
redisTemplate.opsForValue().set(token, "blacklisted", expiration, TimeUnit.MILLISECONDS);
}
// 블랙리스트 여부 확인
public boolean isBlacklisted(String token) {
return Boolean.TRUE.equals(redisTemplate.hasKey(token));
}
// Refresh Token 저장 (유저 ID 기준)
public void storeRefreshToken(String userId, String refreshToken) {
redisTemplate.opsForValue().set(userId, refreshToken, 7, TimeUnit.DAYS);
}
// Refresh Token 조회 (유저 ID 기준)
public String getRefreshToken(String userId) {
return redisTemplate.opsForValue().get(userId);
}
}
// 블랙리스트에 토큰 추가 (만료 시간 설정)
public void addToBlacklist(String token, long expiration) {
redisTemplate.opsForValue().set(token, "blacklisted", expiration, TimeUnit.MILLISECONDS);
}
opsForValue() 메소드
1. 값 저장
redisTemplate.opsForValue().set("key", "value");Redis에 "key"라는 이름으로 "value"를 저장
기본적으로 만료 시간이 설정되지 않으며, 값은 영구적으로 저장
2. 값 조회String value = redisTemplate.opsForValue().get("key");Redis에서 "key"에 해당하는 값을 가져옴
값이 없으면 null을 반환
3. 값 삭제redisTemplate.delete("key");특정 키에 저장된 데이터를 삭제
4. 값 저장 (만료 시간 설정)redisTemplate.opsForValue().set("key", "value", 60, TimeUnit.SECONDS);"key"에 "value"를 저장하고, 만료 시간을 60초로 설정
만료 시간이 지나면 Redis는 해당 데이터를 자동으로 삭제
5. 키 존재 여부 확인boolean exists = redisTemplate.hasKey("key");특정 키가 Redis에 존재하는지 확인
존재하면 true, 없으면 false를 반환
// 블랙리스트 여부 확인
public boolean isBlacklisted(String token){
return Boolean.TRUE.equals(redisTemplate.hasKey(token));
}
// 리프레쉬 토큰 저장
public void storeRefreshToken(String userId, String refreshToken){
redisTemplate.opsForValue().set(userId, refreshToken, 7, TimeUnit.DAYS);
}
// 리프레쉬 토큰 조회
public String getRefreshToken(String userId){
return redisTemplate.opsForValue().get(userId);
}
JWT 검증 필터 구현은 다음 게시글로 !