1
0
mirror of https://github.com/pcvolkmer/etl-processor.git synced 2025-04-19 17:26:51 +00:00

feat: add basic support for OIDC login

This commit is contained in:
Paul-Christian Volkmer 2024-01-31 15:43:10 +01:00
parent f71a775e12
commit 17e04a3f89
7 changed files with 60 additions and 11 deletions

View File

@ -58,6 +58,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6") implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.springframework.kafka:spring-kafka") implementation("org.springframework.kafka:spring-kafka")

View File

@ -86,7 +86,8 @@ data class KafkaTargetProperties(
data class SecurityConfigProperties( data class SecurityConfigProperties(
val adminUser: String?, val adminUser: String?,
val adminPassword: String?, val adminPassword: String?,
val enableTokens: Boolean = false val enableTokens: Boolean = false,
val enableOidc: Boolean = false
) { ) {
companion object { companion object {
const val NAME = "app.security" const val NAME = "app.security"

View File

@ -24,21 +24,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.http.HttpMethod
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.crypto.factory.PasswordEncoderFactories import org.springframework.security.crypto.factory.PasswordEncoderFactories
import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
import java.util.* import java.util.*
@ -82,6 +76,30 @@ class AppSecurityConfiguration(
} }
@Bean @Bean
@ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "true")
fun filterChainOidc(http: HttpSecurity, passwordEncoder: PasswordEncoder): SecurityFilterChain {
http {
authorizeRequests {
authorize("/configs/**", hasRole("ADMIN"))
authorize("/mtbfile/**", hasAnyRole("MTBFILE"))
authorize(anyRequest, permitAll)
}
httpBasic {
realmName = "ETL-Processor"
}
formLogin {
loginPage = "/login"
}
oauth2Login {
loginPage = "/login"
}
csrf { disable() }
}
return http.build()
}
@Bean
@ConditionalOnProperty(value = ["app.security.enable-oidc"], havingValue = "false", matchIfMissing = true)
fun filterChain(http: HttpSecurity, passwordEncoder: PasswordEncoder): SecurityFilterChain { fun filterChain(http: HttpSecurity, passwordEncoder: PasswordEncoder): SecurityFilterChain {
http { http {
authorizeRequests { authorizeRequests {

View File

@ -19,14 +19,29 @@
package dev.dnpm.etl.processor.web package dev.dnpm.etl.processor.web
import dev.dnpm.etl.processor.config.SecurityConfigProperties
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties
import org.springframework.stereotype.Controller import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import java.security.Principal
@Controller @Controller
class LoginController { class LoginController(
private val securityConfigProperties: SecurityConfigProperties,
private val oAuth2ClientProperties: OAuth2ClientProperties?
) {
@GetMapping(path = ["/login"]) @GetMapping(path = ["/login"])
fun login(): String { fun login(principal: Principal?, model: Model): String {
if (securityConfigProperties.enableOidc) {
model.addAttribute(
"oidcLogins",
oAuth2ClientProperties?.registration?.map { (key, value) -> Pair(key, value.clientName) }.orEmpty()
)
} else {
model.addAttribute("oidcLogins", emptyList<Pair<String, String>>())
}
return "login" return "login"
} }

View File

@ -209,7 +209,14 @@ form.samplecode-input input:focus-visible {
border-radius: 3px; border-radius: 3px;
} }
.login-form form hr,
.token-form form hr {
padding: 0;
width: 100%;
}
.login-form button, .login-form button,
.login-form a.btn,
.token-form button { .token-form button {
margin: 1em 0; margin: 1em 0;
background: var(--bg-blue); background: var(--bg-blue);

View File

@ -21,6 +21,11 @@
<a th:href="@{/login}">Login</a> <a th:href="@{/login}">Login</a>
</li> </li>
<li class="login" sec:authorize="isAuthenticated()"> <li class="login" sec:authorize="isAuthenticated()">
<span>
<span>👤</span>
<span sec:authentication="name">?</span>
</span>
&nbsp;
<a th:href="@{/logout}">Abmelden</a> <a th:href="@{/logout}">Abmelden</a>
</li> </li>
</ul> </ul>

View File

@ -13,9 +13,11 @@
<div class="centered notification error" th:if="${param.error}">Anmeldung nicht erfolgreich</div> <div class="centered notification error" th:if="${param.error}">Anmeldung nicht erfolgreich</div>
<div class="centered notification success" th:if="${param.logout}">Sie haben sich abgemeldet</div> <div class="centered notification success" th:if="${param.logout}">Sie haben sich abgemeldet</div>
<form method="post" th:action="@{/login}"> <form method="post" th:action="@{/login}">
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required="" autofocus=""> <input type="text" id="username" name="username" class="form-control" placeholder="Username" required="" autofocus="" />
<input type="password" id="password" name="password" class="form-control" placeholder="Password" required=""> <input type="password" id="password" name="password" class="form-control" placeholder="Password" required="" />
<button type="submit">Anmelden</button> <button type="submit">Anmelden</button>
<hr th:if="${not oidcLogins.isEmpty()}" />
<a th:each="oidcLogin : ${oidcLogins}" class="btn" th:href="@{/oauth2/authorization/{provider}(provider=${oidcLogin.component1()})}">OIDC Login - [[ ${oidcLogin.component2()} ]]</a>
</form> </form>
</div> </div>
</main> </main>