diff --git a/docs/src/main/asciidoc/security-testing.adoc b/docs/src/main/asciidoc/security-testing.adoc index 31c92f70d360e..4726cf34b4bf3 100644 --- a/docs/src/main/asciidoc/security-testing.adoc +++ b/docs/src/main/asciidoc/security-testing.adoc @@ -9,6 +9,7 @@ include::./attributes.adoc[] This document describes how to test Quarkus Security. +[[configuring-user-information]] == Configuring User Information You can use link:security-properties[quarkus-elytron-security-properties-file] for testing security. This supports both embedding user info in `application.properties` and standalone properties files. diff --git a/docs/src/main/asciidoc/security.adoc b/docs/src/main/asciidoc/security.adoc index 6a1cd600fc200..e45a19b21d801 100644 --- a/docs/src/main/asciidoc/security.adoc +++ b/docs/src/main/asciidoc/security.adoc @@ -96,6 +96,7 @@ For example, `quarkus-oidc` uses its own `IdentityProvider` to convert a token t If you use `Basic` or `Form` HTTP-based authentication then you have to add an `IdentityProvider` which can convert a user name and password to `SecurityIdentity`. See link:security-jpa[JPA IdentityProvider] and link:security-jdbc[JDBC IdentityProvider] for more information. +You can also use link:security-testing#configuring-user-information[User Properties IdentityProvider] for testing. == Combining Authentication Mechanisms diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcJsonWebTokenProducer.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcJsonWebTokenProducer.java index 6df9def69bb99..34dfbefb2f183 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcJsonWebTokenProducer.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcJsonWebTokenProducer.java @@ -8,6 +8,7 @@ import org.eclipse.microprofile.jwt.Claims; import org.eclipse.microprofile.jwt.JsonWebToken; +import org.jboss.logging.Logger; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.consumer.InvalidJwtException; import org.jose4j.jwt.consumer.JwtConsumerBuilder; @@ -24,6 +25,7 @@ @Alternative @RequestScoped public class OidcJsonWebTokenProducer { + private static final Logger LOG = Logger.getLogger(OidcJsonWebTokenProducer.class); @Inject SecurityIdentity identity; @@ -77,6 +79,7 @@ private JsonWebToken getTokenCredential(Class type) { return new OidcJwtCallerPrincipal(jwtClaims, credential); } String tokenType = type == AccessTokenCredential.class ? "access" : "ID"; - throw new OIDCException("Current identity is not associated with an " + tokenType + " token"); + LOG.tracef("Current identity is not associated with an %s token", tokenType); + return new NullJsonWebToken(); } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTokenCredentialProducer.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTokenCredentialProducer.java index f29e5f85b622f..2ee0952e4880d 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTokenCredentialProducer.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTokenCredentialProducer.java @@ -4,6 +4,8 @@ import javax.enterprise.inject.Produces; import javax.inject.Inject; +import org.jboss.logging.Logger; + import io.quarkus.oidc.AccessTokenCredential; import io.quarkus.oidc.IdTokenCredential; import io.quarkus.oidc.OIDCException; @@ -13,7 +15,7 @@ @RequestScoped public class OidcTokenCredentialProducer { - + private static final Logger LOG = Logger.getLogger(OidcTokenCredentialProducer.class); @Inject SecurityIdentity identity; @@ -25,19 +27,34 @@ public class OidcTokenCredentialProducer { @Produces @RequestScoped IdTokenCredential currentIdToken() { - return identity.getCredential(IdTokenCredential.class); + IdTokenCredential cred = identity.getCredential(IdTokenCredential.class); + if (cred == null) { + LOG.trace("IdTokenCredential is null"); + cred = new IdTokenCredential(); + } + return cred; } @Produces @RequestScoped AccessTokenCredential currentAccessToken() { - return identity.getCredential(AccessTokenCredential.class); + AccessTokenCredential cred = identity.getCredential(AccessTokenCredential.class); + if (cred == null) { + LOG.trace("AccessTokenCredential is null"); + cred = new AccessTokenCredential(); + } + return cred; } @Produces @RequestScoped RefreshToken currentRefreshToken() { - return identity.getCredential(RefreshToken.class); + RefreshToken cred = identity.getCredential(RefreshToken.class); + if (cred == null) { + LOG.trace("RefreshToken is null"); + cred = new RefreshToken(); + } + return cred; } /** diff --git a/integration-tests/oidc/pom.xml b/integration-tests/oidc/pom.xml index 2e4079086fab8..c616f218a9207 100644 --- a/integration-tests/oidc/pom.xml +++ b/integration-tests/oidc/pom.xml @@ -53,8 +53,11 @@ awaitility test - + + io.quarkus + quarkus-elytron-security-properties-file-deployment + io.quarkus quarkus-oidc-deployment diff --git a/integration-tests/oidc/src/main/java/io/quarkus/it/keycloak/UsersResource.java b/integration-tests/oidc/src/main/java/io/quarkus/it/keycloak/UsersResource.java index 14bead7f636a6..2ea179a688153 100644 --- a/integration-tests/oidc/src/main/java/io/quarkus/it/keycloak/UsersResource.java +++ b/integration-tests/oidc/src/main/java/io/quarkus/it/keycloak/UsersResource.java @@ -1,7 +1,5 @@ package io.quarkus.it.keycloak; -import java.security.Principal; - import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.ws.rs.GET; @@ -11,6 +9,8 @@ import org.eclipse.microprofile.jwt.JsonWebToken; +import io.quarkus.security.identity.SecurityIdentity; + /** * @author Pedro Igor */ @@ -18,14 +18,14 @@ public class UsersResource { @Inject - Principal identity; + SecurityIdentity identity; @GET @Path("/me") @RolesAllowed("user") @Produces(MediaType.APPLICATION_JSON) public User principalName() { - return new User(identity.getName()); + return new User(identity.getPrincipal().getName()); } @GET @@ -33,7 +33,7 @@ public User principalName() { @RolesAllowed("user") @Produces(MediaType.APPLICATION_JSON) public User preferredUserName() { - return new User(((JsonWebToken) identity).getClaim("preferred_username")); + return new User(((JsonWebToken) identity.getPrincipal()).getClaim("preferred_username")); } public static class User { diff --git a/integration-tests/oidc/src/main/resources/application.properties b/integration-tests/oidc/src/main/resources/application.properties index 423e0a93117e1..2a425123ef58e 100644 --- a/integration-tests/oidc/src/main/resources/application.properties +++ b/integration-tests/oidc/src/main/resources/application.properties @@ -3,5 +3,11 @@ quarkus.oidc.auth-server-url=${keycloak.ssl.url}/realms/quarkus/ quarkus.oidc.client-id=quarkus-app quarkus.oidc.credentials.secret=secret quarkus.oidc.token.principal-claim=email -quarkus.http.cors=true quarkus.oidc.tls.verification=none +quarkus.http.cors=true + +quarkus.http.auth.basic=true +quarkus.security.users.embedded.enabled=true +quarkus.security.users.embedded.plain-text=true +quarkus.security.users.embedded.users.alice=password +quarkus.security.users.embedded.roles.alice=user diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java index d3f0e8e6fd3f0..a6e266bddf58b 100644 --- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java +++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java @@ -5,7 +5,9 @@ import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.equalTo; +import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Base64; import java.util.concurrent.TimeUnit; import org.hamcrest.Matchers; @@ -58,6 +60,17 @@ public void testSecureAccessSuccessCustomPrincipal() { } } + @Test + public void testBasicAuth() { + byte[] basicAuthBytes = "alice:password".getBytes(StandardCharsets.UTF_8); + RestAssured.given() + .header("Authorization", "Basic " + Base64.getEncoder().encodeToString(basicAuthBytes)) + .when().get("/api/users/me") + .then() + .statusCode(200) + .body("userName", equalTo("alice")); + } + @Test public void testSecureAccessSuccessPreferredUsername() { for (String username : Arrays.asList("alice", "jdoe", "admin")) {