update user session on event
- update handleRemoteAccountEvent to update user session on change event in AccountListener - add RedisSessionService - add fromAccountResponse to RemoteAuthenticationUtil - extract make remote authentication from RemoteAuthenticationManager to RemoteAuthenticationUtil
This commit is contained in:
@@ -2,6 +2,7 @@ package ltd.hlaeja.listener
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import ltd.hlaeja.library.accountRegistry.event.AccountMessage
|
||||
import ltd.hlaeja.service.RedisSessionService
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||
import org.springframework.kafka.annotation.KafkaListener
|
||||
import org.springframework.stereotype.Component
|
||||
@@ -9,10 +10,15 @@ import org.springframework.stereotype.Component
|
||||
val log = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class AccountListener {
|
||||
class AccountListener(
|
||||
private val sessionService: RedisSessionService,
|
||||
) {
|
||||
|
||||
@KafkaListener(topics = ["account"])
|
||||
fun handleRemoteAccountEvent(record: ConsumerRecord<String, AccountMessage>) {
|
||||
log.trace { "Received event: ${record.key()} for user: ${record.value().userId}" }
|
||||
if (record.key() == "change" && record.value().change.any { it in setOf("enabled", "username", "roles") }) {
|
||||
sessionService.updateUser(record.value().userId).subscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
package ltd.hlaeja.security.manager
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.jsonwebtoken.Claims
|
||||
import io.jsonwebtoken.Jws
|
||||
import io.jsonwebtoken.JwtException
|
||||
import java.util.UUID
|
||||
import ltd.hlaeja.jwt.service.PublicJwtService
|
||||
import ltd.hlaeja.security.user.RemoteAuthentication
|
||||
import ltd.hlaeja.security.user.RemoteUserDetail
|
||||
import ltd.hlaeja.service.AccountRegistryService
|
||||
import ltd.hlaeja.util.RemoteAuthenticationUtil
|
||||
import ltd.hlaeja.util.toAuthenticationRequest
|
||||
import org.springframework.context.ApplicationEventPublisher
|
||||
import org.springframework.security.authentication.AuthenticationServiceException
|
||||
@@ -17,7 +13,6 @@ import org.springframework.security.authentication.event.AuthenticationFailureBa
|
||||
import org.springframework.security.authentication.event.AuthenticationSuccessEvent
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.stereotype.Component
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
@@ -44,7 +39,7 @@ class RemoteAuthenticationManager(
|
||||
private fun processToken(
|
||||
response: ltd.hlaeja.library.accountRegistry.Authentication.Response,
|
||||
): Authentication = try {
|
||||
publicJwtService.verify(response.token) { claims -> makeRemoteAuthentication(claims) }
|
||||
publicJwtService.verify(response.token) { claims -> RemoteAuthenticationUtil.fromJwtClaims(claims) }
|
||||
} catch (e: JwtException) {
|
||||
throw "An error occurred while processing token: ${e.message}"
|
||||
.let {
|
||||
@@ -52,26 +47,4 @@ class RemoteAuthenticationManager(
|
||||
AuthenticationServiceException(it, e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeRemoteAuthentication(
|
||||
claims: Jws<Claims>,
|
||||
) = RemoteAuthentication(
|
||||
makeRemoteUserDetail(claims),
|
||||
makeSimpleGrantedAuthorities(claims),
|
||||
true,
|
||||
)
|
||||
|
||||
private fun makeSimpleGrantedAuthorities(
|
||||
claims: Jws<Claims>,
|
||||
): MutableList<SimpleGrantedAuthority> = (claims.payload["role"] as String)
|
||||
.split(",")
|
||||
.map { SimpleGrantedAuthority("ROLE_$it") }
|
||||
.toMutableList()
|
||||
|
||||
private fun makeRemoteUserDetail(
|
||||
claims: Jws<Claims>,
|
||||
): RemoteUserDetail = RemoteUserDetail(
|
||||
UUID.fromString(claims.payload["id"] as String),
|
||||
claims.payload["username"] as String,
|
||||
)
|
||||
}
|
||||
|
||||
48
src/main/kotlin/ltd/hlaeja/service/RedisSessionService.kt
Normal file
48
src/main/kotlin/ltd/hlaeja/service/RedisSessionService.kt
Normal file
@@ -0,0 +1,48 @@
|
||||
package ltd.hlaeja.service
|
||||
|
||||
import java.util.UUID
|
||||
import ltd.hlaeja.security.user.RemoteAuthentication
|
||||
import ltd.hlaeja.util.RemoteAuthenticationUtil
|
||||
import org.springframework.security.core.context.SecurityContextImpl
|
||||
import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository
|
||||
import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository.RedisSession
|
||||
import org.springframework.stereotype.Service
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
@Service
|
||||
class RedisSessionService(
|
||||
private val redisSessionRepository: ReactiveRedisIndexedSessionRepository,
|
||||
private val accountRegistryService: AccountRegistryService,
|
||||
) {
|
||||
|
||||
fun updateUser(
|
||||
user: UUID,
|
||||
): Flux<RedisSession> = findByUser(user)
|
||||
.flatMapMany { map ->
|
||||
getUserAccount(user).flatMapMany { response ->
|
||||
Flux.fromIterable(map.values).map { session -> session to response }
|
||||
}
|
||||
}
|
||||
.flatMap { (session: RedisSession, response: RemoteAuthentication) ->
|
||||
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextImpl(response))
|
||||
save(session)
|
||||
}
|
||||
|
||||
private fun getUserAccount(
|
||||
user: UUID,
|
||||
) = accountRegistryService.getAccount(user)
|
||||
.map(RemoteAuthenticationUtil::fromAccountResponse)
|
||||
|
||||
private fun findByUser(
|
||||
user: UUID,
|
||||
): Mono<MutableMap<String, RedisSession>> = redisSessionRepository.findByPrincipalName(user.toString())
|
||||
.doOnNext { ltd.hlaeja.listener.log.trace { "Found ${it.size} session(s) for user $user" } }
|
||||
.filter { map -> map.isNotEmpty() }
|
||||
|
||||
private fun save(
|
||||
session: RedisSession,
|
||||
): Mono<RedisSession> = redisSessionRepository.save(session)
|
||||
.thenReturn(session)
|
||||
.doOnNext { ltd.hlaeja.listener.log.trace { "Save session: ${it.id}" } }
|
||||
}
|
||||
44
src/main/kotlin/ltd/hlaeja/util/RemoteAuthenticationUtil.kt
Normal file
44
src/main/kotlin/ltd/hlaeja/util/RemoteAuthenticationUtil.kt
Normal file
@@ -0,0 +1,44 @@
|
||||
package ltd.hlaeja.util
|
||||
|
||||
import io.jsonwebtoken.Claims
|
||||
import io.jsonwebtoken.Jws
|
||||
import java.util.UUID
|
||||
import ltd.hlaeja.library.accountRegistry.Account
|
||||
import ltd.hlaeja.security.user.RemoteAuthentication
|
||||
import ltd.hlaeja.security.user.RemoteUserDetail
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
|
||||
object RemoteAuthenticationUtil {
|
||||
|
||||
fun fromJwtClaims(
|
||||
claims: Jws<Claims>,
|
||||
): RemoteAuthentication = makeRemoteAuthentication(
|
||||
id = UUID.fromString(claims.payload["id"] as String),
|
||||
username = claims.payload["username"] as String,
|
||||
roles = (claims.payload["role"] as String).split(","),
|
||||
authenticated = true,
|
||||
)
|
||||
|
||||
fun fromAccountResponse(
|
||||
response: Account.Response,
|
||||
): RemoteAuthentication = makeRemoteAuthentication(
|
||||
id = response.id,
|
||||
username = response.username,
|
||||
roles = response.roles,
|
||||
authenticated = response.enabled,
|
||||
)
|
||||
|
||||
private fun makeRemoteAuthentication(
|
||||
id: UUID,
|
||||
username: String,
|
||||
roles: List<String>,
|
||||
authenticated: Boolean,
|
||||
) = RemoteAuthentication(
|
||||
remoteUserDetail = RemoteUserDetail(
|
||||
id = id,
|
||||
username = username,
|
||||
),
|
||||
authorities = roles.map { SimpleGrantedAuthority("ROLE_$it") }.toMutableList(),
|
||||
authenticated = authenticated,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user