Authorization
- add SecurityConfiguration - add JwtAuthenticationManager - add JwtAuthenticationConverter - add JwtAuthenticationToken - add JwtUserDetails
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package ltd.hlaeja.configuration
|
||||
|
||||
import ltd.hlaeja.security.JwtAuthenticationConverter
|
||||
import ltd.hlaeja.security.JwtAuthenticationManager
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.SecurityWebFiltersOrder.AUTHENTICATION
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity.CsrfSpec
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity.FormLoginSpec
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity.HttpBasicSpec
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authentication.AuthenticationWebFilter
|
||||
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
class SecurityConfiguration {
|
||||
@Bean
|
||||
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
|
||||
|
||||
@Bean
|
||||
fun securityWebFilterChain(
|
||||
serverHttpSecurity: ServerHttpSecurity,
|
||||
jwtAuthenticationManager: JwtAuthenticationManager,
|
||||
jwtAuthenticationConverter: JwtAuthenticationConverter,
|
||||
): SecurityWebFilterChain = serverHttpSecurity
|
||||
.authorizeExchange(::authorizeExchange)
|
||||
.httpBasic(::httpBasic)
|
||||
.formLogin(::formLogin)
|
||||
.csrf(::csrf)
|
||||
.addFilterAt(
|
||||
AuthenticationWebFilter(jwtAuthenticationManager).apply {
|
||||
setServerAuthenticationConverter(jwtAuthenticationConverter)
|
||||
},
|
||||
AUTHENTICATION,
|
||||
)
|
||||
.build()
|
||||
|
||||
private fun csrf(
|
||||
csrf: CsrfSpec,
|
||||
) = csrf.disable()
|
||||
|
||||
private fun formLogin(
|
||||
formLogin: FormLoginSpec,
|
||||
) = formLogin.disable()
|
||||
|
||||
private fun httpBasic(
|
||||
httpBasic: HttpBasicSpec,
|
||||
) = httpBasic.disable()
|
||||
|
||||
private fun authorizeExchange(
|
||||
authorizeExchange: AuthorizeExchangeSpec,
|
||||
) = authorizeExchange
|
||||
.pathMatchers("/login").permitAll()
|
||||
.anyExchange().hasRole("REGISTRY")
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package ltd.hlaeja.security
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.jsonwebtoken.JwtException
|
||||
import java.util.UUID
|
||||
import ltd.hlaeja.jwt.service.PublicJwtService
|
||||
import org.springframework.http.HttpStatus.UNAUTHORIZED
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
private val log = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class JwtAuthenticationConverter(
|
||||
private val publicJwtService: PublicJwtService,
|
||||
) : ServerAuthenticationConverter {
|
||||
|
||||
companion object {
|
||||
private const val BEARER = "Bearer "
|
||||
private const val AUTHORIZATION = "Authorization"
|
||||
}
|
||||
|
||||
override fun convert(
|
||||
exchange: ServerWebExchange,
|
||||
): Mono<Authentication> = Mono.justOrEmpty(exchange.request.headers.getFirst(AUTHORIZATION))
|
||||
.filter { it.startsWith(BEARER) }
|
||||
.map { it.removePrefix(BEARER) }
|
||||
.flatMap { token ->
|
||||
try {
|
||||
Mono.just(jwtAuthenticationToken(token))
|
||||
} catch (e: JwtException) {
|
||||
log.error(e) { "${e.message}" }
|
||||
Mono.error(ResponseStatusException(UNAUTHORIZED))
|
||||
}
|
||||
}
|
||||
|
||||
private fun jwtAuthenticationToken(token: String) = publicJwtService.verify(token) { claims ->
|
||||
JwtAuthenticationToken(
|
||||
JwtUserDetails(
|
||||
UUID.fromString(claims.payload["id"] as String),
|
||||
claims.payload["username"] as String,
|
||||
),
|
||||
token,
|
||||
(claims.payload["role"] as String).split(",")
|
||||
.map { SimpleGrantedAuthority(it) }
|
||||
.toMutableList(),
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package ltd.hlaeja.security
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
import org.springframework.stereotype.Component
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
@Component
|
||||
class JwtAuthenticationManager : ReactiveAuthenticationManager {
|
||||
|
||||
override fun authenticate(
|
||||
authentication: Authentication,
|
||||
): Mono<Authentication> = if (authentication is JwtAuthenticationToken) {
|
||||
handleJwtToken(authentication)
|
||||
} else {
|
||||
Mono.error(object : AuthenticationException("Unsupported authentication type") {})
|
||||
}
|
||||
|
||||
private fun handleJwtToken(
|
||||
token: JwtAuthenticationToken,
|
||||
): Mono<Authentication> = if (token.isAuthenticated) {
|
||||
Mono.just(token)
|
||||
} else {
|
||||
Mono.error(object : AuthenticationException("Invalid or expired JWT token") {})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package ltd.hlaeja.security
|
||||
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
|
||||
data class JwtAuthenticationToken(
|
||||
private val jwtUserDetails: JwtUserDetails,
|
||||
private val token: String,
|
||||
private var authorities: MutableCollection<out GrantedAuthority>,
|
||||
private var authenticated: Boolean = false,
|
||||
) : Authentication {
|
||||
|
||||
override fun getName(): String = "Bearer Token"
|
||||
|
||||
override fun getAuthorities(): MutableCollection<out GrantedAuthority> = authorities
|
||||
|
||||
override fun getCredentials(): Any = token
|
||||
|
||||
override fun getDetails(): Any? = null
|
||||
|
||||
override fun getPrincipal(): Any = jwtUserDetails
|
||||
|
||||
override fun isAuthenticated(): Boolean = authenticated
|
||||
|
||||
override fun setAuthenticated(isAuthenticated: Boolean) {
|
||||
authenticated = isAuthenticated
|
||||
}
|
||||
}
|
||||
8
src/main/kotlin/ltd/hlaeja/security/JwtUserDetails.kt
Normal file
8
src/main/kotlin/ltd/hlaeja/security/JwtUserDetails.kt
Normal file
@@ -0,0 +1,8 @@
|
||||
package ltd.hlaeja.security
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
data class JwtUserDetails(
|
||||
val id: UUID,
|
||||
val username: String,
|
||||
)
|
||||
Reference in New Issue
Block a user