Authentication
- add AuthenticationController - add AuthenticationService - add getUserByUsername to AccountService - add findByUsername to AccountRepository - add SecurityConfiguration - set up authentication
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
package ltd.hlaeja.configuration
|
||||
|
||||
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.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
|
||||
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
class SecurityConfiguration {
|
||||
|
||||
@Bean
|
||||
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
|
||||
|
||||
@Bean
|
||||
fun securityWebFilterChain(
|
||||
serverHttpSecurity: ServerHttpSecurity,
|
||||
): SecurityWebFilterChain = serverHttpSecurity
|
||||
.authorizeExchange(::authorizeExchange)
|
||||
.httpBasic(::httpBasic)
|
||||
.formLogin(::formLogin)
|
||||
.csrf(::csrf)
|
||||
.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.anyExchange().permitAll()
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package ltd.hlaeja.controller
|
||||
|
||||
import ltd.hlaeja.library.accountRegistry.Authentication
|
||||
import ltd.hlaeja.service.AuthenticationService
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
@RestController
|
||||
class AuthenticationController(
|
||||
private val authenticationService: AuthenticationService,
|
||||
) {
|
||||
|
||||
@PostMapping("/authenticate")
|
||||
fun authenticate(
|
||||
@RequestBody request: Authentication.Request,
|
||||
): Mono<Authentication.Response> = authenticationService.authenticate(request.username, request.password)
|
||||
.map { Authentication.Response(it) }
|
||||
}
|
||||
@@ -4,6 +4,9 @@ import java.util.UUID
|
||||
import ltd.hlaeja.entity.AccountEntity
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
@Repository
|
||||
interface AccountRepository : ReactiveCrudRepository<AccountEntity, UUID>
|
||||
interface AccountRepository : ReactiveCrudRepository<AccountEntity, UUID> {
|
||||
fun findByUsername(username: String): Mono<AccountEntity>
|
||||
}
|
||||
|
||||
@@ -21,4 +21,10 @@ class AccountService(
|
||||
): Mono<AccountEntity> = accountRepository.findById(uuid)
|
||||
.doOnNext { log.debug { "Get account ${it.id}" } }
|
||||
.switchIfEmpty(Mono.error(ResponseStatusException(HttpStatus.NOT_FOUND)))
|
||||
|
||||
fun getUserByUsername(
|
||||
username: String,
|
||||
): Mono<AccountEntity> = accountRepository.findByUsername(username)
|
||||
.doOnNext { log.debug { "Get account ${it.id} for username $username" } }
|
||||
.switchIfEmpty(Mono.error(ResponseStatusException(HttpStatus.NOT_FOUND)))
|
||||
}
|
||||
|
||||
37
src/main/kotlin/ltd/hlaeja/service/AuthenticationService.kt
Normal file
37
src/main/kotlin/ltd/hlaeja/service/AuthenticationService.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
package ltd.hlaeja.service
|
||||
|
||||
import ltd.hlaeja.jwt.service.PrivateJwtService
|
||||
import org.springframework.http.HttpStatus.LOCKED
|
||||
import org.springframework.http.HttpStatus.UNAUTHORIZED
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
@Service
|
||||
class AuthenticationService(
|
||||
private val accountService: AccountService,
|
||||
private val passwordEncoder: PasswordEncoder,
|
||||
private val privateJwtService: PrivateJwtService,
|
||||
) {
|
||||
fun authenticate(
|
||||
username: String,
|
||||
password: CharSequence,
|
||||
): Mono<String> = accountService.getUserByUsername(username)
|
||||
.flatMap {
|
||||
if (!passwordEncoder.matches(password, it.password)) {
|
||||
Mono.error(ResponseStatusException(UNAUTHORIZED, "Invalid password"))
|
||||
} else if (!it.enabled) {
|
||||
Mono.error(ResponseStatusException(LOCKED, "Account disabled"))
|
||||
} else {
|
||||
Mono.just(it)
|
||||
}
|
||||
}
|
||||
.map { accountEntity ->
|
||||
privateJwtService.sign(
|
||||
"id" to accountEntity.id!!,
|
||||
"username" to accountEntity.username,
|
||||
"role" to accountEntity.roles,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@ spring:
|
||||
name: "%APP_BUILD_OS_NAME%"
|
||||
version: "%APP_BUILD_OS_VERSION%"
|
||||
|
||||
jwt:
|
||||
private-key: cert/private_key.pem
|
||||
|
||||
---
|
||||
###############################
|
||||
### Development environment ###
|
||||
|
||||
8
src/test/resources/application.yml
Normal file
8
src/test/resources/application.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
jwt:
|
||||
private-key: cert/valid-private-key.pem
|
||||
|
||||
spring:
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://placeholder
|
||||
username: placeholder
|
||||
password: placeholder
|
||||
28
src/test/resources/cert/valid-private-key.pem
Normal file
28
src/test/resources/cert/valid-private-key.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj
|
||||
MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu
|
||||
NMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZ
|
||||
qgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulg
|
||||
p2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlR
|
||||
ZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwi
|
||||
VuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskV
|
||||
laAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8
|
||||
sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83H
|
||||
mQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwY
|
||||
dgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cw
|
||||
ta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQ
|
||||
DM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2T
|
||||
N0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t
|
||||
0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPv
|
||||
t8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDU
|
||||
AhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk
|
||||
48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISL
|
||||
DY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnK
|
||||
xt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEA
|
||||
mNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh
|
||||
2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfz
|
||||
et6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhr
|
||||
VBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicD
|
||||
TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc
|
||||
dn/RsYEONbwQSjIfMPkvxF+8HQ==
|
||||
-----END PRIVATE KEY-----
|
||||
Reference in New Issue
Block a user