Skip to content

Commit

Permalink
Merge pull request quarkusio#16362 from sberyozkin/test_security_jwt
Browse files Browse the repository at this point in the history
Add test-security-jwt and test-security-oidc modules
  • Loading branch information
sberyozkin authored Apr 19, 2021
2 parents 0cd6553 + 851db5f commit 809b135
Show file tree
Hide file tree
Showing 21 changed files with 552 additions and 9 deletions.
10 changes: 10 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2419,6 +2419,16 @@
<artifactId>quarkus-test-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security-jwt</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security-oidc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
Expand Down
78 changes: 77 additions & 1 deletion docs/src/main/asciidoc/security-jwt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,7 @@ Please see link:security-openid-connect-client#token-propagation[Token Propagati
[[integration-testing]]
== Testing

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

If you 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[OpenId Connect Bearer Token Integration testing] `Wiremock` section but only change the `application.properties` to use MP JWT configuration properties instead:
Expand All @@ -827,6 +828,7 @@ mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
smallrye.jwt.sign.key.location=privateKey.jwk
----

[[integration-testing-keycloak]]
=== 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:
Expand All @@ -838,6 +840,7 @@ mp.jwt.verify.publickey.location=${keycloak.url}/realms/quarkus/protocol/openid-
mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
----

[[integration-testing-public-key]]
=== Local Public Key

You can use the same approach as described in the link:security-openid-connect#integration-testing[OpenId Connect Bearer Token Integration testing] `Local Public Key` section but only change the `application.properties` to use MP JWT configuration properties instead:
Expand All @@ -852,9 +855,82 @@ mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
smallrye.jwt.sign.key.location=privateKey.pem
----

[[integration-testing-security-annotation]]
=== TestSecurity annotation

Please see link:security-testing#testing-security[TestingSecurity Annotation] section how to do simple tests with the `TestSecurity` annotation.
Add the following dependency:
[source,xml]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security-jwt</artifactId>
<scope>test</scope>
</dependency>
----

and write a test code like this one:

[source, java]
----
package io.quarkus.it.keycloak;
import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.Test;
import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.SecurityAttribute;
import io.quarkus.test.security.TestSecurity;
import io.restassured.RestAssured;
@QuarkusTest
@TestHTTPEndpoint(ProtectedResource.class)
public class TestSecurityAuthTest {
@Test
@TestSecurity(user = "userJwt", roles = "viewer", attributes = {
@SecurityAttribute(key = "claim.email", value = "[email protected]")
})
public void testJwtWithDummyUser() {
RestAssured.when().get("test-security-jwt").then()
.body(is("userJwt:viewer:[email protected]"));
}
}
----

where `ProtectedResource` class may look like this:

[source, java]
----
package io.quarkus.it.keycloak;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
@Path("/web-app")
@Authenticated
public class ProtectedResource {
@Inject
JsonWebToken accessToken;
@GET
@Path("test-security-jwt")
public String testSecurityJwt() {
return accessToken.getName() + ":" + accessToken.getGroups().iterator().next()
+ ":" + accessToken.getClaim("email");
}
}
----

Note that `TestSecurity` `user` property is returned as `JsonWebToken.getName()` and `roles` property - as `JsonWebToken.getGroups()`. Additionally, any `SecurityAttribute` with the key starting from `claim.` will be returned as a token claim value, for example, `claim.email` will support `JsonWebToken.getClaim("email")`, etc.

[[generate-jwt-tokens]]
== Generate JWT tokens with SmallRye JWT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@ Additionally, `OidcWiremockTestResource` set token issuer and audience to `https

`OidcWiremockTestResource` can be used to emulate all OpenId Connect providers.

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

If you work with Keycloak then you can test against a live Keycloak instance by adding the following dependency:
Expand Down Expand Up @@ -733,9 +734,20 @@ public class CodeFlowAuthorizationTest {
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.

[[integration-testing-security-annotation]]
=== TestSecurity annotation

Please see link:security-testing#testing-security[TestingSecurity Annotation] section how to do simple tests with the `TestSecurity` annotation.
Please see link:security-jwt#integration-testing-security-annotation[Use TestingSecurity with injected JsonWebToken] section for more information about using `@TestSecurity` for testing the `web-app` application endpoint code which depends on the injected ID and access `JsonWebToken`.

The only difference is that the following dependency should be used when testing the `web-app` applications:
[source,xml]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security-oidc</artifactId>
<scope>test</scope>
</dependency>
----

== Configuration Reference

Expand Down
14 changes: 13 additions & 1 deletion docs/src/main/asciidoc/security-openid-connect.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,7 @@ public class BearerTokenAuthorizationTest {
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.

[[integration-testing-public-key]]
=== Local Public Key

You can also use a local inlined public key for testing your `quarkus-oidc` `service` applications:
Expand All @@ -674,9 +675,20 @@ 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.

[[integration-testing-security-annotation]]
=== TestSecurity annotation

Please see link:security-testing#testing-security[TestingSecurity Annotation] section how to do simple tests with the `TestSecurity` annotation.
Please see link:security-jwt#integration-testing-security-annotation[Use TestingSecurity with injected JsonWebToken] section for more information about using `@TestSecurity` for testing the `service` application endpoint code which depends on the injected `JsonWebToken`.

The only difference is that the following dependency should be used when testing the `service` applications:
[source,xml]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security-oidc</artifactId>
<scope>test</scope>
</dependency>
----

== References

Expand Down
4 changes: 3 additions & 1 deletion docs/src/main/asciidoc/security-testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ This will run the test with an identity with the given username and roles. Note
disable authorization while also providing an identity to run the test under, which can be useful if the endpoint expects an
identity to be present.

See link:security-openid-connect#integration-testing-security-annotation[OpenId Connect Bearer Token Integration testing], link:security-openid-connect-web-authentication#integration-testing-security-annotation[OpenId Connect Authorization Code Flow Integration testing] and link:security-jwt#integration-testing-security-annotation[SmallRye JWT Integration testing] for more details about testing the endpoint code which depends on the injected `JsonWebToken`.

[WARNING]
====
The feature is only available for `@QuarkusTest` and will **not** work on a `@NativeImageTest`.
Expand All @@ -96,7 +98,7 @@ for example by setting `quarkus.http.auth.basic=true` or `%test.quarkus.http.aut
== Use Wiremock for Integration Testing

