Basic accounts

- add account link in welcome.html
- add AuthorizeExchangeSpec adminPaths in SecurityConfiguration
- add AccountController
- add users.html
- add getAccounts in AccountRegistryService
- add WebClient accountRegistryAccounts in webClient
This commit is contained in:
2025-01-23 14:02:59 +01:00
parent 3212226853
commit c40f1a0036
6 changed files with 90 additions and 0 deletions

View File

@@ -31,8 +31,13 @@ class SecurityConfiguration {
private fun authorizeExchange(authorizeExchange: AuthorizeExchangeSpec) = authorizeExchange private fun authorizeExchange(authorizeExchange: AuthorizeExchangeSpec) = authorizeExchange
.publicPaths().permitAll() .publicPaths().permitAll()
.adminPaths().hasRole("ADMIN")
.anyExchange().authenticated() .anyExchange().authenticated()
private fun AuthorizeExchangeSpec.adminPaths(): AuthorizeExchangeSpec.Access = pathMatchers(
"/account/**"
)
private fun AuthorizeExchangeSpec.publicPaths(): AuthorizeExchangeSpec.Access = pathMatchers( private fun AuthorizeExchangeSpec.publicPaths(): AuthorizeExchangeSpec.Access = pathMatchers(
"/css/**", "/css/**",
"/js/**", "/js/**",

View File

@@ -0,0 +1,30 @@
package ltd.hlaeja.controller
import ltd.hlaeja.service.AccountRegistryService
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import reactor.core.publisher.Mono
@Controller
@RequestMapping("/account")
class AccountController(
private val accountRegistryService: AccountRegistryService,
) {
@GetMapping
fun getAccounts(
@RequestParam(defaultValue = "1") page: Int,
@RequestParam(defaultValue = "2") size: Int,
model: Model,
): Mono<String> = accountRegistryService.getAccounts(page, size)
.collectList()
.doOnNext { items ->
model.addAttribute("items", items)
model.addAttribute("page", page)
model.addAttribute("size", size)
}
.then(Mono.just("account/users"))
}

View File

@@ -1,15 +1,20 @@
package ltd.hlaeja.service package ltd.hlaeja.service
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import ltd.hlaeja.library.accountRegistry.Account
import ltd.hlaeja.library.accountRegistry.Authentication import ltd.hlaeja.library.accountRegistry.Authentication
import ltd.hlaeja.property.AccountRegistryProperty import ltd.hlaeja.property.AccountRegistryProperty
import ltd.hlaeja.util.accountRegistryAccounts
import ltd.hlaeja.util.accountRegistryAuthenticate import ltd.hlaeja.util.accountRegistryAuthenticate
import org.springframework.http.HttpStatus.BAD_REQUEST
import org.springframework.security.authentication.AuthenticationServiceException import org.springframework.security.authentication.AuthenticationServiceException
import org.springframework.security.core.AuthenticationException import org.springframework.security.core.AuthenticationException
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.WebClientRequestException import org.springframework.web.reactive.function.client.WebClientRequestException
import org.springframework.web.reactive.function.client.WebClientResponseException import org.springframework.web.reactive.function.client.WebClientResponseException
import org.springframework.web.server.ResponseStatusException
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
private val log = KotlinLogging.logger {} private val log = KotlinLogging.logger {}
@@ -43,4 +48,10 @@ class AccountRegistryService(
} }
} }
} }
fun getAccounts(
page: Int,
size: Int,
): Flux<Account.Response> = webClient.accountRegistryAccounts(page, size, property)
.onErrorResume { error -> Flux.error(ResponseStatusException(BAD_REQUEST, error.message, error)) }
} }

View File

@@ -1,5 +1,6 @@
package ltd.hlaeja.util package ltd.hlaeja.util
import ltd.hlaeja.library.accountRegistry.Account
import ltd.hlaeja.library.accountRegistry.Authentication import ltd.hlaeja.library.accountRegistry.Authentication
import ltd.hlaeja.property.AccountRegistryProperty import ltd.hlaeja.property.AccountRegistryProperty
import org.springframework.http.HttpStatus.LOCKED import org.springframework.http.HttpStatus.LOCKED
@@ -9,6 +10,7 @@ import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.authentication.LockedException import org.springframework.security.authentication.LockedException
import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.web.reactive.function.client.WebClient import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
fun WebClient.accountRegistryAuthenticate( fun WebClient.accountRegistryAuthenticate(
@@ -22,3 +24,12 @@ fun WebClient.accountRegistryAuthenticate(
.onStatus(UNAUTHORIZED::equals) { throw BadCredentialsException("Invalid credentials") } .onStatus(UNAUTHORIZED::equals) { throw BadCredentialsException("Invalid credentials") }
.onStatus(NOT_FOUND::equals) { throw UsernameNotFoundException("User not found") } .onStatus(NOT_FOUND::equals) { throw UsernameNotFoundException("User not found") }
.bodyToMono(Authentication.Response::class.java) .bodyToMono(Authentication.Response::class.java)
fun WebClient.accountRegistryAccounts(
page: Int,
size: Int,
property: AccountRegistryProperty
): Flux<Account.Response> = get()
.uri("${property.url}/accounts?page=$page&size=$size".also(::logCall))
.retrieve()
.bodyToFlux(Account.Response::class.java)

View File

@@ -0,0 +1,32 @@
<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Home Pages</title>
<!--/*/<th:block th:insert="~{layout.html :: documentHead}"/>/*/-->
</head>
<body>
<main>
<h1>Test</h1>
<hr>
<table>
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
</tr>
<tr th:each="item : ${items}">
<td th:text="${item.id}">ID</td>
<td th:text="${item.timestamp}">timestamp</td>
<td th:text="${item.username}">username</td>
</tr>
</table>
<a th:href="@{/account(page=${page}+1, size=${size})}">Next</a>
<a th:href="@{/account(page=${page}-1, size=${size})}" th:unless="${page == 1}">Previous</a>
<br>
<a href="/logout">Logout</a>
</main>
<!--/*/<th:block th:replace="~{layout.html :: script}"/>/*/-->
</body>
</html>

View File

@@ -11,6 +11,7 @@
<!--/*@thymesVar id="remoteUser" type="ltd.hlaeja.security.RemoteAuthentication"*/--> <!--/*@thymesVar id="remoteUser" type="ltd.hlaeja.security.RemoteAuthentication"*/-->
<div th:if="${remoteUser.hasRole('admin')}"> <div th:if="${remoteUser.hasRole('admin')}">
You are an admin! You are an admin!
<a href="/account">Account</a>
</div> </div>
<div th:if="${remoteUser.hasRole('user')}"> <div th:if="${remoteUser.hasRole('user')}">
You are a user! You are a user!