diff --git a/.travis/build.sh b/.travis/build.sh
index 3fc80b21..b104037a 100755
--- a/.travis/build.sh
+++ b/.travis/build.sh
@@ -38,13 +38,16 @@ mvn spotbugs:check
# Also test examples build on different architectures (exclude ppc64le until fixed)
if [ "$arch" != 'ppc64le' ]; then
mvn clean install -f examples/docker $MAVEN_EXTRA_ARGS
- cd examples/docker
- set +e
- ./spring/test-spring.sh
- EXIT=$?
- cd ../..
- exitIfError
- set -e
+
+ if [[ "$JAVA_MAJOR_VERSION" -ge "17" ]]; then
+ cd examples/docker
+ set +e
+ ./spring/test-spring.sh
+ EXIT=$?
+ cd ../..
+ exitIfError
+ set -e
+ fi
fi
# Run testsuite
diff --git a/examples/docker/kafka-oauth-strimzi/compose-spring-jwt.yml b/examples/docker/kafka-oauth-strimzi/compose-spring-jwt.yml
new file mode 100644
index 00000000..df3c16df
--- /dev/null
+++ b/examples/docker/kafka-oauth-strimzi/compose-spring-jwt.yml
@@ -0,0 +1,88 @@
+version: '3.5'
+
+services:
+
+ #################################### KAFKA BROKER ####################################
+ kafka:
+ image: strimzi/example-kafka
+ build: kafka-oauth-strimzi/kafka/target
+ container_name: kafka
+ command:
+ - /bin/bash
+ - /opt/kafka/start_without_wait.sh
+ ports:
+ - 9092:9092
+
+ # javaagent debug port
+ #- 5005:5005
+
+ environment:
+
+ # Java Debug
+ #KAFKA_DEBUG: y
+ #DEBUG_SUSPEND_FLAG: y
+ #JAVA_DEBUG_PORT: "*:5005"
+
+ #
+ # KAFKA Configuration
+ #
+ LOG_DIR: /home/kafka/logs
+ #KAFKA_LOG_DIRS: /home/kafka/1
+
+ KAFKA_PORT: 9092
+ KAFKA_ADVERTISED_HOST_NAME: kafka
+
+ KAFKA_BROKER_ID: 1
+ KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+ KAFKA_LISTENERS: CLIENT://kafka:9092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CLIENT:SASL_PLAINTEXT
+ KAFKA_SASL_ENABLED_MECHANISMS: OAUTHBEARER
+ KAFKA_INTER_BROKER_LISTENER_NAME: CLIENT
+ KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL: OAUTHBEARER
+
+ KAFKA_LISTENER_NAME_CLIENT_OAUTHBEARER_SASL_JAAS_CONFIG: "org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required;"
+ KAFKA_LISTENER_NAME_CLIENT_OAUTHBEARER_SASL_LOGIN_CALLBACK_HANDLER_CLASS: io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
+ KAFKA_LISTENER_NAME_CLIENT_OAUTHBEARER_SASL_SERVER_CALLBACK_HANDLER_CLASS: io.strimzi.kafka.oauth.server.JaasServerOauthValidatorCallbackHandler
+
+ KAFKA_SUPER_USERS: User:kafkabroker
+
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+
+
+
+ #
+ # Strimzi OAuth Configuration
+ #
+
+ # Authentication config for inter broker communication AND to authenticate to JWKS endpoint
+ # The JaasClientOauthLoginCallbackHandler for inter broker communication understands this as the client_id and secret for client_credentials
+ # While OAUTHBEARER can be used for inter broker communication it adds unnecessary complexity and lag. We really should be using mTLS instead.
+ # See: compose-spring.yml
+
+ # The JaasServerOauthValidatorCallbackHandler understand this same configuration as client_id and secret to use
+ # with Basic authentication to JWKS endpoint. In this example JWKS endpoint is not protected so authentication is redundant.
+ # Nevertheless, the presence of these configuration options results in `Authorization: Basic ...` header sent to Spring Authorization Server
+ OAUTH_CLIENT_ID: "kafkabroker"
+ OAUTH_CLIENT_SECRET: "kafkabrokersecret"
+ OAUTH_TOKEN_ENDPOINT_URI: "http://${SPRING_HOST:-spring}:8080/oauth2/token"
+
+ # Validation config
+ OAUTH_JWKS_ENDPOINT_URI: "http://${SPRING_HOST:-spring}:8080/oauth2/jwks"
+ OAUTH_JWKS_IGNORE_KEY_USE: "true"
+ OAUTH_VALID_ISSUER_URI: "http://${SPRING_HOST:-spring}:8080"
+ OAUTH_CHECK_ACCESS_TOKEN_TYPE: "false"
+
+ # For start.sh script to know where the keycloak is listening
+ SPRING_HOST: ${SPRING_HOST:-spring}
+ depends_on:
+ - zookeeper
+ restart: on-failure
+
+ zookeeper:
+ image: strimzi/example-zookeeper
+ build: kafka-oauth-strimzi/zookeeper/target
+ container_name: zookeeper
+ ports:
+ - 2181:2181
+ environment:
+ LOG_DIR: /home/kafka/logs
\ No newline at end of file
diff --git a/examples/docker/kafka-oauth-strimzi/compose-spring.yml b/examples/docker/kafka-oauth-strimzi/compose-spring.yml
index aac6b977..23ff293f 100644
--- a/examples/docker/kafka-oauth-strimzi/compose-spring.yml
+++ b/examples/docker/kafka-oauth-strimzi/compose-spring.yml
@@ -11,6 +11,7 @@ services:
- /bin/bash
- /opt/kafka/start_without_wait.sh
ports:
+ - 9091:9091
- 9092:9092
# javaagent debug port
@@ -29,44 +30,37 @@ services:
LOG_DIR: /home/kafka/logs
#KAFKA_LOG_DIRS: /home/kafka/1
+ KAFKA_PORT: 9092
+ KAFKA_ADVERTISED_HOST_NAME: kafka
+
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
- KAFKA_LISTENERS: CLIENT://kafka:9092
- KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CLIENT:SASL_PLAINTEXT
- KAFKA_SASL_ENABLED_MECHANISMS: OAUTHBEARER
- KAFKA_INTER_BROKER_LISTENER_NAME: CLIENT
- KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL: OAUTHBEARER
-
- KAFKA_LISTENER_NAME_CLIENT_OAUTHBEARER_SASL_JAAS_CONFIG: "org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required;"
- KAFKA_LISTENER_NAME_CLIENT_OAUTHBEARER_SASL_LOGIN_CALLBACK_HANDLER_CLASS: io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
- KAFKA_LISTENER_NAME_CLIENT_OAUTHBEARER_SASL_SERVER_CALLBACK_HANDLER_CLASS: io.strimzi.kafka.oauth.server.JaasServerOauthValidatorCallbackHandler
- KAFKA_SUPER_USERS: User:service-account-kafka-broker
+ KAFKA_LISTENERS: REPLICATION://kafka:9091,CLIENT://kafka:9092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: REPLICATION:SSL,CLIENT:SASL_PLAINTEXT
- KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_INTER_BROKER_LISTENER_NAME: REPLICATION
+ KAFKA_SSL_SECURE_RANDOM_IMPLEMENTATION: SHA1PRNG
+ KAFKA_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM: ""
+
+ KAFKA_LISTENER_NAME_REPLICATION_SSL_KEYSTORE_LOCATION: /tmp/kafka/cluster.keystore.p12
+ KAFKA_LISTENER_NAME_REPLICATION_SSL_KEYSTORE_PASSWORD: Z_pkTh9xgZovK4t34cGB2o6afT4zZg0L
+ KAFKA_LISTENER_NAME_REPLICATION_SSL_KEYSTORE_TYPE: PKCS12
+ KAFKA_LISTENER_NAME_REPLICATION_SSL_TRUSTSTORE_LOCATION: /tmp/kafka/cluster.truststore.p12
+ KAFKA_LISTENER_NAME_REPLICATION_SSL_TRUSTSTORE_PASSWORD: Z_pkTh9xgZovK4t34cGB2o6afT4zZg0L
+ KAFKA_LISTENER_NAME_REPLICATION_SSL_TRUSTSTORE_TYPE: PKCS12
+ KAFKA_LISTENER_NAME_REPLICATION_SSL_CLIENT_AUTH: required
+ KAFKA_LISTENER_NAME_CLIENT_SASL_ENABLED_MECHANISMS: OAUTHBEARER
+ KAFKA_LISTENER_NAME_CLIENT_OAUTHBEARER_SASL_JAAS_CONFIG: "org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required oauth.introspection.endpoint.uri=\"http://${SPRING_HOST:-spring}:8080/oauth2/introspect\" oauth.valid.issuer.uri=\"http://${SPRING_HOST:-spring}:8080\" oauth.client.id=\"kafkabroker\" oauth.client.secret=\"kafkabrokersecret\" oauth.access.token.is.jwt=\"false\" oauth.check.access.token.type=\"false\" unsecuredLoginStringClaim_sub=\"admin\" ;"
+ KAFKA_LISTENER_NAME_CLIENT_OAUTHBEARER_SASL_SERVER_CALLBACK_HANDLER_CLASS: io.strimzi.kafka.oauth.server.JaasServerOauthValidatorCallbackHandler
+ KAFKA_SUPER_USERS: User:CN=my-cluster-kafka,O=io.strimzi;User:CN=my-cluster-entity-operator,O=io.strimzi;User:CN=my-cluster-kafka-exporter,O=io.strimzi;User:kafkabroker
- #
- # Strimzi OAuth Configuration
- #
+ KAFKA_CONNECTIONS_MAX_REAUTH_MS: 3600000
- # Authentication config
- OAUTH_CLIENT_ID: "kafka"
- OAUTH_CLIENT_SECRET: "kafkasecret"
- OAUTH_TOKEN_ENDPOINT_URI: "http://${SPRING_HOST:-spring}:8080/oauth/token"
- OAUTH_SCOPE: "any"
-
- # Validation config
- OAUTH_INTROSPECTION_ENDPOINT_URI: "http://${SPRING_HOST:-spring}:8080/oauth/check_token"
- OAUTH_CHECK_ISSUER: "false"
- OAUTH_ACCESS_TOKEN_IS_JWT: "false"
- OAUTH_CHECK_ACCESS_TOKEN_TYPE: "false"
-
- # username extraction from JWT token claim
- OAUTH_USERNAME_CLAIM: user_name
- OAUTH_FALLBACK_USERNAME_CLAIM: client_id
- OAUTH_FALLBACK_USERNAME_PREFIX: client-account-
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_PRINCIPAL_BUILDER_CLASS: io.strimzi.kafka.oauth.server.OAuthKafkaPrincipalBuilder
# For start.sh script to know where the keycloak is listening
SPRING_HOST: ${SPRING_HOST:-spring}
diff --git a/examples/docker/kafka-oauth-strimzi/kafka/jwt.sh b/examples/docker/kafka-oauth-strimzi/kafka/jwt.sh
index 4e3a62d6..0bb55f83 100755
--- a/examples/docker/kafka-oauth-strimzi/kafka/jwt.sh
+++ b/examples/docker/kafka-oauth-strimzi/kafka/jwt.sh
@@ -8,7 +8,7 @@ fi
IFS='.' read -r -a PARTS <<< "$1"
echo "Head: "
-echo $(echo -n "${PARTS[0]}" | base64 -d 2>/dev/null)
+echo $(echo -n "${PARTS[0]}===" | base64 -d 2>/dev/null)
echo
echo "Payload: "
-echo $(echo -n "${PARTS[1]}" | base64 -d 2>/dev/null)
\ No newline at end of file
+echo $(echo -n "${PARTS[1]}===" | base64 -d 2>/dev/null)
\ No newline at end of file
diff --git a/examples/docker/pom.xml b/examples/docker/pom.xml
index eddebdca..b902a1a2 100644
--- a/examples/docker/pom.xml
+++ b/examples/docker/pom.xml
@@ -14,7 +14,7 @@
3.1.1
3.1.0
- 9.31
+ 9.37.2
1.0.0-SNAPSHOT
@@ -90,6 +90,19 @@
kafka-oauth-strimzi
- spring
+
+
+
+ java-17
+
+ [17,
+
+
+
+ kafka-oauth-strimzi
+ spring
+
+
+
diff --git a/examples/docker/spring/README.md b/examples/docker/spring/README.md
index fae5b19c..e4646ff7 100644
--- a/examples/docker/spring/README.md
+++ b/examples/docker/spring/README.md
@@ -2,6 +2,8 @@ Spring Authorization Server
===========================
This project builds and runs Spring Authorization Server as a docker container.
+The accompanying Kafka broker example is configured to use this authorization server for Kafka authentication (not for Kafka authorization).
+Client definitions are defined in code in [SecurityConfig.java](file://./src/main/java/io/strimzi/examples/spring/SecurityConfig.java) file.
Building
@@ -27,54 +29,118 @@ Using
Make sure to add `spring` entry to your `/etc/hosts` as explained [here](../README.md#preparing).
-Configure your Kafka broker with the following settings:
+Spring Authorization Server exposes multiple OAuth2 / OIDC endpoints. For our purposes the important ones are:
- oauth.introspection.endpoint.uri=http://spring:8080/oauth/check_token
- oauth.token.endpoint.uri=http://spring:8080/oauth/token
- oauth.client.id=kafka
- oauth.client.secret=kafkasecret
- oauth.scope=any
+* The token endpoint: http://spring:8080/oauth2/token
+* The signing keys endpoint: http://spring:8080/oauth2/jwks
+* The introspection endpoint: http://spring:8080/oauth2/introspect
+
+This example demonstrates using `client_credentials` grant with two different clients. First client `kafkaclient` produces
+JWT access tokens which can be validated by using the JWKS endpoint. The second client `kafkaclient2` produces opaque access tokens
+which can only be validated by using the Introspection endpoint.
+
+For example, in order to use the JWKS endpoint you could configure your Kafka broker listener with the following JAAS config parameters:
+
+ oauth.jwks.endpoint.uri=http://spring:8080/oauth2/jwks
+ oauth.jwks.ignore.key.use=true
+ oauth.valid.issuer.uri=http://spring:8080
+ oauth.check.access.token.type=false
+
+In order to use the Introspection endpoint you could configure your Kafka broker listener with the following JAAS config parameters:
+
+ oauth.introspect.endpoint.uri=http://spring:8080/oauth2/introspect
+ oauth.valid.issuer.uri=http://spring:8080
+ oauth.client.id=kafkabroker
+ oauth.client.secret=kafkabrokersecret
oauth.access.token.is.jwt=false
- oauth.check.issuer=false
- oauth.username.claim=user_name
- oauth.fallback.username.claim=client_id
- oauth.fallback.username.prefix=client-account-
+ oauth.check.access.token.type=false
-Spring authorization server by default uses opaque tokens (non-JWT) which means validation has to use the introspection endpoint.
-The example single-broker cluster uses OAuth2 for inter-broker communication as well as Kafka client communication which requires token endpoint to be configured.
-Authorization server's Token endpoint requires `scope` to be specified.
-The Introspection endpoint returns no information about issuer, so we have to disable that check.
-User information is sent depending on the type of authentication.
-If Kafka client sends a token obtained by a user using `password` grant, then `user_name` attribute
-of introspection endpoint response contains user's username. If Kafka client sends a token obtained in as the client by using `client_credentials` grant, then no `user_name` is set, but `client_id` is.
-By using username prefix we can quickly differentiate client accounts from user accounts - client_id with the same name as another username will become a different principal.
-We can then use Kafka Simple ACL Authorization to define ACL policies based on authenticated principal.
+### Token validation using the Introspection endpoint (JWT and opaque tokens)
+You can run the prepared Kafka example from parent directory (`examples/docker`) - make sure the Spring Authorization Server is up before running this command:
-You can run the prepared example from parent directory (`examples/docker`):
+ docker-compose -f compose.yml -f kafka-oauth-strimzi/compose-spring.yml up --build
- docker-compose -f compose.yml -f kafka-oauth-strimzi/compose-spring.yml up
-
-Check the logging output of the Spring container for the default user's password:
+You can authenticate as a Kafka client using a service account `kafkaclient` to obtain the access token:
- docker logs spring | grep "Using generated security password:"
+ curl http://spring:8080/oauth2/token -d "grant_type=client_credentials&scope=profile" -u kafkaclient:kafkaclientsecret
-Set the password as env var `PASSWORD`:
+Or if you want to automate:
- export PASSWORD=$(docker logs spring | grep "Using generated security password:" | awk '{print $NF}')
+ RESPONSE=$(curl -s http://spring:8080/oauth2/token -d "grant_type=client_credentials&scope=profile" -u kafkaclient:kafkaclientsecret)
+ ACCESS_TOKEN=$(echo "$RESPONSE" | sed -E -e "s#.*\"access_token\":\"([^\"]+)\",\".*#\1#")
+You can take the role of the Kafka broker to check if the token is valid:
-Configure your Kafka client by obtaining the token as user `user`:
+ curl http://spring:8080/oauth2/introspect -d "token=$ACCESS_TOKEN" -u kafkabroker:kafkabrokersecret
- curl spring:8080/oauth/token -d "grant_type=password&scope=read&username=user&password=$PASSWORD" -u kafka:kafkasecret
+In this case the returned access token was a JWT token which you can confirm by using the following helper script (from `examples/docker` directory):
+ ./kafka-oauth-strimzi/kafka/jwt.sh $ACCESS_TOKEN
-Use the following configuration options to configure your client with the refresh token:
- oauth.token.endpoint.uri=http://spring:8080/oauth/token
- oauth.refresh.token=$REFRESH_TOKEN
- oauth.scope=any
- oauth.access.token.is.jwt=false
+If you authenticate as client `kafkaclient2` the returned access token will be an opaque token (not JWT):
+
+ curl http://spring:8080/oauth2/token -d "grant_type=client_credentials&scope=profile" -u kafkaclient2:kafkaclient2secret
+
+Or if you want to automate:
+
+ RESPONSE2=$(curl -s http://spring:8080/oauth2/token -d "grant_type=client_credentials&scope=profile" -u kafkaclient2:kafkaclient2secret)
+ ACCESS_TOKEN2=$(echo "$RESPONSE2" | sed -E -e "s#.*\"access_token\":\"([^\"]+)\",\".*#\1#")
+
+You can again take the role of the Kafka broker to check if the token is valid:
+
+ curl http://spring:8080/oauth2/introspect -d "token=$ACCESS_TOKEN2" -u kafkabroker:kafkabrokersecret
+
+You can not introspect the opaque token as it's just a random string without any internal structure that could be parsed.
+Using the introspection endpoint is the only way to validate such a token.
+
+
+### Connecting with a Kafka client
+
+You can try both JWT access token and the opaque access token with a Kafka client that connects to the example Kafka broker.
+
+Start a new kafka container for the client:
+
+ docker run -ti --name kafka-client --network docker_default strimzi/example-kafka /bin/sh
+
+```
+$ echo 'security.protocol=SASL_PLAINTEXT
+sasl.mechanism=OAUTHBEARER
+sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
+oauth.client.id="kafkaclient" \
+oauth.client.secret="kafkaclientsecret" \
+oauth.token.endpoint.uri="http://spring:8080/oauth2/token" ;
+sasl.login.callback.handler.class=io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler' > $HOME/client.properties
+```
+
+Check which topics exist:
+
+ bin/kafka-topics.sh --bootstrap-server kafka:9092 --command-config ~/client.properties --list
+
+
+Run a console kafka producer:
+
+ bin/kafka-console-producer.sh --broker-list kafka:9092 --topic a_messages --producer.config=$HOME/client.properties
+ # type some messages then press CTRL-C or CTRL-D
+
+ bin/kafka-console-consumer.sh --bootstrap-server kafka:9092 --topic a_messages --from-beginning --consumer.config $HOME/client.properties --group a_consumer_group_1
+ # Press CTRL-C to exit
+
+You can also use `kafkaclient2` to see how it works with an opaque token.
+
+
+### Token validation using JWT key signature checking (JWT tokens only, opaque tokens not supported)
+
+There is a Kafka broker configuration example where the listener is configured to use the JWKS endpoint. Such a setup can only be used with JWT tokens.
+
+You can run the prepared Kafka example from parent directory (`examples/docker`) - make sure the Spring Authorization Server is up before running this command:
+
+ docker-compose -f compose.yml -f kafka-oauth-strimzi/compose-spring-jwt.yml up --build
+
+You can run the Kafka client using the same commands as in the previous chapter.
+If you try to authenticate the client as `kafkaclient2` the authentication will fail. While the Kafka client will successfully obtain the token,
+the validation of the token will fail on Kafka broker due to not being a JWT token.
\ No newline at end of file
diff --git a/examples/docker/spring/pom.xml b/examples/docker/spring/pom.xml
index 3b7e2bcd..deeb0f66 100644
--- a/examples/docker/spring/pom.xml
+++ b/examples/docker/spring/pom.xml
@@ -1,159 +1,80 @@
-
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.3
+
+
- 4.0.0
+ io.strimzi.oauth.docker
+ kafka-oauth-docker-spring
+ 1.0.0-SNAPSHOT
-
- org.springframework.boot
- spring-boot-starter-parent
- 2.6.15
-
-
+ kafka-oauth-docker-spring
+ OAuth2 Example using Spring Authorization Server
- io.strimzi.oauth.docker
- kafka-oauth-docker-spring
- 1.0.0-SNAPSHOT
+
+ 17
+ 3.1.0
+ 0.40.2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-authorization-server
+
-
- UTF-8
- 3.1.1
- 3.1.0
- 0.40.2
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
- 2.6.8
-
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ ${plugins.resources.version}
+
+
+ io.fabric8
+ docker-maven-plugin
+ ${fabric8-docker-plugin.version}
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ io.fabric8
+ docker-maven-plugin
+
+
+ build
+ pre-integration-test
+
+ build
+
+
+
+
+
+
+ strimzi/example-spring:latest
+
+
+
+
+
+
-
-
- Apache License, Version 2.0
- https://www.apache.org/licenses/LICENSE-2.0.txt
-
-
-
-
-
-
- org.springframework.security.oauth.boot
- spring-security-oauth2-autoconfigure
- ${spring.boot.oauth.version}
-
-
- com.fasterxml.jackson.core
- jackson-databind
- 2.15.2
-
-
- com.fasterxml.jackson.core
- jackson-core
- 2.15.2
-
-
- org.bouncycastle
- bcprov-jdk15on
- 1.69
-
-
- org.yaml
- snakeyaml
- 1.33
-
-
- org.springframework
- spring-beans
- 5.3.20
-
-
- org.springframework
- spring-context
- 5.3.20
-
-
- org.springframework.security
- spring-security-web
- 5.5.7
-
-
- org.springframework.security
- spring-security-crypto
- 5.5.7
-
-
-
-
-
-
- org.springframework.security.oauth.boot
- spring-security-oauth2-autoconfigure
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-resources-plugin
- ${plugins.resources.version}
-
-
- io.fabric8
- docker-maven-plugin
- ${fabric8-docker-plugin.version}
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
- repackage
-
-
-
-
-
- org.apache.maven.plugins
- maven-dependency-plugin
- ${plugins.dependency.version}
-
-
- analyze-deps
- verify
-
- analyze-only
-
-
-
-
-
- io.fabric8
- docker-maven-plugin
-
-
- build
- pre-integration-test
-
- build
-
-
-
-
-
-
- strimzi/example-spring:latest
-
-
-
-
-
-
-
diff --git a/examples/docker/spring/src/main/java/io/strimzi/examples/spring/SecurityConfig.java b/examples/docker/spring/src/main/java/io/strimzi/examples/spring/SecurityConfig.java
new file mode 100644
index 00000000..6927b977
--- /dev/null
+++ b/examples/docker/spring/src/main/java/io/strimzi/examples/spring/SecurityConfig.java
@@ -0,0 +1,143 @@
+package io.strimzi.examples.spring;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.oidc.OidcScopes;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
+import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
+import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
+import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.SecurityFilterChain;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.LinkedList;
+import java.util.UUID;
+
+@Configuration(proxyBeanMethods = false)
+@EnableWebSecurity
+public class SecurityConfig {
+
+ @Bean
+ @Order(1)
+ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
+
+ OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
+
+ // Enable OpenID Connect 1.0
+ // Provides User Info endpoint
+ http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
+ .oidc(Customizer.withDefaults());
+
+ // Accept access tokens for User Info and/or Client Registration
+ http.oauth2ResourceServer((resourceServer) -> resourceServer
+ .jwt(Customizer.withDefaults()));
+
+ return http.build();
+ }
+
+ @Bean
+ public UserDetailsService userDetailsService() {
+ UserDetails userDetails = User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build();
+
+ return new InMemoryUserDetailsManager(userDetails);
+ }
+
+ @Bean
+ public RegisteredClientRepository registeredClientRepository() {
+
+ LinkedList clients = new LinkedList<>();
+ clients.add(RegisteredClient.withId(UUID.randomUUID().toString())
+ .clientId("kafkaclient")
+ .clientSecret("{noop}kafkaclientsecret")
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+ .scope(OidcScopes.OPENID)
+ .scope(OidcScopes.PROFILE)
+ .build());
+
+ clients.add(RegisteredClient.withId(UUID.randomUUID().toString())
+ .clientId("kafkaclient2")
+ .clientSecret("{noop}kafkaclient2secret")
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+ .scope(OidcScopes.OPENID)
+ .scope(OidcScopes.PROFILE)
+ // The tokens for 'kafkaclient2' should be opaque, requiring the use of introspect endpoint
+ .tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE).build())
+ .build());
+
+ clients.add(RegisteredClient.withId(UUID.randomUUID().toString())
+ .clientId("kafkabroker")
+ .clientSecret("{noop}kafkabrokersecret")
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+ .build());
+
+ return new InMemoryRegisteredClientRepository(clients);
+ }
+
+ @Bean
+ // Required for the JWKS endpoint
+ public JWKSource jwkSource() {
+ KeyPair keyPair = generateRsaKey();
+ RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
+ RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
+ RSAKey rsaKey = new RSAKey.Builder(publicKey)
+ .privateKey(privateKey)
+ .keyID(UUID.randomUUID().toString())
+ .build();
+ JWKSet jwkSet = new JWKSet(rsaKey);
+ return new ImmutableJWKSet<>(jwkSet);
+ }
+
+ private static KeyPair generateRsaKey() {
+ KeyPair keyPair;
+ try {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(2048);
+ keyPair = keyPairGenerator.generateKeyPair();
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException(ex);
+ }
+ return keyPair;
+ }
+
+ @Bean
+ // Required for the User Info endpoint
+ public JwtDecoder jwtDecoder(JWKSource jwkSource) {
+ return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
+ }
+
+ @Bean
+ public AuthorizationServerSettings authorizationServerSettings() {
+ return AuthorizationServerSettings.builder().build();
+ }
+}
diff --git a/examples/docker/spring/src/main/java/io/strimzi/examples/spring/SimpleAuthorizationServerApplication.java b/examples/docker/spring/src/main/java/io/strimzi/examples/spring/SimpleAuthorizationServerApplication.java
deleted file mode 100644
index fc6e3ebe..00000000
--- a/examples/docker/spring/src/main/java/io/strimzi/examples/spring/SimpleAuthorizationServerApplication.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package io.strimzi.examples.spring;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
-
-@EnableAuthorizationServer
-@SpringBootApplication
-public class SimpleAuthorizationServerApplication {
- public static void main(String[] args) {
- SpringApplication.run(SimpleAuthorizationServerApplication.class, args);
- }
-}
diff --git a/examples/docker/spring/src/main/java/io/strimzi/examples/spring/SpringAuthorizationServerApplication.java b/examples/docker/spring/src/main/java/io/strimzi/examples/spring/SpringAuthorizationServerApplication.java
new file mode 100644
index 00000000..ca61a544
--- /dev/null
+++ b/examples/docker/spring/src/main/java/io/strimzi/examples/spring/SpringAuthorizationServerApplication.java
@@ -0,0 +1,15 @@
+package io.strimzi.examples.spring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Import;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
+
+@SpringBootApplication
+public class SpringAuthorizationServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringAuthorizationServerApplication.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/examples/docker/spring/src/main/resources/application.yml b/examples/docker/spring/src/main/resources/application.yml
index f9a3a1ee..c889b87b 100644
--- a/examples/docker/spring/src/main/resources/application.yml
+++ b/examples/docker/spring/src/main/resources/application.yml
@@ -1,7 +1,11 @@
-security:
- oauth2:
- authorization:
- check-token-access: isAuthenticated()
- client:
- client-id: kafka
- client-secret: kafkasecret
+server:
+ port: 8080
+
+logging:
+ level:
+ root: INFO
+ org.springframework.web: INFO
+ org.springframework.security: DEBUG
+ org.springframework.security.authentication: DEBUG
+ org.springframework.security.oauth2: DEBUG
+# org.springframework.boot.autoconfigure: DEBUG
diff --git a/examples/docker/spring/src/test/java/io/strimzi/examples/spring/SpringAuthorizationServerApplicationTests.java b/examples/docker/spring/src/test/java/io/strimzi/examples/spring/SpringAuthorizationServerApplicationTests.java
new file mode 100644
index 00000000..859ce405
--- /dev/null
+++ b/examples/docker/spring/src/test/java/io/strimzi/examples/spring/SpringAuthorizationServerApplicationTests.java
@@ -0,0 +1,13 @@
+package io.strimzi.examples.spring;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SpringAuthorizationServerApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/examples/docker/spring/test-spring.sh b/examples/docker/spring/test-spring.sh
index 530bcb1e..597d94ed 100755
--- a/examples/docker/spring/test-spring.sh
+++ b/examples/docker/spring/test-spring.sh
@@ -6,7 +6,7 @@ docker run -d --name spring strimzi/example-spring
for i in {1..10}
do
sleep 1
- RESULT=$(docker logs spring | grep "Started SimpleAuthorizationServerApplication")
+ RESULT=$(docker logs spring | grep "Started SpringAuthorizationServerApplication")
if [ "$RESULT" != "" ]; then
docker rm -f spring
exit 0
diff --git a/pom.xml b/pom.xml
index 9a99aa7b..8c98a7bd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -103,14 +103,14 @@
3.3.0
4.7.3
1.6.3
- 3.6.1
+ 3.7.0
2.15.3
2.15.3
2.9.0
4.13.2
1.7.36
3.12.4
- 9.31
+ 9.37.2
diff --git a/testsuite/keycloak-authz-tests/src/main/java/io/strimzi/testsuite/oauth/authz/Common.java b/testsuite/keycloak-authz-tests/src/main/java/io/strimzi/testsuite/oauth/authz/Common.java
index 32426d75..40739bc1 100644
--- a/testsuite/keycloak-authz-tests/src/main/java/io/strimzi/testsuite/oauth/authz/Common.java
+++ b/testsuite/keycloak-authz-tests/src/main/java/io/strimzi/testsuite/oauth/authz/Common.java
@@ -46,7 +46,7 @@
import static io.strimzi.kafka.oauth.common.OAuthAuthenticator.loginWithClientSecret;
import static io.strimzi.kafka.oauth.common.OAuthAuthenticator.urlencode;
-@SuppressFBWarnings({"THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", "THROWS_METHOD_THROWS_RUNTIMEEXCEPTION"})
+@SuppressFBWarnings({"THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", "THROWS_METHOD_THROWS_RUNTIMEEXCEPTION", "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"})
public class Common {
private static final Logger log = LoggerFactory.getLogger(Common.class);
diff --git a/testsuite/mock-oauth-server/pom.xml b/testsuite/mock-oauth-server/pom.xml
index 2d6095c9..f5220fa7 100644
--- a/testsuite/mock-oauth-server/pom.xml
+++ b/testsuite/mock-oauth-server/pom.xml
@@ -21,7 +21,7 @@
../..
- 4.3.8
+ 4.4.8
1.3.14
3.8.1
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 21ca0bc6..94d587e0 100644
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -41,7 +41,7 @@
2.22.1
0.40.2
- 1.17.3
+ 1.19.6
4.13.2
4.7.0
2.15.2