Cognito logout + revamp (#1063)
* application yml example * Update SSO guide * Add cognito logout handler * Fix annotations * impl a separate config file for cognito * cleanup * Rollback auth.type change * Add compose example * Auth revamp * Review suggestions * Use configurationProperties, rename urls to uris
This commit is contained in:
parent
6df2d0b602
commit
5db2c17994
8 changed files with 206 additions and 6 deletions
|
@ -11,3 +11,4 @@
|
||||||
9. [kafka-ui-reverse-proxy.yaml](./kafka-ui-reverse-proxy.yaml) - An example for using the app behind a proxy (like nginx).
|
9. [kafka-ui-reverse-proxy.yaml](./kafka-ui-reverse-proxy.yaml) - An example for using the app behind a proxy (like nginx).
|
||||||
10. [kafka-ui-sasl.yaml](./kafka-ui-sasl.yaml) - SASL auth for Kafka.
|
10. [kafka-ui-sasl.yaml](./kafka-ui-sasl.yaml) - SASL auth for Kafka.
|
||||||
11. [kafka-ui-traefik-proxy.yaml](./kafka-ui-traefik-proxy.yaml) - Traefik specific proxy configuration.
|
11. [kafka-ui-traefik-proxy.yaml](./kafka-ui-traefik-proxy.yaml) - Traefik specific proxy configuration.
|
||||||
|
12. [oauth-cognito.yaml](./oauth-cognito.yaml) - OAuth2 with Cognito
|
||||||
|
|
22
documentation/compose/oauth-cognito.yaml
Normal file
22
documentation/compose/oauth-cognito.yaml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
version: '3.4'
|
||||||
|
services:
|
||||||
|
|
||||||
|
kafka-ui:
|
||||||
|
container_name: kafka-ui
|
||||||
|
image: provectuslabs/kafka-ui:local
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
depends_on:
|
||||||
|
- kafka0 # OMITTED, TAKE UP AN EXAMPLE FROM OTHER COMPOSE FILES
|
||||||
|
environment:
|
||||||
|
KAFKA_CLUSTERS_0_NAME: local
|
||||||
|
KAFKA_CLUSTERS_0_PROPERTIES_SECURITY_PROTOCOL: SSL
|
||||||
|
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka0:29092
|
||||||
|
AUTH_TYPE: OAUTH2_COGNITO
|
||||||
|
AUTH_COGNITO_ISSUER_URI: "https://cognito-idp.eu-central-1.amazonaws.com/eu-central-xxxxxx"
|
||||||
|
AUTH_COGNITO_CLIENT_ID: ""
|
||||||
|
AUTH_COGNITO_CLIENT_SECRET: ""
|
||||||
|
AUTH_COGNITO_SCOPE: "openid"
|
||||||
|
AUTH_COGNITO_USER_NAME_ATTRIBUTE: "username"
|
||||||
|
AUTH_COGNITO_LOGOUT_URI: "https://<domain>.auth.eu-central-1.amazoncognito.com/logout"
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.provectus.kafka.ui.config;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.server.WebFilterExchange;
|
||||||
|
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
|
||||||
|
import org.springframework.security.web.util.UrlUtils;
|
||||||
|
import org.springframework.web.server.WebSession;
|
||||||
|
import org.springframework.web.util.UriComponents;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CognitoOidcLogoutSuccessHandler implements ServerLogoutSuccessHandler {
|
||||||
|
|
||||||
|
private final String logoutUrl;
|
||||||
|
private final String clientId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> onLogoutSuccess(final WebFilterExchange exchange, final Authentication authentication) {
|
||||||
|
final ServerHttpResponse response = exchange.getExchange().getResponse();
|
||||||
|
response.setStatusCode(HttpStatus.FOUND);
|
||||||
|
|
||||||
|
final var requestUri = exchange.getExchange().getRequest().getURI();
|
||||||
|
|
||||||
|
final var fullUrl = UrlUtils.buildFullRequestUrl(requestUri.getScheme(),
|
||||||
|
requestUri.getHost(), requestUri.getPort(),
|
||||||
|
requestUri.getPath(), requestUri.getQuery());
|
||||||
|
|
||||||
|
final UriComponents baseUrl = UriComponentsBuilder
|
||||||
|
.fromHttpUrl(fullUrl)
|
||||||
|
.replacePath("/")
|
||||||
|
.replaceQuery(null)
|
||||||
|
.fragment(null)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final var uri = UriComponentsBuilder
|
||||||
|
.fromUri(URI.create(logoutUrl))
|
||||||
|
.queryParam("client_id", clientId)
|
||||||
|
.queryParam("logout_uri", baseUrl)
|
||||||
|
.encode(StandardCharsets.UTF_8)
|
||||||
|
.build()
|
||||||
|
.toUri();
|
||||||
|
|
||||||
|
response.getHeaders().setLocation(uri);
|
||||||
|
return exchange.getExchange().getSession().flatMap(WebSession::invalidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package com.provectus.kafka.ui.config.auth;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.util.EmptyRedirectStrategy;
|
import com.provectus.kafka.ui.util.EmptyRedirectStrategy;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
@ -17,7 +17,7 @@ import org.springframework.security.web.server.ui.LogoutPageGeneratingWebFilter;
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebFluxSecurity
|
@EnableWebFluxSecurity
|
||||||
@ConditionalOnProperty(value = "auth.type", havingValue = "LOGIN_FORM")
|
@ConditionalOnProperty(value = "auth.type", havingValue = "LOGIN_FORM")
|
||||||
@Log4j2
|
@Slf4j
|
||||||
public class BasicAuthSecurityConfig extends AbstractAuthSecurityConfig {
|
public class BasicAuthSecurityConfig extends AbstractAuthSecurityConfig {
|
||||||
|
|
||||||
public static final String LOGIN_URL = "/auth";
|
public static final String LOGIN_URL = "/auth";
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.provectus.kafka.ui.config.auth;
|
||||||
|
|
||||||
|
import com.provectus.kafka.ui.config.CognitoOidcLogoutSuccessHandler;
|
||||||
|
import com.provectus.kafka.ui.config.auth.props.CognitoProperties;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
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.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
||||||
|
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
|
||||||
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
|
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebFluxSecurity
|
||||||
|
@ConditionalOnProperty(value = "auth.type", havingValue = "OAUTH2_COGNITO")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class CognitoOAuthSecurityConfig extends AbstractAuthSecurityConfig {
|
||||||
|
|
||||||
|
private static final String COGNITO = "cognito";
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityWebFilterChain configure(ServerHttpSecurity http, CognitoProperties props) {
|
||||||
|
log.info("Configuring Cognito OAUTH2 authentication.");
|
||||||
|
|
||||||
|
String clientId = props.getClientId();
|
||||||
|
String logoutUrl = props.getLogoutUri();
|
||||||
|
|
||||||
|
final ServerLogoutSuccessHandler logoutHandler = new CognitoOidcLogoutSuccessHandler(logoutUrl, clientId);
|
||||||
|
|
||||||
|
return http.authorizeExchange()
|
||||||
|
.pathMatchers(AUTH_WHITELIST)
|
||||||
|
.permitAll()
|
||||||
|
.anyExchange()
|
||||||
|
.authenticated()
|
||||||
|
|
||||||
|
.and()
|
||||||
|
.oauth2Login()
|
||||||
|
|
||||||
|
.and()
|
||||||
|
.oauth2Client()
|
||||||
|
|
||||||
|
.and()
|
||||||
|
.logout()
|
||||||
|
.logoutSuccessHandler(logoutHandler)
|
||||||
|
|
||||||
|
.and()
|
||||||
|
.csrf().disable()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public InMemoryReactiveClientRegistrationRepository clientRegistrationRepository(CognitoProperties props) {
|
||||||
|
ClientRegistration.Builder builder = ClientRegistrations
|
||||||
|
.fromIssuerLocation(props.getIssuerUri())
|
||||||
|
.registrationId(COGNITO);
|
||||||
|
|
||||||
|
builder.clientId(props.getClientId());
|
||||||
|
builder.clientSecret(props.getClientSecret());
|
||||||
|
|
||||||
|
Optional.ofNullable(props.getScope()).ifPresent(builder::scope);
|
||||||
|
Optional.ofNullable(props.getUserNameAttribute()).ifPresent(builder::userNameAttributeName);
|
||||||
|
|
||||||
|
return new InMemoryReactiveClientRegistrationRepository(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConfigurationProperties("auth.cognito")
|
||||||
|
public CognitoProperties cognitoProperties() {
|
||||||
|
return new CognitoProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package com.provectus.kafka.ui.config.auth;
|
package com.provectus.kafka.ui.config.auth;
|
||||||
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
@ -14,7 +14,7 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebFluxSecurity
|
@EnableWebFluxSecurity
|
||||||
@ConditionalOnProperty(value = "auth.type", havingValue = "DISABLED")
|
@ConditionalOnProperty(value = "auth.type", havingValue = "DISABLED")
|
||||||
@Log4j2
|
@Slf4j
|
||||||
public class DisabledAuthSecurityConfig extends AbstractAuthSecurityConfig {
|
public class DisabledAuthSecurityConfig extends AbstractAuthSecurityConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package com.provectus.kafka.ui.config.auth;
|
package com.provectus.kafka.ui.config.auth;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
@ -25,7 +25,7 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebFluxSecurity
|
@EnableWebFluxSecurity
|
||||||
@ConditionalOnProperty(value = "auth.type", havingValue = "LDAP")
|
@ConditionalOnProperty(value = "auth.type", havingValue = "LDAP")
|
||||||
@Log4j2
|
@Slf4j
|
||||||
public class LdapSecurityConfig extends AbstractAuthSecurityConfig {
|
public class LdapSecurityConfig extends AbstractAuthSecurityConfig {
|
||||||
|
|
||||||
@Value("${spring.ldap.urls}")
|
@Value("${spring.ldap.urls}")
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.provectus.kafka.ui.config.auth.props;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ToString(exclude = "clientSecret")
|
||||||
|
public class CognitoProperties {
|
||||||
|
|
||||||
|
String clientId;
|
||||||
|
String logoutUri;
|
||||||
|
String issuerUri;
|
||||||
|
String clientSecret;
|
||||||
|
@Nullable
|
||||||
|
String scope;
|
||||||
|
@Nullable
|
||||||
|
String userNameAttribute;
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLogoutUri() {
|
||||||
|
return logoutUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIssuerUri() {
|
||||||
|
return issuerUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientSecret() {
|
||||||
|
return clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getScope() {
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getUserNameAttribute() {
|
||||||
|
return userNameAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue