Add remote authentication for users
- add RemoteReactiveAuthenticationManager - add RemoteAuthentication - add RemoteUserDetail - add AccountRegistryService - add WebClientCalls.kt with accountRegistryAuthenticate - add Helper.kt with logCall - add Mapping.kt toAuthenticationRequest
This commit is contained in:
27
src/main/kotlin/ltd/hlaeja/security/RemoteAuthentication.kt
Normal file
27
src/main/kotlin/ltd/hlaeja/security/RemoteAuthentication.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package ltd.hlaeja.security
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication
|
||||||
|
import org.springframework.security.core.GrantedAuthority
|
||||||
|
|
||||||
|
data class RemoteAuthentication(
|
||||||
|
private val remoteUserDetail: RemoteUserDetail,
|
||||||
|
private var authorities: MutableCollection<out GrantedAuthority>,
|
||||||
|
private var authenticated: Boolean = false,
|
||||||
|
) : Authentication {
|
||||||
|
|
||||||
|
override fun getName(): String = "Hlaeja Account Registry"
|
||||||
|
|
||||||
|
override fun getAuthorities(): MutableCollection<out GrantedAuthority> = authorities
|
||||||
|
|
||||||
|
override fun getCredentials(): Any? = null
|
||||||
|
|
||||||
|
override fun getDetails(): Any? = null
|
||||||
|
|
||||||
|
override fun getPrincipal(): Any = remoteUserDetail
|
||||||
|
|
||||||
|
override fun isAuthenticated(): Boolean = authenticated
|
||||||
|
|
||||||
|
override fun setAuthenticated(isAuthenticated: Boolean) {
|
||||||
|
authenticated = isAuthenticated
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package ltd.hlaeja.security
|
||||||
|
|
||||||
|
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.library.accountRegistry.Authentication.Response
|
||||||
|
import ltd.hlaeja.service.AccountRegistryService
|
||||||
|
import ltd.hlaeja.util.toAuthenticationRequest
|
||||||
|
import org.springframework.security.authentication.AuthenticationServiceException
|
||||||
|
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||||
|
import org.springframework.security.core.Authentication
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class RemoteReactiveAuthenticationManager(
|
||||||
|
private val accountRegistryService: AccountRegistryService,
|
||||||
|
private val publicJwtService: PublicJwtService,
|
||||||
|
) : ReactiveAuthenticationManager {
|
||||||
|
|
||||||
|
override fun authenticate(
|
||||||
|
authentication: Authentication,
|
||||||
|
): Mono<Authentication> = accountRegistryService.authenticate(authentication.toAuthenticationRequest())
|
||||||
|
.map(::processToken)
|
||||||
|
|
||||||
|
private fun processToken(
|
||||||
|
response: Response,
|
||||||
|
): Authentication = try {
|
||||||
|
publicJwtService.verify(response.token) { claims -> makeRemoteAuthentication(claims) }
|
||||||
|
} catch (e: JwtException) {
|
||||||
|
"An error occurred while processing token: ${e.message}".let {
|
||||||
|
log.error(e) { it }
|
||||||
|
throw 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(it) }
|
||||||
|
.toMutableList()
|
||||||
|
|
||||||
|
private fun makeRemoteUserDetail(
|
||||||
|
claims: Jws<Claims>,
|
||||||
|
): RemoteUserDetail = RemoteUserDetail(
|
||||||
|
UUID.fromString(claims.payload["id"] as String),
|
||||||
|
claims.payload["username"] as String,
|
||||||
|
)
|
||||||
|
}
|
||||||
8
src/main/kotlin/ltd/hlaeja/security/RemoteUserDetail.kt
Normal file
8
src/main/kotlin/ltd/hlaeja/security/RemoteUserDetail.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package ltd.hlaeja.security
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
data class RemoteUserDetail(
|
||||||
|
val id: UUID,
|
||||||
|
val username: String,
|
||||||
|
)
|
||||||
46
src/main/kotlin/ltd/hlaeja/service/AccountRegistryService.kt
Normal file
46
src/main/kotlin/ltd/hlaeja/service/AccountRegistryService.kt
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package ltd.hlaeja.service
|
||||||
|
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import ltd.hlaeja.library.accountRegistry.Authentication
|
||||||
|
import ltd.hlaeja.property.AccountRegistryProperty
|
||||||
|
import ltd.hlaeja.util.accountRegistryAuthenticate
|
||||||
|
import org.springframework.security.authentication.AuthenticationServiceException
|
||||||
|
import org.springframework.security.core.AuthenticationException
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient
|
||||||
|
import org.springframework.web.reactive.function.client.WebClientRequestException
|
||||||
|
import org.springframework.web.reactive.function.client.WebClientResponseException
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AccountRegistryService(
|
||||||
|
private val webClient: WebClient,
|
||||||
|
private val property: AccountRegistryProperty,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun authenticate(
|
||||||
|
request: Authentication.Request,
|
||||||
|
): Mono<Authentication.Response> = webClient.accountRegistryAuthenticate(request, property)
|
||||||
|
.onErrorResume { error ->
|
||||||
|
when (error) {
|
||||||
|
is AuthenticationException -> Mono.error(error)
|
||||||
|
|
||||||
|
is WebClientResponseException -> "WebClient response exception: ${error.message}".let {
|
||||||
|
log.error(error) { it }
|
||||||
|
Mono.error(AuthenticationServiceException(it, error))
|
||||||
|
}
|
||||||
|
|
||||||
|
is WebClientRequestException -> "An error occurred while making a request: ${error.message}".let {
|
||||||
|
log.error(error) { it }
|
||||||
|
Mono.error(AuthenticationServiceException(it, error))
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> "An unexpected error occurred: ${error.message}".let {
|
||||||
|
log.error(error) { it }
|
||||||
|
Mono.error(AuthenticationServiceException(it, error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/main/kotlin/ltd/hlaeja/util/Helper.kt
Normal file
7
src/main/kotlin/ltd/hlaeja/util/Helper.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package ltd.hlaeja.util
|
||||||
|
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
fun logCall(url: String) = log.debug { "calling: $url" }
|
||||||
10
src/main/kotlin/ltd/hlaeja/util/Mapping.kt
Normal file
10
src/main/kotlin/ltd/hlaeja/util/Mapping.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package ltd.hlaeja.util
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication as SpringAuthentication
|
||||||
|
|
||||||
|
import ltd.hlaeja.library.accountRegistry.Authentication
|
||||||
|
|
||||||
|
fun SpringAuthentication.toAuthenticationRequest(): Authentication.Request = Authentication.Request(
|
||||||
|
principal as String,
|
||||||
|
credentials as String,
|
||||||
|
)
|
||||||
24
src/main/kotlin/ltd/hlaeja/util/WebClientCalls.kt
Normal file
24
src/main/kotlin/ltd/hlaeja/util/WebClientCalls.kt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package ltd.hlaeja.util
|
||||||
|
|
||||||
|
import ltd.hlaeja.library.accountRegistry.Authentication
|
||||||
|
import ltd.hlaeja.property.AccountRegistryProperty
|
||||||
|
import org.springframework.http.HttpStatus.LOCKED
|
||||||
|
import org.springframework.http.HttpStatus.NOT_FOUND
|
||||||
|
import org.springframework.http.HttpStatus.UNAUTHORIZED
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException
|
||||||
|
import org.springframework.security.authentication.LockedException
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
fun WebClient.accountRegistryAuthenticate(
|
||||||
|
request: Authentication.Request,
|
||||||
|
property: AccountRegistryProperty,
|
||||||
|
): Mono<Authentication.Response> = post()
|
||||||
|
.uri("${property.url}/authenticate".also(::logCall))
|
||||||
|
.bodyValue(request)
|
||||||
|
.retrieve()
|
||||||
|
.onStatus(LOCKED::equals) { throw LockedException("Account is locked") }
|
||||||
|
.onStatus(UNAUTHORIZED::equals) { throw BadCredentialsException("Invalid credentials") }
|
||||||
|
.onStatus(NOT_FOUND::equals) { throw UsernameNotFoundException("User not found") }
|
||||||
|
.bodyToMono(Authentication.Response::class.java)
|
||||||
Reference in New Issue
Block a user