Skip to content

Commit

Permalink
Update Keycloak Test Module to support code flow tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Apr 6, 2021
1 parent 13d339a commit 38f253f
Show file tree
Hide file tree
Showing 20 changed files with 316 additions and 476 deletions.
15 changes: 14 additions & 1 deletion docs/src/main/asciidoc/security-jwt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,16 @@ mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
smallrye.jwt.sign.key.location=privateKey.jwk
----

It will ensure that the `smallrye-jwt` remote key resolution code also works as expected.
=== Keycloak

If you work with Keycloak and configure `mp.jwt.verify.publickey.location` to point to HTTPS or HTTP based JsonWebKey (JWK) set then you can use the same approach as described in the link:security-openid-connect#integration-testing-keycloak[OpenId Connect Bearer Token Integration testing] `Keycloak` section but only change the `application.properties` to use MP JWT configuration properties instead:

[source, properties]
----
# keycloak.url is set by OidcWiremockTestResource
mp.jwt.verify.publickey.location=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs
mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
----

=== Local Public Key

Expand All @@ -839,6 +848,10 @@ mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
smallrye.jwt.sign.key.location=privateKey.pem
----

=== TestSecurity annotation

Please see link:security-testing#testing-security[TestingSecurity Annotation] section how to do simple tests with the `TestSecurity` annotation.

[[generate-jwt-tokens]]
== Generate JWT tokens with SmallRye JWT

Expand Down
33 changes: 20 additions & 13 deletions docs/src/main/asciidoc/security-openid-connect-client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ Using `private_key_jwt` or `private_key_jwt` authentication methods ensures that
[[integration-testing-oidc-client]]
=== Testing

Add the following dependencies to your test project:
Start by adding the following dependencies to your test project:

[source,xml]
----
Expand All @@ -340,18 +340,22 @@ Add the following dependencies to your test project:
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
----

[[integration-testing-wiremock]]
==== Wiremock

Add the following dependencies to your test project:

[source,xml]
----
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<scope>test</scope>
</dependency>
----
Expand Down Expand Up @@ -400,8 +404,7 @@ public class KeycloakRealmResourceManager implements QuarkusTestResourceLifecycl
Map<String, String> conf = new HashMap<>();
conf.put("quarkus.oidc-client.auth-server-url", server.baseUrl());
conf.put("keycloak-url", server.baseUrl());
conf.put("keycloak.url", server.baseUrl());
return conf;
}
Expand All @@ -425,7 +428,7 @@ Set `application.properties`, for example:
quarkus.oidc-client.auth-server-url=${keycloak.url}
quarkus.oidc-client.discovery-enabled=false
quarkus.oidc-client.token-path=/tokens
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.client-id=quarkus-service-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=password
quarkus.oidc-client.grant-options.password.username=alice
Expand All @@ -434,6 +437,10 @@ quarkus.oidc-client.grant-options.password.password=alice

and finally write the test code. Given the Wiremock-based resource above, the first test invocation should return `access_token_1` access token which will expire in 4 seconds. Use `awaitility` to wait for about 5 seconds, and now the next test invocation should return `access_token_2` access token which confirms the expired `access_token_1` access token has been refreshed.

=== Keycloak

If you work with Keycloak then you can use the same approach as described in the link:security-openid-connect#integration-testing-keycloak[OpenId Connect Bearer Token Integration testing] `Keycloak` section.

== Token endpoint configuration

By default the token endpoint address is discovered by adding a `/.well-known/openid-configuration` path to the configured `quarkus.oidc-client.auth-server-url`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,15 +576,10 @@ Please see link:security-openid-connect-client#token-propagation[Token Propagati
[[integration-testing]]
== Testing

Add the following dependencies to your test project:
Start by adding the following dependencies to your test project:

[source,xml]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-oidc-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
Expand All @@ -601,10 +596,19 @@ Add the following dependencies to your test project:
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
----

[[integration-testing-wiremock]]
=== Wiremock

Add the following dependency:

[source,xml]
----
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-oidc-server</artifactId>
<scope>test</scope>
</dependency>
----

