From eae23e2f81fbbfedebab63dfc1141e0a41676882 Mon Sep 17 00:00:00 2001 From: German Osin Date: Mon, 23 Dec 2024 11:53:38 +0100 Subject: [PATCH] Fixed login page renderer --- .java-version | 1 + .../auth/AbstractAuthSecurityConfig.java | 7 ++- .../ui/config/auth/OAuthSecurityConfig.java | 15 ++++-- .../controller/AuthenticationController.java | 6 +++ .../kafbat/ui/util/StaticFileWebFilter.java | 52 +++++++++++++++++++ frontend/src/components/App.tsx | 2 +- 6 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 .java-version create mode 100644 api/src/main/java/io/kafbat/ui/util/StaticFileWebFilter.java diff --git a/.java-version b/.java-version new file mode 100644 index 000000000..aabe6ec39 --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +21 diff --git a/api/src/main/java/io/kafbat/ui/config/auth/AbstractAuthSecurityConfig.java b/api/src/main/java/io/kafbat/ui/config/auth/AbstractAuthSecurityConfig.java index dc58a7299..d706daef8 100644 --- a/api/src/main/java/io/kafbat/ui/config/auth/AbstractAuthSecurityConfig.java +++ b/api/src/main/java/io/kafbat/ui/config/auth/AbstractAuthSecurityConfig.java @@ -19,7 +19,12 @@ protected AbstractAuthSecurityConfig() { "/logout", "/oauth2/**", "/static/**", - "/api/config/authentication" + "/api/config/authentication", + "/index.html", + "/assets/**", + "/manifest.json", + "/favicon/**", + "/api/authorization" }; } diff --git a/api/src/main/java/io/kafbat/ui/config/auth/OAuthSecurityConfig.java b/api/src/main/java/io/kafbat/ui/config/auth/OAuthSecurityConfig.java index 09c7df794..7b32f7d2a 100644 --- a/api/src/main/java/io/kafbat/ui/config/auth/OAuthSecurityConfig.java +++ b/api/src/main/java/io/kafbat/ui/config/auth/OAuthSecurityConfig.java @@ -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; @@ -16,9 +17,11 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; 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; @@ -50,7 +53,7 @@ public class OAuthSecurityConfig extends AbstractAuthSecurityConfig { public SecurityWebFilterChain configure(ServerHttpSecurity http, OAuthLogoutSuccessHandler logoutHandler) { log.info("Configuring OAUTH2 authentication."); - return http.authorizeExchange(spec -> spec + ServerHttpSecurity builder = http.authorizeExchange(spec -> spec .pathMatchers(AUTH_WHITELIST) .permitAll() .anyExchange() @@ -58,8 +61,14 @@ public SecurityWebFilterChain configure(ServerHttpSecurity http, OAuthLogoutSucc ) .oauth2Login(Customizer.withDefaults()) .logout(spec -> spec.logoutSuccessHandler(logoutHandler)) - .csrf(ServerHttpSecurity.CsrfSpec::disable) - .build(); + .csrf(ServerHttpSecurity.CsrfSpec::disable); + + + builder.addFilterAt(new StaticFileWebFilter( + "/login", new ClassPathResource("/static/index.html") + ), SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING); + + return builder.build(); } @Bean diff --git a/api/src/main/java/io/kafbat/ui/controller/AuthenticationController.java b/api/src/main/java/io/kafbat/ui/controller/AuthenticationController.java index b50c64546..1c7145847 100644 --- a/api/src/main/java/io/kafbat/ui/controller/AuthenticationController.java +++ b/api/src/main/java/io/kafbat/ui/controller/AuthenticationController.java @@ -3,6 +3,7 @@ import java.nio.charset.Charset; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ClassPathResource; import org.springframework.security.web.server.csrf.CsrfToken; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; @@ -15,6 +16,11 @@ @Slf4j public class AuthenticationController { + @GetMapping(value = "/login", produces = {"text/html"}) + public Mono getLoginPage(ServerWebExchange exchange) { + return Mono.just(new ClassPathResource("static/index.html")); + } + @GetMapping(value = "/auth", produces = {"text/html"}) public Mono getAuth(ServerWebExchange exchange) { Mono token = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty()); diff --git a/api/src/main/java/io/kafbat/ui/util/StaticFileWebFilter.java b/api/src/main/java/io/kafbat/ui/util/StaticFileWebFilter.java new file mode 100644 index 000000000..391fa8dbd --- /dev/null +++ b/api/src/main/java/io/kafbat/ui/util/StaticFileWebFilter.java @@ -0,0 +1,52 @@ +package io.kafbat.ui.util; + +import java.io.IOException; +import java.io.InputStream; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +public class StaticFileWebFilter implements WebFilter { + private ServerWebExchangeMatcher matcher; + private String contents; + + public StaticFileWebFilter(String path, ClassPathResource resource) { + this.matcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, path); + try (InputStream inputStream = resource.getInputStream()) { + this.contents = ResourceUtil.readAsString(resource); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + return this.matcher.matches(exchange) + .filter(ServerWebExchangeMatcher.MatchResult::isMatch) + .switchIfEmpty(chain.filter(exchange).then(Mono.empty())) + .flatMap((matchResult) -> this.render(exchange)); + } + + private Mono render(ServerWebExchange exchange) { + String contextPath = exchange.getRequest().getPath().contextPath().value(); + String contentBody = contents + .replace("\"assets/", "\"" + contextPath + "/assets/") + .replace("PUBLIC-PATH-VARIABLE", contextPath); + + ServerHttpResponse result = exchange.getResponse(); + result.setStatusCode(HttpStatus.OK); + result.getHeaders().setContentType(MediaType.TEXT_HTML); + DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory(); + return result.writeWith(Mono.just(bufferFactory.wrap(contentBody.getBytes()))); + } + +} diff --git a/frontend/src/components/App.tsx b/frontend/src/components/App.tsx index 413d397be..d15cb95a0 100644 --- a/frontend/src/components/App.tsx +++ b/frontend/src/components/App.tsx @@ -50,7 +50,7 @@ const queryClient = new QueryClient({ }); const App: React.FC = () => { const { isDarkMode } = useContext(ThemeModeContext); - const isAuthRoute = useMatch('/auth/*'); + const isAuthRoute = useMatch('/login'); return (