diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 1cddc4b..6484027 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -52,4 +52,7 @@ object Dependencies { // cloud const val SPRING_CLOUD = "org.springframework.cloud:spring-cloud-dependencies:${DependencyVersions.SPRING_CLOUD_VERSION}" + + // redis + const val REACTIVE_REDIS = "org.springframework.boot:spring-boot-starter-data-redis-reactive" } diff --git a/user-domain/src/main/kotlin/com/xquare/v1userservice/user/api/impl/UserSignInApiImpl.kt b/user-domain/src/main/kotlin/com/xquare/v1userservice/user/api/impl/UserSignInApiImpl.kt index 1777ac4..4587b26 100644 --- a/user-domain/src/main/kotlin/com/xquare/v1userservice/user/api/impl/UserSignInApiImpl.kt +++ b/user-domain/src/main/kotlin/com/xquare/v1userservice/user/api/impl/UserSignInApiImpl.kt @@ -8,6 +8,8 @@ import com.xquare.v1userservice.user.api.dtos.SignInDomainRequest import com.xquare.v1userservice.user.api.dtos.SignInResponse import com.xquare.v1userservice.user.exceptions.PasswordNotMatchesException import com.xquare.v1userservice.user.exceptions.UserNotFoundException +import com.xquare.v1userservice.user.refreshtoken.RefreshToken +import com.xquare.v1userservice.user.refreshtoken.spi.RefreshTokenSpi import com.xquare.v1userservice.user.spi.AuthorityListSpi import com.xquare.v1userservice.user.spi.JwtTokenGeneratorSpi import com.xquare.v1userservice.user.spi.PasswordMatcherSpi @@ -19,7 +21,8 @@ class UserSignInApiImpl( private val userRepositorySpi: UserRepositorySpi, private val passwordMatcherSpi: PasswordMatcherSpi, private val jwtTokenGeneratorSpi: JwtTokenGeneratorSpi, - private val authorityListSpi: AuthorityListSpi + private val authorityListSpi: AuthorityListSpi, + private val refreshTokenSpi: RefreshTokenSpi ) : UserSignInApi { override suspend fun userSignIn(signInDomainRequest: SignInDomainRequest): SignInResponse { val user = userRepositorySpi.findByAccountIdAndStateWithCreated(signInDomainRequest.accountId) @@ -37,13 +40,19 @@ class UserSignInApiImpl( } val accessToken = jwtTokenGeneratorSpi.generateJwtToken(signInDomainRequest.accountId, TokenType.ACCESS_TOKEN, params) + val expireAt = LocalDateTime.now().plusHours(jwtTokenGeneratorSpi.getAccessTokenExpirationAsHour().toLong()) + val refreshToken = jwtTokenGeneratorSpi.generateJwtToken(signInDomainRequest.accountId, TokenType.REFRESH_TOKEN, params) - val expirationAt = LocalDateTime.now().plusHours(jwtTokenGeneratorSpi.getAccessTokenExpirationAsHour().toLong()) + val refreshTokenDomain = RefreshToken( + tokenValue = refreshToken, + userId = user.id + ) + refreshTokenSpi.saveRefreshToken(refreshTokenDomain) return SignInResponse( accessToken = accessToken, refreshToken = refreshToken, - expireAt = expirationAt + expireAt = expireAt ) } diff --git a/user-domain/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/RefreshToken.kt b/user-domain/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/RefreshToken.kt new file mode 100644 index 0000000..e459921 --- /dev/null +++ b/user-domain/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/RefreshToken.kt @@ -0,0 +1,8 @@ +package com.xquare.v1userservice.user.refreshtoken + +import java.util.UUID + +data class RefreshToken( + val tokenValue: String, + val userId: UUID +) diff --git a/user-domain/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/spi/RefreshTokenSpi.kt b/user-domain/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/spi/RefreshTokenSpi.kt new file mode 100644 index 0000000..f118f9b --- /dev/null +++ b/user-domain/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/spi/RefreshTokenSpi.kt @@ -0,0 +1,7 @@ +package com.xquare.v1userservice.user.refreshtoken.spi + +import com.xquare.v1userservice.user.refreshtoken.RefreshToken + +interface RefreshTokenSpi { + suspend fun saveRefreshToken(refreshToken: RefreshToken): RefreshToken +} diff --git a/user-infrastructure/build.gradle.kts b/user-infrastructure/build.gradle.kts index 0780e09..d115456 100644 --- a/user-infrastructure/build.gradle.kts +++ b/user-infrastructure/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { testImplementation(Dependencies.SPRING_MOCKK) kapt(Dependencies.MAPSTRUCT_APT) kapt(Dependencies.CONFIGURATION_PROCESSOR) + implementation(Dependencies.REACTIVE_REDIS) implementation(project(":user-domain")) } diff --git a/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/configuration/redis/RedisConfiguration.kt b/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/configuration/redis/RedisConfiguration.kt new file mode 100644 index 0000000..e991c04 --- /dev/null +++ b/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/configuration/redis/RedisConfiguration.kt @@ -0,0 +1,28 @@ +package com.xquare.v1userservice.configuration.redis + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Primary +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory +import org.springframework.data.redis.core.ReactiveRedisOperations +import org.springframework.data.redis.core.ReactiveRedisTemplate +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer +import org.springframework.data.redis.serializer.RedisSerializationContext +import org.springframework.data.redis.serializer.StringRedisSerializer + +@Configuration +class RedisConfiguration { + @Primary + @Bean + fun redisOperations(factory: ReactiveRedisConnectionFactory): ReactiveRedisOperations { + val jsonSerializer = GenericJackson2JsonRedisSerializer() + val stringSerializer = StringRedisSerializer() + + val serializationContext = RedisSerializationContext.newSerializationContext() + .key(stringSerializer) + .value(jsonSerializer) + .build() + + return ReactiveRedisTemplate(factory, serializationContext) + } +} diff --git a/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/RefreshTokenEntity.kt b/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/RefreshTokenEntity.kt new file mode 100644 index 0000000..c98d29f --- /dev/null +++ b/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/RefreshTokenEntity.kt @@ -0,0 +1,8 @@ +package com.xquare.v1userservice.user.refreshtoken + +import java.util.UUID + +data class RefreshTokenEntity( + val tokenValue: String, + val userId: UUID +) diff --git a/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/exceptions/RefreshTokenSaveFailedException.kt b/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/exceptions/RefreshTokenSaveFailedException.kt new file mode 100644 index 0000000..38bc507 --- /dev/null +++ b/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/exceptions/RefreshTokenSaveFailedException.kt @@ -0,0 +1,7 @@ +package com.xquare.v1userservice.user.refreshtoken.exceptions + +import com.xquare.v1userservice.exceptions.BaseException + +class RefreshTokenSaveFailedException( + errorMessage: String +) : BaseException(errorMessage, 500) diff --git a/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/mapper/RefreshTokenDomainMapper.kt b/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/mapper/RefreshTokenDomainMapper.kt new file mode 100644 index 0000000..347a443 --- /dev/null +++ b/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/mapper/RefreshTokenDomainMapper.kt @@ -0,0 +1,11 @@ +package com.xquare.v1userservice.user.refreshtoken.mapper + +import com.xquare.v1userservice.user.refreshtoken.RefreshToken +import com.xquare.v1userservice.user.refreshtoken.RefreshTokenEntity +import org.mapstruct.Mapper + +@Mapper +interface RefreshTokenDomainMapper { + fun refreshTokenDomainToEntity(refreshToken: RefreshToken): RefreshTokenEntity + fun refreshTokenEntityToDomain(refreshTokenEntity: RefreshTokenEntity): RefreshToken +} diff --git a/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/spi/RefreshTokenSpiImpl.kt b/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/spi/RefreshTokenSpiImpl.kt new file mode 100644 index 0000000..89e4643 --- /dev/null +++ b/user-infrastructure/src/main/kotlin/com/xquare/v1userservice/user/refreshtoken/spi/RefreshTokenSpiImpl.kt @@ -0,0 +1,27 @@ +package com.xquare.v1userservice.user.refreshtoken.spi + +import com.xquare.v1userservice.user.refreshtoken.RefreshToken +import com.xquare.v1userservice.user.refreshtoken.exceptions.RefreshTokenSaveFailedException +import com.xquare.v1userservice.user.refreshtoken.mapper.RefreshTokenDomainMapper +import java.time.Duration +import kotlinx.coroutines.reactor.awaitSingle +import org.springframework.data.redis.core.ReactiveRedisOperations +import org.springframework.stereotype.Repository + +@Repository +class RefreshTokenSpiImpl( + private val reactiveRedisOperations: ReactiveRedisOperations, + private val refreshTokenDomainMapper: RefreshTokenDomainMapper +) : RefreshTokenSpi { + override suspend fun saveRefreshToken(refreshToken: RefreshToken): RefreshToken { + val refreshTokenEntity = refreshTokenDomainMapper.refreshTokenDomainToEntity(refreshToken) + val isSaveSuccess = reactiveRedisOperations.opsForValue() + .set(refreshToken.tokenValue, refreshTokenEntity, Duration.ofDays(14)).awaitSingle() + + if (!isSaveSuccess) { + throw RefreshTokenSaveFailedException("Refresh token save failed") + } + + return refreshToken + } +}