Expand Down Expand Up @@ -666,9 +670,66 @@ public class CodeFlowAuthorizationTest {
----

`OidcWiremockTestResource` recognizes `alice` and `admin` users. The user `alice` has the `user` role only by default - it can be customized with a `quarkus.test.oidc.token.user-roles` system property. The user `admin` has the `user` and `admin` roles by default - it can be customized with a `quarkus.test.oidc.token.admin-roles` system property.

Additionally, `OidcWiremockTestResource` set token issuer and audience to `https://service.example.com` which can be customized with `quarkus.test.oidc.token.issuer` and `quarkus.test.oidc.token.audience` system properties.

`OidcWiremockTestResource` will be enhanced going forward to support more complex authorization code flow test scenarios.
`OidcWiremockTestResource` can be used to emulate all OpenId Connect providers.

=== Keycloak

If you work with Keycloak then you can test against a live Keycloak instance by adding the following dependency:

[source,xml]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-keycloak-server</artifactId>
<scope>test</scope>
</dependency>
----

and configure `maven.surefire.plugin` as follows:

[source,xml]
----
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<!-- or, alternativey, configure 'keycloak.version' -->
<keycloak.docker.image>${keycloak.docker.image}</keycloak.docker.image>
<!--
Disable HTTPS if required:
<keycloak.use.https>false</keycloak.use.https>
-->
</systemPropertyVariables>
</configuration>
</plugin>
----

(and similarly `maven.failsafe.plugin` when testing in native image).

And now set the configuration and write the test code the same way as it is described in the <<integration-testing-wiremock, Wiremock>> section above.
The only difference is the name of `QuarkusTestResource`:

[source, java]
----
import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
@QuarkusTest
@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
public class CodeFlowAuthorizationTest {
}
----

`KeycloakTestResourceLifecycleManager` registers `alice` and `admin` users. The user `alice` has the `user` role only by default - it can be customized with a `keycloak.token.user-roles` system property. The user `admin` has the `user` and `admin` roles by default - it can be customized with a `keycloak.token.admin-roles` system property.

By default, `KeycloakTestResourceLifecycleManager` uses HTTPS to initialize a Keycloak instance which can be disabled with `keycloak.use.https=false`.
Default realm name is `quarkus` and client id - `quarkus-web-app` - set `keycloak.realm` and `keycloak.web-app.client` system properties to customize the values if needed.

=== TestSecurity annotation

Please see link:security-testing#testing-security[TestingSecurity Annotation] section how to do simple tests with the `TestSecurity` annotation.

== Configuration Reference

Expand Down
116 changes: 108 additions & 8 deletions docs/src/main/asciidoc/security-openid-connect.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -434,26 +434,39 @@ Please see link:security-openid-connect-client#token-propagation[Token Propagati
[[integration-testing]]
== Testing

=== Wiremock

Add the following dependencies to your test project:
Start by adding the following dependencies to your test project:

[source,xml]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-oidc-server</artifactId>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<exclusions>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
----

[[integration-testing-wiremock]]
=== Wiremock

Add the following dependencies to your test project:

[source,xml]
----
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-oidc-server</artifactId>
<scope>test</scope>
</dependency>
----

Expand Down Expand Up @@ -516,6 +529,89 @@ public class BearerTokenAuthorizationTest {
Testing your `quarkus-oidc` `service` application with `OidcWiremockTestResource` provides the best coverage as even the communication channel is tested against the Wiremock HTTP stubs.
`OidcWiremockTestResource` will be enhanced going forward to support more complex Bearer token test scenarios.

[[integration-testing-keycloak]]
=== Keycloak

If you work with Keycloak then you can test against a live Keycloak instance by adding the following dependency:

[source,xml]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-keycloak-server</artifactId>
<scope>test</scope>
</dependency>
----

and configure `maven.surefire.plugin` as follows:

[source,xml]
----
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<!-- or, alternativey, configure 'keycloak.version' -->
<keycloak.docker.image>${keycloak.docker.image}</keycloak.docker.image>
<!--
Disable HTTPS if required:
<keycloak.use.https>false</keycloak.use.https>
-->
</systemPropertyVariables>
</configuration>
</plugin>
----

(and similarly `maven.failsafe.plugin` when testing in native image).

Prepare the REST test endpoint, set `application.properties`, for example:

[source, properties]
----
# keycloak.url is set by KeycloakTestResourceLifecycleManager
quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.client-id=quarkus-service-app
quarkus.oidc.credentials=secret
quarkus.oidc.application-type=service
----

and finally write the test code, for example:

[source, java]
----
import static io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager.getAccessToken;
import static org.hamcrest.Matchers.equalTo;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
import io.restassured.RestAssured;
@QuarkusTest
@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
public class BearerTokenAuthorizationTest {
@Test
public void testBearerToken() {
RestAssured.given().auth().oauth2(getAccessToken("alice"))))
.when().get("/api/users/preferredUserName")
.then()
.statusCode(200)
// the test endpoint returns the name extracted from the injected SecurityIdentity Principal
.body("userName", equalTo("alice"));
}
}
----

`KeycloakTestResourceLifecycleManager` registers `alice` and `admin` users. The user `alice` has the `user` role only by default - it can be customized with a `keycloak.token.user-roles` system property. The user `admin` has the `user` and `admin` roles by default - it can be customized with a `keycloak.token.admin-roles` system property.

By default, `KeycloakTestResourceLifecycleManager` uses HTTPS to initialize a Keycloak instance which can be disabled with `keycloak.use.https=false`.
Default realm name is `quarkus` and client id - `quarkus-service-app` - set `keycloak.realm` and `keycloak.service.client` system properties to customize the values if needed.

=== Local Public Key

You can also use a local inlined public key for testing your `quarkus-oidc` `service` applications:
Expand All @@ -532,6 +628,10 @@ copy `privateKey.pem` from the `integration-tests/oidc-tenancy` in the `main` Qu

This approach provides a more limited coverage compared to the Wiremock approach - for example, the remote communication code is not covered.

=== TestSecurity annotation

Please see link:security-testing#testing-security[TestingSecurity Annotation] section how to do simple tests with the `TestSecurity` annotation.

== References

* https://www.keycloak.org/documentation.html[Keycloak Documentation]
Expand Down
Loading

0 comments on commit 38f253f

Please sign in to comment.