From 0727d52f64d6594a3759996d99e62efb126e1ce0 Mon Sep 17 00:00:00 2001 From: German Osin Date: Thu, 20 Mar 2025 00:28:54 +0100 Subject: [PATCH 1/2] Introduced Resource server configuration --- api/build.gradle | 1 + .../ui/config/auth/OAuthProperties.java | 2 + .../ui/config/auth/OAuthSecurityConfig.java | 15 ++++ .../compose/oauth/docker-compose.yml | 86 +++++++++++++++++++ documentation/compose/oauth/realm-export.json | 28 ++++++ gradle/libs.versions.toml | 1 + 6 files changed, 133 insertions(+) create mode 100644 documentation/compose/oauth/docker-compose.yml create mode 100644 documentation/compose/oauth/realm-export.json diff --git a/api/build.gradle b/api/build.gradle index 9cdbae4f1..ca51f8b21 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -18,6 +18,7 @@ dependencies { implementation libs.spring.starter.actuator implementation libs.spring.starter.logging implementation libs.spring.starter.oauth2.client + implementation libs.spring.security.oauth2.resource.server implementation libs.spring.boot.actuator compileOnly libs.spring.boot.devtools diff --git a/api/src/main/java/io/kafbat/ui/config/auth/OAuthProperties.java b/api/src/main/java/io/kafbat/ui/config/auth/OAuthProperties.java index 7021be8d5..5c861b7e1 100644 --- a/api/src/main/java/io/kafbat/ui/config/auth/OAuthProperties.java +++ b/api/src/main/java/io/kafbat/ui/config/auth/OAuthProperties.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Set; import lombok.Data; +import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.util.Assert; @@ -13,6 +14,7 @@ @Data public class OAuthProperties { private Map client = new HashMap<>(); + private OAuth2ResourceServerProperties resourceServer = null; @PostConstruct public void init() { 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 4794b83ca..1787ad847 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 @@ -14,6 +14,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper; +import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -62,6 +63,20 @@ public SecurityWebFilterChain configure(ServerHttpSecurity http, OAuthLogoutSucc .logout(spec -> spec.logoutSuccessHandler(logoutHandler)) .csrf(ServerHttpSecurity.CsrfSpec::disable); + if (properties.getResourceServer() != null) { + OAuth2ResourceServerProperties resourceServer = properties.getResourceServer(); + if (resourceServer.getJwt() != null) { + builder.oauth2ResourceServer((c) -> c.jwt((j) -> j.jwkSetUri(resourceServer.getJwt().getJwkSetUri()))); + } else if (resourceServer.getOpaquetoken() != null) { + OAuth2ResourceServerProperties.Opaquetoken opaquetoken = resourceServer.getOpaquetoken(); + builder.oauth2ResourceServer( + (c) -> c.opaqueToken( + (o) -> o.introspectionUri(opaquetoken.getIntrospectionUri()) + .introspectionClientCredentials(opaquetoken.getClientId(), opaquetoken.getClientSecret()) + ) + ); + } + } builder.addFilterAt(new StaticFileWebFilter(), SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING); diff --git a/documentation/compose/oauth/docker-compose.yml b/documentation/compose/oauth/docker-compose.yml new file mode 100644 index 000000000..9636cd01d --- /dev/null +++ b/documentation/compose/oauth/docker-compose.yml @@ -0,0 +1,86 @@ +version: '3.8' + +services: + keycloak: + image: quay.io/keycloak/keycloak:latest + container_name: keycloak + restart: always + command: start-dev --import-realm + environment: + - KEYCLOAK_ADMIN=admin + - KEYCLOAK_ADMIN_PASSWORD=admin + - KC_DB=postgres + - KC_DB_URL=jdbc:postgresql://db/keycloak + - KC_DB_USERNAME=keycloak + - KC_DB_PASSWORD=keycloak + - KC_HOSTNAME=keycloak.oauth.orb.local + - KC_HOSTNAME_STRICT=false + ports: + - "8080:8080" + volumes: + - ./realm-export.json:/opt/keycloak/data/import/realm-export.json + depends_on: + - db + + db: + image: postgres:15 + container_name: keycloak-db + restart: always + environment: + - POSTGRES_DB=keycloak + - POSTGRES_USER=keycloak + - POSTGRES_PASSWORD=keycloak + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + kafka: + image: confluentinc/cp-kafka:7.8.0 + hostname: kafka + container_name: kafka + ports: + - "9092:9092" + - "9997:9997" + environment: + KAFKA_BROKER_ID: 1 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT' + KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092' + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + KAFKA_JMX_PORT: 9997 + KAFKA_JMX_HOSTNAME: localhost + KAFKA_PROCESS_ROLES: 'broker,controller' + KAFKA_NODE_ID: 1 + KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka:29093' + KAFKA_LISTENERS: 'PLAINTEXT://kafka:29092,CONTROLLER://kafka:29093,PLAINTEXT_HOST://0.0.0.0:9092' + KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT' + KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER' + KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs' + CLUSTER_ID: 'MkU3OEVBNTcwNTJENDM2Qk' + + kafbat-ui: + container_name: kafbat-ui + image: ghcr.io/kafbat/kafka-ui:0.0.1-SNAPSHOT + ports: + - 8090:8080 + depends_on: + - kafka + - keycloak + environment: + KAFKA_CLUSTERS_0_NAME: local + KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092 + AUTH_TYPE: "OAUTH2" + AUTH_OAUTH2_RESOURCE_SERVER_JWT_JWK_SET_URI: "http://keycloak.oauth.orb.local:8080/realms/myrealm/protocol/openid-connect/certs" + AUTH_OAUTH2_CLIENT_KEYCLOACK_CLIENT_ID: "my-client" + AUTH_OAUTH2_CLIENT_KEYCLOACK_CLIENT_SECRET: "my-secret" + AUTH_OAUTH2_CLIENT_KEYCLOACK_SCOPE: openid + AUTH_OAUTH2_CLIENT_KEYCLOACK_CLIENT_NAME: keycloack + AUTH_OAUTH2_CLIENT_KEYCLOACK_PROVIDER: keycloack + AUTH_OAUTH2_CLIENT_KEYCLOACK_CUSTOM_PARAMS_TYPE: oauth + AUTH_OAUTH2_CLIENT_KEYCLOACK_ISSUER_URI: "http://keycloak.oauth.orb.local:8080/realms/myrealm" + AUTH_OAUTH2_CLIENT_KEYCLOACK_USER_NAME_ATTRIBUTE: "preferred_username" +volumes: + postgres_data: diff --git a/documentation/compose/oauth/realm-export.json b/documentation/compose/oauth/realm-export.json new file mode 100644 index 000000000..621584e9d --- /dev/null +++ b/documentation/compose/oauth/realm-export.json @@ -0,0 +1,28 @@ +{ + "id": "myrealm", + "realm": "myrealm", + "enabled": true, + "clients": [ + { + "clientId": "my-client", + "enabled": true, + "publicClient": false, + "secret": "my-secret", + "directAccessGrantsEnabled": true, + "redirectUris": ["http://localhost:8090/*"] + } + ], + "users": [ + { + "username": "testuser", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "testpassword" + } + ] + } + ] +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e61bf538a..5a7e7f96f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -66,6 +66,7 @@ spring-boot-devtools = { module = 'org.springframework.boot:spring-boot-devtools spring-boot-configuration-processor = { module = 'org.springframework.boot:spring-boot-configuration-processor', version.ref = 'spring-boot' } spring-security-ldap = { module = 'org.springframework.security:spring-security-ldap' } +spring-security-oauth2-resource-server = { module = 'org.springframework.security:spring-security-oauth2-resource-server'} swagger-integration-jakarta = { module = 'io.swagger.core.v3:swagger-integration-jakarta', version.ref = 'swagger-integration-jakarta' } lombok = { module = 'org.projectlombok:lombok', version.ref = 'lombok' } From 48c46d67228bd2037606a177938da6040c04ed98 Mon Sep 17 00:00:00 2001 From: German Osin Date: Thu, 20 Mar 2025 12:29:07 +0100 Subject: [PATCH 2/2] Moved docker compose to separate repository (ui-config-examples) --- .../compose/oauth/docker-compose.yml | 86 ------------------- documentation/compose/oauth/realm-export.json | 28 ------ 2 files changed, 114 deletions(-) delete mode 100644 documentation/compose/oauth/docker-compose.yml delete mode 100644 documentation/compose/oauth/realm-export.json diff --git a/documentation/compose/oauth/docker-compose.yml b/documentation/compose/oauth/docker-compose.yml deleted file mode 100644 index 9636cd01d..000000000 --- a/documentation/compose/oauth/docker-compose.yml +++ /dev/null @@ -1,86 +0,0 @@ -version: '3.8' - -services: - keycloak: - image: quay.io/keycloak/keycloak:latest - container_name: keycloak - restart: always - command: start-dev --import-realm - environment: - - KEYCLOAK_ADMIN=admin - - KEYCLOAK_ADMIN_PASSWORD=admin - - KC_DB=postgres - - KC_DB_URL=jdbc:postgresql://db/keycloak - - KC_DB_USERNAME=keycloak - - KC_DB_PASSWORD=keycloak - - KC_HOSTNAME=keycloak.oauth.orb.local - - KC_HOSTNAME_STRICT=false - ports: - - "8080:8080" - volumes: - - ./realm-export.json:/opt/keycloak/data/import/realm-export.json - depends_on: - - db - - db: - image: postgres:15 - container_name: keycloak-db - restart: always - environment: - - POSTGRES_DB=keycloak - - POSTGRES_USER=keycloak - - POSTGRES_PASSWORD=keycloak - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - - kafka: - image: confluentinc/cp-kafka:7.8.0 - hostname: kafka - container_name: kafka - ports: - - "9092:9092" - - "9997:9997" - environment: - KAFKA_BROKER_ID: 1 - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT' - KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092' - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 - KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 - KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 - KAFKA_JMX_PORT: 9997 - KAFKA_JMX_HOSTNAME: localhost - KAFKA_PROCESS_ROLES: 'broker,controller' - KAFKA_NODE_ID: 1 - KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka:29093' - KAFKA_LISTENERS: 'PLAINTEXT://kafka:29092,CONTROLLER://kafka:29093,PLAINTEXT_HOST://0.0.0.0:9092' - KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT' - KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER' - KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs' - CLUSTER_ID: 'MkU3OEVBNTcwNTJENDM2Qk' - - kafbat-ui: - container_name: kafbat-ui - image: ghcr.io/kafbat/kafka-ui:0.0.1-SNAPSHOT - ports: - - 8090:8080 - depends_on: - - kafka - - keycloak - environment: - KAFKA_CLUSTERS_0_NAME: local - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092 - AUTH_TYPE: "OAUTH2" - AUTH_OAUTH2_RESOURCE_SERVER_JWT_JWK_SET_URI: "http://keycloak.oauth.orb.local:8080/realms/myrealm/protocol/openid-connect/certs" - AUTH_OAUTH2_CLIENT_KEYCLOACK_CLIENT_ID: "my-client" - AUTH_OAUTH2_CLIENT_KEYCLOACK_CLIENT_SECRET: "my-secret" - AUTH_OAUTH2_CLIENT_KEYCLOACK_SCOPE: openid - AUTH_OAUTH2_CLIENT_KEYCLOACK_CLIENT_NAME: keycloack - AUTH_OAUTH2_CLIENT_KEYCLOACK_PROVIDER: keycloack - AUTH_OAUTH2_CLIENT_KEYCLOACK_CUSTOM_PARAMS_TYPE: oauth - AUTH_OAUTH2_CLIENT_KEYCLOACK_ISSUER_URI: "http://keycloak.oauth.orb.local:8080/realms/myrealm" - AUTH_OAUTH2_CLIENT_KEYCLOACK_USER_NAME_ATTRIBUTE: "preferred_username" -volumes: - postgres_data: diff --git a/documentation/compose/oauth/realm-export.json b/documentation/compose/oauth/realm-export.json deleted file mode 100644 index 621584e9d..000000000 --- a/documentation/compose/oauth/realm-export.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "id": "myrealm", - "realm": "myrealm", - "enabled": true, - "clients": [ - { - "clientId": "my-client", - "enabled": true, - "publicClient": false, - "secret": "my-secret", - "directAccessGrantsEnabled": true, - "redirectUris": ["http://localhost:8090/*"] - } - ], - "users": [ - { - "username": "testuser", - "enabled": true, - "emailVerified": true, - "credentials": [ - { - "type": "password", - "value": "testpassword" - } - ] - } - ] -}