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