Skip to content

Commit

Permalink
feat: support ExpirableTokenProvider (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ahoo-Wang authored Apr 3, 2024
1 parent 68b9607 commit 6e86fb1
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 50 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ kotlin.code.style=official
ksp.incremental=true
ksp.incremental.log=true
group=me.ahoo.coapi
version=1.2.2
version=1.2.5
description=Streamlining HTTP client definition in Spring 6, CoApi provides zero boilerplate code auto-configuration for more convenient and efficient interface calls.
website=https://github.com/Ahoo-Wang/CoApi
issues=https://github.com/Ahoo-Wang/CoApi/issues
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,16 @@ package me.ahoo.coapi.spring.client.reactive.auth

import org.springframework.http.HttpHeaders

class BearerTokenFilter(tokenProvider: BearerTokenProvider) :
HeaderSetFilter(headerName = HttpHeaders.AUTHORIZATION, headerValueProvider = tokenProvider)
class BearerTokenFilter(tokenProvider: ExpirableTokenProvider) :
HeaderSetFilter(
headerName = HttpHeaders.AUTHORIZATION,
headerValueProvider = tokenProvider,
headerValueMapper = BearerHeaderValueMapper
)

object BearerHeaderValueMapper : HeaderValueMapper {
const val BEARER_TOKEN_PREFIX = "Bearer "
override fun map(headerValue: String): String {
return "$BEARER_TOKEN_PREFIX$headerValue"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,45 +13,23 @@

package me.ahoo.coapi.spring.client.reactive.auth

import com.auth0.jwt.JWT
import me.ahoo.coapi.spring.client.reactive.auth.CachedBearerTokenProvider.JwtToken.Companion.toJwtToken
import org.slf4j.LoggerFactory
import reactor.core.publisher.Mono
import java.util.*

class CachedBearerTokenProvider(tokenProvider: BearerTokenProvider) : BearerTokenProvider {
class CachedExpirableTokenProvider(tokenProvider: ExpirableTokenProvider) : ExpirableTokenProvider {
companion object {
private val log = LoggerFactory.getLogger(CachedBearerTokenProvider::class.java)
private val log = LoggerFactory.getLogger(CachedExpirableTokenProvider::class.java)
}

private val tokenCache: Mono<String> = tokenProvider.getBearerToken()
.map {
it.toJwtToken()
}
private val tokenCache: Mono<ExpirableToken> = tokenProvider.getToken()
.cacheInvalidateIf {
if (log.isDebugEnabled) {
log.debug("CacheInvalidateIf - isExpired:${it.isExpired}")
}
it.isExpired
}.map {
it.accessToken
}

override fun getBearerToken(): Mono<String> {
override fun getToken(): Mono<ExpirableToken> {
return tokenCache
}

data class JwtToken(val accessToken: String, val expiresAt: Date) {
val isExpired: Boolean
get() = Date().after(expiresAt)

companion object {
private val jwtParser = JWT()
fun String.toJwtToken(): JwtToken {
val decodedJWT = jwtParser.decodeJwt(this)
val expiresAt = checkNotNull(decodedJWT.expiresAt)
return JwtToken(this, expiresAt)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,27 @@

package me.ahoo.coapi.spring.client.reactive.auth

import com.auth0.jwt.JWT
import reactor.core.publisher.Mono

interface BearerTokenProvider : HeaderValueProvider {
data class ExpirableToken(val token: String, val expiresAt: Long) {
val isExpired: Boolean
get() = System.currentTimeMillis() > expiresAt

companion object {
const val HEADER_VALUE_PREFIX = "Bearer "
private val jwtParser = JWT()
fun String.jwtToExpirableToken(): ExpirableToken {
val decodedJWT = jwtParser.decodeJwt(this)
val expiresAt = checkNotNull(decodedJWT.expiresAt)
return ExpirableToken(this, expiresAt.time)
}
}
}

fun getBearerToken(): Mono<String>
interface ExpirableTokenProvider : HeaderValueProvider {
fun getToken(): Mono<ExpirableToken>

override fun getHeaderValue(): Mono<String> {
return getBearerToken().map { "$HEADER_VALUE_PREFIX$it" }
return getToken().map { it.token }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import reactor.core.publisher.Mono

open class HeaderSetFilter(
private val headerName: String,
private val headerValueProvider: HeaderValueProvider
private val headerValueProvider: HeaderValueProvider,
private val headerValueMapper: HeaderValueMapper = HeaderValueMapper.IDENTITY
) : ExchangeFilterFunction {
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
return headerValueProvider.getHeaderValue()
.map { headerValue ->
ClientRequest.from(request)
.headers { headers ->
headers[headerName] = headerValue
headers[headerName] = headerValueMapper.map(headerValue)
}
.build()
}
Expand All @@ -39,3 +40,11 @@ open class HeaderSetFilter(
fun interface HeaderValueProvider {
fun getHeaderValue(): Mono<String>
}

fun interface HeaderValueMapper {
companion object {
val IDENTITY = HeaderValueMapper { it }
}

fun map(headerValue: String): String
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package me.ahoo.coapi.spring.client.reactive.auth

import me.ahoo.coapi.spring.client.reactive.auth.ExpirableToken.Companion.jwtToExpirableToken
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.springframework.http.HttpMethod
import org.springframework.web.reactive.function.client.ClientRequest
import org.springframework.web.reactive.function.client.ExchangeFunction
import reactor.core.publisher.Mono
import reactor.kotlin.test.test
import java.net.URI
import java.util.Date
import java.util.*

class BearerTokenFilterTest {

Expand All @@ -24,9 +24,9 @@ class BearerTokenFilterTest {
assertThat(request.headers().getFirst("Authorization"), equalTo("Bearer $jwtToken"))
Mono.empty()
}
val tokenProvider = object : BearerTokenProvider {
override fun getBearerToken(): Mono<String> {
return Mono.just(jwtToken)
val tokenProvider = object : ExpirableTokenProvider {
override fun getToken(): Mono<ExpirableToken> {
return Mono.just(jwtToken.jwtToExpirableToken())
}
}
val bearerTokenFilter = BearerTokenFilter(tokenProvider)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,51 @@
package me.ahoo.coapi.spring.client.reactive.auth

import me.ahoo.coapi.spring.client.reactive.auth.ExpirableToken.Companion.jwtToExpirableToken
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import reactor.core.publisher.Mono
import reactor.kotlin.test.test
import java.util.*

class CachedBearerTokenProviderTest {
class CachedExpirableTokenProviderTest {

@Test
fun getBearerToken() {
val cachedBearerTokenProvider = CachedBearerTokenProvider(MockBearerTokenProvider)
cachedBearerTokenProvider.getBearerToken()
val cachedExpirableTokenProvider = CachedExpirableTokenProvider(MockBearerTokenProvider)
cachedExpirableTokenProvider.getToken()
.test()
.consumeNextWith {
// 仅当缓存当前已填充时才会评估
assertThat(it, equalTo(MockBearerTokenProvider.expiredToken))
}.verifyComplete()

cachedBearerTokenProvider.getBearerToken()
cachedExpirableTokenProvider.getToken()
.test()
.consumeNextWith {
assertThat(it, equalTo(MockBearerTokenProvider.notExpiredToken))
}.verifyComplete()
cachedBearerTokenProvider.getBearerToken()
cachedExpirableTokenProvider.getToken()
.test()
.consumeNextWith {
assertThat(it, equalTo(MockBearerTokenProvider.notExpiredToken))
}.verifyComplete()
cachedBearerTokenProvider.getBearerToken()
cachedExpirableTokenProvider.getToken()
.test()
.consumeNextWith {
assertThat(it, equalTo(MockBearerTokenProvider.notExpiredToken))
}.verifyComplete()
}

object MockBearerTokenProvider : BearerTokenProvider {
object MockBearerTokenProvider : ExpirableTokenProvider {
@Volatile
private var isFistCall = true
val expiredToken = JwtFixture.generateToken(Date(System.currentTimeMillis() - 10000))
val notExpiredToken = JwtFixture.generateToken(Date(System.currentTimeMillis() + 10000))
override fun getBearerToken(): Mono<String> {
val expiredToken = JwtFixture
.generateToken(Date(System.currentTimeMillis() - 10000)).jwtToExpirableToken()
val notExpiredToken = JwtFixture
.generateToken(Date(System.currentTimeMillis() + 10000)).jwtToExpirableToken()

override fun getToken(): Mono<ExpirableToken> {
return Mono.create {
if (isFistCall) {
isFistCall = false
Expand Down

0 comments on commit 6e86fb1

Please sign in to comment.