You can also use Wiremock to mock the authorization OAuth2 and OIDC services:
See link:security-oauth2#integration-testing[OAuth2 Integration testing], link:security-openid-connect#integration-testing[OpenId Connect Bearer Token Integration testing], link:security-openid-connect-web-authentication#integration-testing[OpenId Connect Authorization Code Flow Integration testing] and link:security-jwt#integration-testing[SmallRye JWT Integration testing] for more details.
See link:security-oauth2#integration-testing[OAuth2 Integration testing], link:security-openid-connect#integration-testing-wiremock[OpenId Connect Bearer Token Integration testing], link:security-openid-connect-web-authentication#integration-testing-wiremock[OpenId Connect Authorization Code Flow Integration testing] and link:security-jwt#integration-testing-wiremock[SmallRye JWT Integration testing] for more details.

== References

Expand Down
2 changes: 1 addition & 1 deletion integration-tests/oidc-code-flow/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security</artifactId>
<artifactId>quarkus-test-security-oidc</artifactId>
<scope>test</scope>
</dependency>
<!-- test dependencies -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,18 @@ public class ProtectedResource {
SecurityContext securityContext;

@GET
@Path("sec")
public String hello() {
@Path("test-security")
public String testSecurity() {
return securityContext.getUserPrincipal().getName();
}

@GET
@Path("test-security-oidc")
public String testSecurityJwt() {
return idToken.getName() + ":" + idToken.getGroups().iterator().next()
+ ":" + idToken.getClaim("email");
}

@GET
@Path("configMetadataIssuer")
public String configMetadataIssuer() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.SecurityAttribute;
import io.quarkus.test.security.TestSecurity;
import io.restassured.RestAssured;

Expand All @@ -16,8 +17,17 @@ public class TestSecurityLazyAuthTest {
@Test
@TestSecurity(user = "user1", roles = "viewer")
public void testWithDummyUser() {
RestAssured.when().get("sec").then()
RestAssured.when().get("test-security").then()
.body(is("user1"));
}

@Test
@TestSecurity(user = "userOidc", roles = "viewer", attributes = {
@SecurityAttribute(key = "claim.email", value = "[email protected]")
})
public void testJwtWithDummyUser() {
RestAssured.when().get("test-security-oidc").then()
.body(is("userOidc:viewer:[email protected]"));
}

}
5 changes: 5 additions & 0 deletions integration-tests/oidc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
<artifactId>quarkus-test-keycloak-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security-oidc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.quarkus.it.keycloak;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;

import org.eclipse.microprofile.jwt.JsonWebToken;

import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;

@Path("/web-app")
@Authenticated
public class ProtectedJwtResource {

@Inject
SecurityIdentity identity;

@Inject
JsonWebToken accessToken;

@Context
SecurityContext securityContext;

@GET
@Path("test-security")
public String testSecurity() {
return securityContext.getUserPrincipal().getName();
}

@GET
@Path("test-security-jwt")
public String testSecurityJwt() {
return accessToken.getName() + ":" + accessToken.getGroups().iterator().next()
+ ":" + accessToken.getClaim("email");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.it.keycloak;

import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.SecurityAttribute;
import io.quarkus.test.security.TestSecurity;
import io.restassured.RestAssured;

@QuarkusTest
@TestHTTPEndpoint(ProtectedJwtResource.class)
public class TestSecurityLazyAuthTest {

@Test
@TestSecurity(user = "user1", roles = "viewer")
public void testWithDummyUser() {
RestAssured.when().get("test-security").then()
.body(is("user1"));
}

@Test
@TestSecurity(user = "userJwt", roles = "viewer", attributes = {
@SecurityAttribute(key = "claim.email", value = "[email protected]")
})
public void testJwtWithDummyUser() {
RestAssured.when().get("test-security-jwt").then()
.body(is("userJwt:viewer:[email protected]"));
}

}
5 changes: 5 additions & 0 deletions integration-tests/smallrye-jwt-token-propagation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
<artifactId>keycloak-core</artifactId>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security-jwt</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.quarkus.it.keycloak;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;

import org.eclipse.microprofile.jwt.JsonWebToken;

import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;

@Path("/web-app")
@Authenticated
public class ProtectedJwtResource {

@Inject
SecurityIdentity identity;

@Inject
JsonWebToken accessToken;

@Context
SecurityContext securityContext;

@GET
@Path("test-security")
public String testSecurity() {
return securityContext.getUserPrincipal().getName();
}

@GET
@Path("test-security-jwt")
public String testSecurityJwt() {
return accessToken.getName() + ":" + accessToken.getGroups().iterator().next()
+ ":" + accessToken.getClaim("email");
}
}
Loading

0 comments on commit 809b135

Please sign in to comment.