Skip to content

Commit

Permalink
BE: Impl custom auth page (#635)
Browse files Browse the repository at this point in the history
Co-authored-by: German Osin <[email protected]>
  • Loading branch information
Haarolean and germanosin authored Dec 28, 2024
1 parent bacf1b4 commit 517502b
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 138 deletions.
1 change: 1 addition & 0 deletions .java-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
21
Original file line number Diff line number Diff line change
@@ -1,24 +1,53 @@
package io.kafbat.ui.config.auth;

import io.kafbat.ui.util.EmptyRedirectStrategy;
import java.net.URI;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler;

abstract class AbstractAuthSecurityConfig {

protected AbstractAuthSecurityConfig() {

}

protected static final String LOGIN_URL = "/login";
protected static final String LOGOUT_URL = "/auth?logout";

protected static final String[] AUTH_WHITELIST = {
"/css/**",
"/js/**",
"/media/**",
/* STATIC */
"/index.html",
"/assets/**",
"/manifest.json",
"/favicon.svg",
"/favicon/**",

"/static/**",
"/resources/**",

/* ACTUATOR */
"/actuator/health/**",
"/actuator/info",
"/actuator/prometheus",
"/auth",

/* AUTH */
"/login",
"/logout",
"/oauth2/**",
"/static/**"
"/api/config/authentication",
"/api/authorization"
};

protected RedirectServerAuthenticationSuccessHandler emptyRedirectSuccessHandler() {
final var authHandler = new RedirectServerAuthenticationSuccessHandler();
authHandler.setRedirectStrategy(new EmptyRedirectStrategy());
return authHandler;
}

protected RedirectServerLogoutSuccessHandler redirectLogoutSuccessHandler() {
final var logoutSuccessHandler = new RedirectServerLogoutSuccessHandler();
logoutSuccessHandler.setLogoutSuccessUrl(URI.create(LOGOUT_URL));
return logoutSuccessHandler;
}

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package io.kafbat.ui.config.auth;

import io.kafbat.ui.util.EmptyRedirectStrategy;
import io.kafbat.ui.util.StaticFileWebFilter;
import java.net.URI;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
Expand All @@ -20,32 +22,28 @@
@Slf4j
public class BasicAuthSecurityConfig extends AbstractAuthSecurityConfig {

public static final String LOGIN_URL = "/auth";
public static final String LOGOUT_URL = "/auth?logout";

@Bean
public SecurityWebFilterChain configure(ServerHttpSecurity http) {
log.info("Configuring LOGIN_FORM authentication.");

final var authHandler = new RedirectServerAuthenticationSuccessHandler();
authHandler.setRedirectStrategy(new EmptyRedirectStrategy());

final var logoutSuccessHandler = new RedirectServerLogoutSuccessHandler();
logoutSuccessHandler.setLogoutSuccessUrl(URI.create(LOGOUT_URL));


return http.authorizeExchange(spec -> spec
var builder = http.authorizeExchange(spec -> spec
.pathMatchers(AUTH_WHITELIST)
.permitAll()
.anyExchange()
.authenticated()
)
.formLogin(spec -> spec.loginPage(LOGIN_URL).authenticationSuccessHandler(authHandler))
.formLogin(form -> form
.loginPage(LOGIN_URL)
.authenticationSuccessHandler(emptyRedirectSuccessHandler())
)
.logout(spec -> spec
.logoutSuccessHandler(logoutSuccessHandler)
.logoutSuccessHandler(redirectLogoutSuccessHandler())
.requiresLogout(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/logout")))
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.build();
.csrf(ServerHttpSecurity.CsrfSpec::disable);

builder.addFilterAt(new StaticFileWebFilter(), SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING);

return builder.build();
}

}
27 changes: 18 additions & 9 deletions api/src/main/java/io/kafbat/ui/config/auth/LdapSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package io.kafbat.ui.config.auth;

import static io.kafbat.ui.config.auth.AbstractAuthSecurityConfig.AUTH_WHITELIST;

import io.kafbat.ui.service.rbac.AccessControlService;
import io.kafbat.ui.service.rbac.extractor.RbacLdapAuthoritiesExtractor;
import io.kafbat.ui.util.StaticFileWebFilter;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
Expand All @@ -14,15 +13,16 @@
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerAdapter;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
Expand All @@ -36,14 +36,15 @@
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;

@Configuration
@EnableWebFluxSecurity
@ConditionalOnProperty(value = "auth.type", havingValue = "LDAP")
@EnableConfigurationProperties(LdapProperties.class)
@RequiredArgsConstructor
@Slf4j
public class LdapSecurityConfig {
public class LdapSecurityConfig extends AbstractAuthSecurityConfig {

private final LdapProperties props;

Expand Down Expand Up @@ -121,16 +122,24 @@ public SecurityWebFilterChain configureLdap(ServerHttpSecurity http) {
log.info("Active Directory support for LDAP has been enabled.");
}

return http.authorizeExchange(spec -> spec
var builder = http.authorizeExchange(spec -> spec
.pathMatchers(AUTH_WHITELIST)
.permitAll()
.anyExchange()
.authenticated()
)
.formLogin(Customizer.withDefaults())
.logout(Customizer.withDefaults())
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.build();
.formLogin(form -> form
.loginPage(LOGIN_URL)
.authenticationSuccessHandler(emptyRedirectSuccessHandler())
)
.logout(spec -> spec
.logoutSuccessHandler(redirectLogoutSuccessHandler())
.requiresLogout(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/logout")))
.csrf(ServerHttpSecurity.CsrfSpec::disable);

builder.addFilterAt(new StaticFileWebFilter(), SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING);

return builder.build();
}

private static class UserDetailsMapper extends LdapUserDetailsMapper {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.kafbat.ui.config.auth.logout.OAuthLogoutSuccessHandler;
import io.kafbat.ui.service.rbac.AccessControlService;
import io.kafbat.ui.service.rbac.extractor.ProviderAuthorityExtractor;
import io.kafbat.ui.util.StaticFileWebFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand All @@ -19,6 +20,7 @@
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
Expand Down Expand Up @@ -50,16 +52,20 @@ public class OAuthSecurityConfig extends AbstractAuthSecurityConfig {
public SecurityWebFilterChain configure(ServerHttpSecurity http, OAuthLogoutSuccessHandler logoutHandler) {
log.info("Configuring OAUTH2 authentication.");

return http.authorizeExchange(spec -> spec
var builder = http.authorizeExchange(spec -> spec
.pathMatchers(AUTH_WHITELIST)
.permitAll()
.anyExchange()
.authenticated()
)
.oauth2Login(Customizer.withDefaults())
.logout(spec -> spec.logoutSuccessHandler(logoutHandler))
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.build();
.csrf(ServerHttpSecurity.CsrfSpec::disable);


builder.addFilterAt(new StaticFileWebFilter(), SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING);

return builder.build();
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.kafbat.ui.api.ApplicationConfigApi;
import io.kafbat.ui.config.ClustersProperties;
import io.kafbat.ui.model.ActionDTO;
import io.kafbat.ui.model.AppAuthenticationSettingsDTO;
import io.kafbat.ui.model.ApplicationConfigDTO;
import io.kafbat.ui.model.ApplicationConfigPropertiesDTO;
import io.kafbat.ui.model.ApplicationConfigValidationDTO;
Expand Down Expand Up @@ -66,6 +67,13 @@ public Mono<ResponseEntity<ApplicationInfoDTO>> getApplicationInfo(ServerWebExch
return Mono.just(applicationInfoService.getApplicationInfo()).map(ResponseEntity::ok);
}

@Override
public Mono<ResponseEntity<AppAuthenticationSettingsDTO>> getAuthenticationSettings(
ServerWebExchange exchange) {
return Mono.just(applicationInfoService.getAuthenticationProperties())
.map(ResponseEntity::ok);
}

@Override
public Mono<ResponseEntity<ApplicationConfigDTO>> getCurrentConfig(ServerWebExchange exchange) {
var context = AccessContext.builder()
Expand Down Expand Up @@ -109,7 +117,7 @@ public Mono<ResponseEntity<UploadedFileInfoDTO>> uploadConfigRelatedFile(Flux<Pa
.then(fileFlux.single())
.flatMap(file ->
dynamicConfigOperations.uploadConfigRelatedFile((FilePart) file)
.map(path -> new UploadedFileInfoDTO().location(path.toString()))
.map(path -> new UploadedFileInfoDTO(path.toString()))
.map(ResponseEntity::ok))
.doOnEach(sig -> audit(context, sig));
}
Expand Down
99 changes: 0 additions & 99 deletions api/src/main/java/io/kafbat/ui/controller/AuthController.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.kafbat.ui.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@RequiredArgsConstructor
@Slf4j
public class AuthenticationController {

private static final String INDEX_HTML = "/static/index.html";

@GetMapping(value = "/login", produces = {"text/html"})
public Mono<ClassPathResource> getLoginPage() {
return Mono.just(new ClassPathResource(INDEX_HTML));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
@RestController
@RequiredArgsConstructor
@Slf4j
public class AccessController implements AuthorizationApi {
public class AuthorizationController implements AuthorizationApi {

private final AccessControlService accessControlService;

Expand Down
Loading

0 comments on commit 517502b

Please sign in to comment.