Redis를 이용한 로그아웃
2025. 1. 14. 19:37ㆍTIL
블랙리스트를 이용하여 서버에서 로그아웃 기능을 수행하도록 설계할 것이다.
Redis DB 생성
의존성 추가
더보기
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-redis")
}
Redis Config 설정
더보기
@Configuration
class RedisConfig {
@Bean
fun redisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate<String, String> {
val redisTemplate = RedisTemplate<String, String>()
redisTemplate.connectionFactory = connectionFactory
redisTemplate.keySerializer = StringRedisSerializer()
redisTemplate.valueSerializer = StringRedisSerializer()
return redisTemplate
}
}
토큰을 블랙리스트로 등록하고 블랙리스트인지 확인하는 Repository 작성
더보기
package hjp.hjchat.infra.redis
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Repository
import java.util.concurrent.TimeUnit
@Repository
class TokenBlacklistRepository(
private val redisTemplate: RedisTemplate<String, String>
) {
fun addToBlacklist(token: String, expirationTime: Long) {
redisTemplate.opsForValue().set(token, "blacklisted", expirationTime, TimeUnit.MILLISECONDS)
}
fun isBlacklisted(token: String): Boolean {
return redisTemplate.hasKey(token)
}
}
기존 Controller에 logout 작성
더보기
@PostMapping("/logout")
fun logOut(
@CookieValue("refreshToken") refreshToken: String
): ResponseEntity<String>{
return ResponseEntity.status(HttpStatus.OK).body(oAuthService.logout(refreshToken))
}
기존 Service에 logout 작성
더보기
fun logout(refreshToken: String): String {
jwtTokenManager.validateRefreshToken(refreshToken)
val expirationTime = jwtTokenManager.getExpiration(refreshToken)
tokenBlacklistRepository.addToBlacklist(refreshToken, expirationTime)
return "로그아웃 성공"
}
jwtTokenManger에 로그아웃 기능 작성
( refreshToken이 블랙리스트에 등록되었는지의 로직과 AccessToken이 만료되었을때 RefreshToken을 이용하여 AccessToken을 재발급 하는 로직 추가 )
더보기
fun validateRefreshToken(refreshToken: String){
validateToken(refreshToken)
.onSuccess {
try{
if(tokenBlacklistRepository.isBlacklisted(refreshToken)){
throw IllegalStateException("토큰이 블랙리스트에 등록됨 " )
}
} catch(e: Exception){
throw IllegalStateException("Redis 연결 실패: ${e.message}")
}
if(it.payload[TOKEN_TYPE_KEY] == TokenType.ACCESS_TOKEN_TYPE){
throw IllegalStateException(" refreshToken이 아닌 AccessToken입니다")
}
}
.onFailure {
throw IllegalStateException("refreshToken 올바르지 않음 ${it.message}")
}
}
fun reissueAccessToken(refreshToken: String): String{
val tokenInfo = validateToken(refreshToken).getOrNull()
val memberId = tokenInfo!!.payload.subject.toLong()
val memberRole = tokenInfo.payload[MEMBER_ROLE_KEY] as String
return generateTokenResponse(memberId = memberId, memberRole = memberRole).accessToken
}
jwtAuthenticationFilter를 통해 재발급 로직 적용
더보기
.onFailure {
if (it is ExpiredJwtException) {
if(refreshToken != null) {
jwtTokenManager.validateRefreshToken(refreshToken)
val newAccessToken = jwtTokenManager.reissueAccessToken(refreshToken)
response.setHeader(HttpHeaders.AUTHORIZATION, "Bearer $newAccessToken")
val tokenInfo = jwtTokenManager.getInfoToken(newAccessToken)
val userPrincipal = UserPrincipal(memberId = tokenInfo!!.memberId, memberRole = setOf(tokenInfo.memberRole))
val authentication = JwtAuthenticationToken(
userPrincipal = userPrincipal, details = WebAuthenticationDetailsSource().buildDetails(request)
)
SecurityContextHolder.getContext().authentication = authentication
}
return
}
토큰 만료시간으로 인해서 validate가 실패한경우 refreshToken을 통해 새로운 AccessToken을 받는다.
로그아웃 흐름
1. Client에서 로그아웃 버튼을 클릭
2. 쿠키에 저장되어있는 RefreshToken을 Server로 전송
3. Server는 해당 RefreshToken을 Redis를 이용하여 블랙리스트로 등록
4. Client는 AccessToken/Refresh Token을 삭제하고 메인 페이지로 이동
시연 영상: https://www.youtube.com/watch?v=3KqZcLyYgxo
'TIL' 카테고리의 다른 글
테스트환경에서 SSL/HTTPS 설정하기 (0) | 2025.01.15 |
---|---|
Swagger failed to load api definition 오류 (0) | 2025.01.13 |
신규 회원가입시 𝐷𝑒𝑓𝑎𝑢𝑙𝑡-𝐼𝑚𝑎𝑔𝑒 설정 및 접근 가능한 채팅방만 조회 (0) | 2025.01.07 |
𝒫𝓇ℯ𝓈𝒾ℊ𝓃ℯ𝒹 𝒰ℛℒ과 프로필 사진 (0) | 2025.01.06 |
S3을 이용한 프로필 사진 업로드 구현 (1) | 2025.01.03 |