실무 프로젝트로 배우는 Kotlin & Spring/회원 인증 서비스 개발하기

내정보 조회

webmaster 2022. 12. 16. 14:35
728x90

Controller

@GetMapping("/me") //pathVariable 로 Id를 받게 되면, 헤커가 다른 유저 정보도 알수 있게된다(보안에 취약)
suspend fun get(
    @AuthToken token: String,
): MeResponse {
    return MeResponse(userService.getByToken(token))
}

MeResponse

data class MeResponse(
    val id: Long,
    val profileUrl: String?,
    val username: String,
    val email: String,
    val createdAt: LocalDateTime?,
    val updatedAt: LocalDateTime?,
) {
    companion object {
        operator fun invoke(user: User) = with(user) {
            MeResponse(
                id = id!!,
                profileUrl = if (profileUrl.isNullOrEmpty()) null else "http://localhost:9090/images/$profileUrl",
                username = username,
                email = email,
                createdAt = createdAt,
                updatedAt = updatedAt,
            )
        }
    }
}
  • PathVariable을 통해 userId 값을 받을 수도 있지만, 보안에 취약해지며, 헤커가 악의적인 방법으로 다른 사람을 user정보를 해킹할 수 있다.
  • MeResponse에서는 동반 객체와 리플렉션을 이용하여, 생성자처럼 객체를 생성할 수 있다.

Service

suspend fun getByToken(token: String): User {
    //캐시 조회(없으면 복호화된 값 반환)
    val cacheUser = cacheManager.awaitGetOrPut(key = token, ttl = CACHE_TTL) {
        //캐시가 유효하지 않은 경우 동작
        val decodedJWT = JWTUtils.decode(token, jwtProperties.secret, jwtProperties.issuer)

        val userId = decodedJWT.claims["userId"]?.asLong() ?: throw InvalidJwtTokenException()
        get(userId)
    }
    return cacheUser
}

suspend fun get(userId: Long):User {
    return userRepository.findById(userId) ?: throw UserNotFoundException()
}

cacheManager.awaitGetOrPut()

suspend fun awaitGetOrPut(
    key: String,
    ttl: Duration? = Duration.ofMinutes(5),
    supplier: suspend () -> T
): T {
    val now = Instant.now()
    val cacheWrapper = localCache[key]
    val cached = if (cacheWrapper == null) {
        CacheWrapper(cached = supplier(), ttl = now.plusMillis(ttl!!.toMillis())).also {
            localCache[key] = it
        }
    } else if (now.isAfter(cacheWrapper.ttl)) {//캐시 ttl이 지난 경우
        localCache.remove(key)
        CacheWrapper(
            cached = supplier(),
            ttl = now.plusMillis(ttl!!.toMillis())
        ).also { localCache[key] }
    } else {
        cacheWrapper
    }
    checkNotNull(cached.cached)
    return cached.cached
}
  • awaitGetOrPut 함수의 3번째 인자를 람다식으로 받아 cache 존재할 때, 람다식을 실행시키지 않는다.
    • 캐시에 값이 있을경우는(else) decode를 할 필요 없이 캐시 값만 가지고 오면 된다.
  • cacheWrapper 가 null일 경우
    • cache에 새로 넣어주어야 한다.
  • cache되어 있던 데이터의 ttl 값보다, 현재 시간이 초과되었을 경우(캐시 시간 초과)
    • 기존 캐시되어 있던 데이터를 지우고, cache에 다시 넣어준다.
  • cache 존재 && ttl 값보다 초과되지 않았을 경우
    • 현재 cache에 있는 값을 그대로 사용한다.
  • checkNotNull 함수를 통해 cache값을 잘 가지고 왔는지 확인한 뒤, 이를 반환한다(null처리를 해주었기 때문에 해당 함수 이후는 컴파일러가 반드시 값이 있는지를 알 수 있다)
728x90