opaque token을 사용하여 JWT 자체 만료시간 강제 무효화 하기
JWT를 사용하여 인가/인증 처리를 할 때, 로그아웃과 관련된 이슈가 한 가지 있습니다. JWT 특성상 자체 만료시간이 기술되어 있으므로 강제 무효화가 안된다는 문제 입니다. JWT에 직접 기술된 만료시간으로 무효화가 되기 때문이죠.
이런 AccessToken을 무효화하기 위해 Oauth2.0을 사용하여 아래의 그림처럼 opaque token을 고려해볼 수 있습니다.
Opaque token이란?
이름에서 알 수 있듯이 전달하는 정보 측면에서 불투명합니다. 토큰은 인증 서버에 저장된 정보를 가리키는 식별자일
뿐이며 서버 측에서 자체 검사를 통해 유효성을 검사합니다.
기존 JWT AccessToken의 자체 만료시간으로 인해 강제 무효화가 안되는 것을 Oauth2의 opaque token을 사용하여 로그아웃 시점에 폐기함으로써 위의 문제를 해결하는 그림입니다.
추가로, 클라이언트와 API 사이에 배치되는 API 게이트웨이를 이용하기 때문에. 이러한 방식으로 API와 마이크로서비스는 클라이언트가 불투명한 토큰만 검색하므로 클라이언트에 데이터를 노출하지 않고 JWT의 이점을 얻을 수 있습니다.
OAuth 2.0 리소스 서버는 JWT와 함께 확인(Introspection) 사용이 가능할까?
흔히 하는 질문 중 하나는 확인(introspection)이 JWT와 호환되냐는 것입니다. Spring Security의 Opaque token 지원은 토큰의 형식에 대해서는 신경쓰지 않도록 설계되어 있습니다.
각 요청을 인가 서버를 이용해 검사해야 하는데, JWT가 회수 돼었다고 가정해보겠습니다. Opaque token에 JWT 형식을 사용하더라도 검증 방법은 확인(introspection)입니다.
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.org/introspection
client-id: client
client-secret: secret
이 경우, 결과로 받는 Authentication은 BearerTokenAuthentication입니다. OAuth2AuthenticatedPrincipal에 해당하는 속성은 확인 엔드포인트에서 반환된 것이 될것입니다.
하지만 이 경우 확인 엔드포인트는 토큰이 활성 상태인지 여부만을 반환합니다. 따라서 이 경우에는 엔드포인트에 요청을 보낸 뒤 반환된 주체가 JWT 클레임을 속성으로 갖도록 갱신하는 커스텀 OpaqueTokenIntrospector를 만들 수 있습니다.
class JwtOpaqueTokenIntrospector : OpaqueTokenIntrospector {
private val delegate: OpaqueTokenIntrospector = NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret")
private val jwtDecoder: JwtDecoder = NimbusJwtDecoder(ParseOnlyJWTProcessor())
override fun introspect(token: String): OAuth2AuthenticatedPrincipal {
val principal = delegate.introspect(token)
return try {
val jwt: Jwt = jwtDecoder.decode(token)
DefaultOAuth2AuthenticatedPrincipal(jwt.claims, NO_AUTHORITIES)
} catch (ex: JwtException) {
throw OAuth2IntrospectionException(ex.message)
}
}
private class ParseOnlyJWTProcessor : DefaultJWTProcessor<SecurityContext>() {
override fun process(jwt: SignedJWT, context: SecurityContext): JWTClaimsSet {
return jwt.jwtClaimsSet
}
}
}
그러고 나서, 이 커스텀 확인자를 @Bean으로 노출하여 구성되도록 합니다.
@Bean
fun introspector(): OpaqueTokenIntrospector {
return JwtOpaqueTokenIntrospector()
}
위의 코드를 구현하기 위해 필요한 dependency입니다.
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
runtimeOnly("com.nimbusds:oauth2-oidc-sdk")
OpaqueTokenIntrospector는 어떤 방식으로 작동할까요?
OptroTokenAuthenticationProvider는 OptroTokenIntrospector를 활용하여 불투명 토큰을 인증하는 AuthenticationProvider 구현체입니다.
ProfaileTokenAuthenticationProvider가 Spring Security 내에서 어떻게 작동하는지 살펴보겠습니다. 그림은 보유자 토큰 읽기의 그림에서 AuthenticationManager의 작동 방식을 자세히 설명합니다.
1. Filter에서 베어러 토큰을 판독 전달 BearerTokenAuthenticationToken받는 AuthenticationManager의해 구현됩니다.
2. ProviderManager가 OfloseTokenAuthenticationProvider 유형의 AuthenticationProvider를 사용하도록 구성되어 있습니다.
3. ProfaileTokenAuthenticationProvider는 불투명 토큰을 검사하고 ProfaileTokenIntrospector를 사용하여 부여된 권한을 추가합니다.
인증이 성공하면 반환되는 인증은 BearerTokenAuthentication 유형이며 구성된OfloopeTokenIntrospector에 의해 반OAuth2AuthenticatedPrincipal 주체가 됩니다.
궁극적으로 반환된 BearerTokenAuthentication은 인증 필터에 의해 SecurityContextHolder에서 설정됩니다.
.. 작성중