diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/BearerTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/BearerTest.java
new file mode 100644
index 00000000000..1aacbe3239d
--- /dev/null
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/BearerTest.java
@@ -0,0 +1,545 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2022 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wildfly.security.http.oidc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.ALLOWED_ORIGIN;
+import static org.wildfly.security.http.oidc.Oidc.OIDC_NAME;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.http.HttpStatus;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.wildfly.common.iteration.CodePointIterator;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
+
+import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
+import com.gargoylesoftware.htmlunit.HttpMethod;
+import com.gargoylesoftware.htmlunit.TextPage;
+import com.gargoylesoftware.htmlunit.WebClient;
+
+import io.restassured.RestAssured;
+import okhttp3.mockwebserver.Dispatcher;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.QueueDispatcher;
+import okhttp3.mockwebserver.RecordedRequest;
+
+/**
+ * Tests for bearer only auth.
+ *
+ * @author Farah Juma
+ */
+public class BearerTest extends OidcBaseTest {
+
+ private static boolean DIRECT_ACCESS_GRANT_ENABLED = true;
+ private static final String BEARER_ONLY_CLIENT_ID = "bearer-client";
+ private static final String CORS_CLIENT_ID = "cors-client";
+ private static final String SECURED_ENDPOINT = "/service/secured";
+ private static final String SECURED_PAGE_TEXT = "Welcome to the secured page!";
+ private static final String WRONG_PASSWORD = "WRONG_PASSWORD";
+
+ protected HttpServerAuthenticationMechanismFactory oidcFactory;
+
+ private enum BearerAuthType {
+ BEARER,
+ QUERY_PARAM,
+ BASIC
+ }
+
+ @BeforeClass
+ public static void startTestContainers() throws Exception {
+ assumeTrue("Docker isn't available, OIDC tests will be skipped", isDockerAvailable());
+ KEYCLOAK_CONTAINER = new KeycloakContainer();
+ KEYCLOAK_CONTAINER.start();
+ sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET,
+ CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, DIRECT_ACCESS_GRANT_ENABLED, BEARER_ONLY_CLIENT_ID,
+ CORS_CLIENT_ID));
+ client = new MockWebServer();
+ client.start(CLIENT_PORT);
+ }
+
+ private static Dispatcher createAppBearerResponse(HttpServerAuthenticationMechanism mechanism, String clientPageText,
+ String expectedError, String originHeader) {
+ return new Dispatcher() {
+ @Override
+ public MockResponse dispatch(RecordedRequest recordedRequest) throws InterruptedException {
+ String path = recordedRequest.getPath();
+ if (path.contains("/" + CLIENT_APP + SECURED_ENDPOINT)) {
+ try {
+ String authorizationHeader = recordedRequest.getHeader("Authorization");
+ TestingHttpServerRequest request;
+ if (originHeader != null) {
+ Map> requestHeaders = new HashMap<>();
+ if (authorizationHeader != null) {
+ requestHeaders.put("Authorization", Collections.singletonList(authorizationHeader));
+ }
+ requestHeaders.put(CorsHeaders.ORIGIN, Collections.singletonList(originHeader));
+ request = new TestingHttpServerRequest(requestHeaders, new URI(recordedRequest.getRequestUrl().toString()), recordedRequest.getMethod());
+ } else {
+ request = new TestingHttpServerRequest(authorizationHeader == null ? null : new String[]{authorizationHeader},
+ new URI(recordedRequest.getRequestUrl().toString()));
+ }
+ mechanism.evaluateRequest(request);
+ TestingHttpServerResponse response = request.getResponse();
+ int statusCode = response.getStatusCode();
+ if (expectedError != null) {
+ assertTrue(response.getAuthenticateHeader().contains(expectedError));
+ return new MockResponse().setResponseCode(statusCode);
+ } else if (statusCode > 300) {
+ // unexpected error
+ return new MockResponse().setResponseCode(statusCode);
+ }
+ return new MockResponse().setBody(clientPageText);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return new MockResponse()
+ .setBody("");
+ }
+ };
+ }
+
+ @AfterClass
+ public static void generalCleanup() throws Exception {
+ if (KEYCLOAK_CONTAINER != null) {
+ RestAssured
+ .given()
+ .auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
+ .when()
+ .delete(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms/" + TEST_REALM).then().statusCode(204);
+ KEYCLOAK_CONTAINER.stop();
+ }
+ if (client != null) {
+ client.shutdown();
+ }
+ }
+
+ @Test
+ public void testSucessfulAuthenticationWithAuthServerUrl() throws Exception {
+ performBearerAuthentication(getOidcConfigurationInputStream(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT);
+ }
+
+ @Test
+ public void testSucessfulAuthenticationWithProviderUrl() throws Exception {
+ performBearerAuthentication(getOidcConfigurationInputStreamWithProviderUrl(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT);
+ }
+
+ @Test
+ public void testWrongToken() throws Exception {
+ String wrongToken = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJrNmhQYTdHdmdrajdFdlhLeFAtRjFLZkNSUk85Q3kwNC04YzFqTERWOXNrIn0.eyJleHAiOjE2NTc2NjExODksImlhdCI6MTY1NzY2MTEyOSwianRpIjoiZThiZGQ3MWItYTA2OC00Mjc3LTkyY2UtZWJkYmU2MDVkMzBhIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOlsibXlyZWFsbS1yZWFsbSIsIm1hc3Rlci1yZWFsbSIsImFjY291bnQiXSwic3ViIjoiZTliOGE2OWItM2RlNy00ZDYzLWFjYmItMmYyNTRhMDM1MjVkIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdC13ZWJhcHAiLCJzZXNzaW9uX3N0YXRlIjoiMTQ1OTdhMmUtOGM1Ni00YzkwLWI3NjAtZWFjYzczNWU1Zjc1IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJjcmVhdGUtcmVhbG0iLCJkZWZhdWx0LXJvbGVzLW1hc3RlciIsIm9mZmxpbmVfYWNjZXNzIiwiYWRtaW4iLCJ1bWFfYXV0aG9yaXphdGlvbiIsInVzZXIiXX0sInJlc291cmNlX2FjY2VzcyI6eyJteXJlYWxtLXJlYWxtIjp7InJvbGVzIjpbInZpZXctcmVhbG0iLCJ2aWV3LWlkZW50aXR5LXByb3ZpZGVycyIsIm1hbmFnZS1pZGVudGl0eS1wcm92aWRlcnMiLCJpbXBlcnNvbmF0aW9uIiwiY3JlYXRlLWNsaWVudCIsIm1hbmFnZS11c2VycyIsInF1ZXJ5LXJlYWxtcyIsInZpZXctYXV0aG9yaXphdGlvbiIsInF1ZXJ5LWNsaWVudHMiLCJxdWVyeS11c2VycyIsIm1hbmFnZS1ldmVudHMiLCJtYW5hZ2UtcmVhbG0iLCJ2aWV3LWV2ZW50cyIsInZpZXctdXNlcnMiLCJ2aWV3LWNsaWVudHMiLCJtYW5hZ2UtYXV0aG9yaXphdGlvbiIsIm1hbmFnZS1jbGllbnRzIiwicXVlcnktZ3JvdXBzIl19LCJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsidmlldy1yZWFsbSIsInZpZXctaWRlbnRpdHktcHJvdmlkZXJzIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsImltcGVyc29uYXRpb24iLCJjcmVhdGUtY2xpZW50IiwibWFuYWdlLXVzZXJzIiwicXVlcnktcmVhbG1zIiwidmlldy1hdXRob3JpemF0aW9uIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LXVzZXJzIiwibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1yZWFsbSIsInZpZXctZXZlbnRzIiwidmlldy11c2VycyIsInZpZXctY2xpZW50cyIsIm1hbmFnZS1hdXRob3JpemF0aW9uIiwibWFuYWdlLWNsaWVudHMiLCJxdWVyeS1ncm91cHMiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjE0NTk3YTJlLThjNTYtNGM5MC1iNzYwLWVhY2M3MzVlNWY3NSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxpY2UifQ.hVj6SG-aTcDYhifdljpiBcz4ShCHej3h_4-82rgX0s_oJ-En68Cqt-_DgJLtMdr6dW_gQFFCPYBJfEGvZ8L6b_TwzbdLxyrQrKTOpeG0KJ8VAFlbWum9B1vvES_sav1Gj1sQHlV621EaLISYz7pnknuQEvrB7liJFRRjN9SH30AsAJy6nmKTDHGZ6Eegkveqd_7POaKfsHS3Z0-SGyL5GClXv9yZ1l5Y4VH-rrMUztLPCFH5bJ319-m-7sgizvV-C2EcM37XVAtPRVQbJNRW0wVmLEJKMuLYVnjS1Wn5eU_qnBvVMEaENNG3TzNd6b4YmxMFHFf9tnkb3wkDzdrRTA";
+ performBearerAuthentication(getOidcConfigurationInputStreamWithProviderUrl(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT, wrongToken, BearerAuthType.BEARER);
+ }
+
+ @Test
+ public void testInvalidToken() throws Exception {
+ performBearerAuthentication(getOidcConfigurationInputStreamWithProviderUrl(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT, "INVALID_TOKEN", BearerAuthType.BEARER);
+ }
+
+ @Test
+ public void testNoTokenProvidedWithAuthServerUrl() throws Exception {
+ accessAppWithoutToken(SECURED_ENDPOINT, getOidcConfigurationInputStream());
+ }
+
+ @Test
+ public void testNoTokenProvidedWithProviderUrl() throws Exception {
+ accessAppWithoutToken(SECURED_ENDPOINT, getOidcConfigurationInputStreamWithProviderUrl());
+ }
+
+ @Test
+ public void testTokenProvidedBearerOnlyNotSet() throws Exception {
+ // ensure we still make use of the bearer token
+ performBearerAuthentication(getOidcConfigurationInputStreamWithoutBearerOnly(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT);
+ }
+
+ @Test
+ public void testTokenNotProvidedBearerOnlyNotSet() throws Exception {
+ // ensure the regular OIDC flow takes place
+ accessAppWithoutToken("", getRegularOidcConfigurationInputStream());
+ }
+
+ /**
+ * Tests that pass the bearer token to use via an access_token query param.
+ */
+
+ @Test
+ public void testValidTokenViaQueryParameter() throws Exception {
+ performBearerAuthentication(getOidcConfigurationInputStreamWithProviderUrl(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT, null, BearerAuthType.QUERY_PARAM);
+ }
+
+ @Test
+ public void testWrongTokenViaQueryParameter() throws Exception {
+ String wrongToken = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJrNmhQYTdHdmdrajdFdlhLeFAtRjFLZkNSUk85Q3kwNC04YzFqTERWOXNrIn0.eyJleHAiOjE2NTc2NjExODksImlhdCI6MTY1NzY2MTEyOSwianRpIjoiZThiZGQ3MWItYTA2OC00Mjc3LTkyY2UtZWJkYmU2MDVkMzBhIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOlsibXlyZWFsbS1yZWFsbSIsIm1hc3Rlci1yZWFsbSIsImFjY291bnQiXSwic3ViIjoiZTliOGE2OWItM2RlNy00ZDYzLWFjYmItMmYyNTRhMDM1MjVkIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdC13ZWJhcHAiLCJzZXNzaW9uX3N0YXRlIjoiMTQ1OTdhMmUtOGM1Ni00YzkwLWI3NjAtZWFjYzczNWU1Zjc1IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJjcmVhdGUtcmVhbG0iLCJkZWZhdWx0LXJvbGVzLW1hc3RlciIsIm9mZmxpbmVfYWNjZXNzIiwiYWRtaW4iLCJ1bWFfYXV0aG9yaXphdGlvbiIsInVzZXIiXX0sInJlc291cmNlX2FjY2VzcyI6eyJteXJlYWxtLXJlYWxtIjp7InJvbGVzIjpbInZpZXctcmVhbG0iLCJ2aWV3LWlkZW50aXR5LXByb3ZpZGVycyIsIm1hbmFnZS1pZGVudGl0eS1wcm92aWRlcnMiLCJpbXBlcnNvbmF0aW9uIiwiY3JlYXRlLWNsaWVudCIsIm1hbmFnZS11c2VycyIsInF1ZXJ5LXJlYWxtcyIsInZpZXctYXV0aG9yaXphdGlvbiIsInF1ZXJ5LWNsaWVudHMiLCJxdWVyeS11c2VycyIsIm1hbmFnZS1ldmVudHMiLCJtYW5hZ2UtcmVhbG0iLCJ2aWV3LWV2ZW50cyIsInZpZXctdXNlcnMiLCJ2aWV3LWNsaWVudHMiLCJtYW5hZ2UtYXV0aG9yaXphdGlvbiIsIm1hbmFnZS1jbGllbnRzIiwicXVlcnktZ3JvdXBzIl19LCJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsidmlldy1yZWFsbSIsInZpZXctaWRlbnRpdHktcHJvdmlkZXJzIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsImltcGVyc29uYXRpb24iLCJjcmVhdGUtY2xpZW50IiwibWFuYWdlLXVzZXJzIiwicXVlcnktcmVhbG1zIiwidmlldy1hdXRob3JpemF0aW9uIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LXVzZXJzIiwibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1yZWFsbSIsInZpZXctZXZlbnRzIiwidmlldy11c2VycyIsInZpZXctY2xpZW50cyIsIm1hbmFnZS1hdXRob3JpemF0aW9uIiwibWFuYWdlLWNsaWVudHMiLCJxdWVyeS1ncm91cHMiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjE0NTk3YTJlLThjNTYtNGM5MC1iNzYwLWVhY2M3MzVlNWY3NSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxpY2UifQ.hVj6SG-aTcDYhifdljpiBcz4ShCHej3h_4-82rgX0s_oJ-En68Cqt-_DgJLtMdr6dW_gQFFCPYBJfEGvZ8L6b_TwzbdLxyrQrKTOpeG0KJ8VAFlbWum9B1vvES_sav1Gj1sQHlV621EaLISYz7pnknuQEvrB7liJFRRjN9SH30AsAJy6nmKTDHGZ6Eegkveqd_7POaKfsHS3Z0-SGyL5GClXv9yZ1l5Y4VH-rrMUztLPCFH5bJ319-m-7sgizvV-C2EcM37XVAtPRVQbJNRW0wVmLEJKMuLYVnjS1Wn5eU_qnBvVMEaENNG3TzNd6b4YmxMFHFf9tnkb3wkDzdrRTA";
+ performBearerAuthentication(getOidcConfigurationInputStreamWithProviderUrl(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT, wrongToken, BearerAuthType.QUERY_PARAM);
+ }
+
+ @Test
+ public void testInvalidTokenViaQueryParameter() throws Exception {
+ performBearerAuthentication(getOidcConfigurationInputStreamWithProviderUrl(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT, "INVALID_TOKEN", BearerAuthType.QUERY_PARAM);
+ }
+
+ /**
+ * Tests that rely on obtaining the bearer token to use from credentials obtained from basic auth.
+ */
+
+ @Test
+ public void testBasicAuthenticationWithoutEnableBasicAuthSet() throws Exception {
+ accessAppWithoutToken(SECURED_ENDPOINT, getOidcConfigurationInputStream(), BearerAuthType.BASIC, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD);
+ }
+
+ @Test
+ public void testBasicAuthenticationWithoutEnableBasicAuthSetAndWithoutBearerOnlySet() throws Exception {
+ // ensure the regular OIDC flow takes place
+ accessAppWithoutToken("", getRegularOidcConfigurationInputStream(), BearerAuthType.BASIC, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD);
+ }
+
+ @Test
+ public void testValidCredentialsBasicAuthentication() throws Exception {
+ performBearerAuthentication(getOidcConfigurationInputStreamWithEnableBasicAuth(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT, null, BearerAuthType.BASIC);
+ }
+
+ @Test
+ public void testInvalidCredentialsBasicAuthentication() throws Exception {
+ accessAppWithoutToken(SECURED_ENDPOINT, getOidcConfigurationInputStreamWithEnableBasicAuth(), BearerAuthType.BASIC, KeycloakConfiguration.ALICE, WRONG_PASSWORD);
+ }
+
+ /**
+ * Tests that simulate CORS preflight requests.
+ */
+
+ @Test
+ public void testCorsRequestWithEnableCors() throws Exception {
+ performBearerAuthenticationCorsRequest(getOidcConfigurationInputStreamWithEnableCors(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT, null, ALLOWED_ORIGIN);
+ }
+
+ @Test
+ public void testCorsRequestWithEnableCorsWithWrongToken() throws Exception {
+ String wrongToken = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJrNmhQYTdHdmdrajdFdlhLeFAtRjFLZkNSUk85Q3kwNC04YzFqTERWOXNrIn0.eyJleHAiOjE2NTc2NjExODksImlhdCI6MTY1NzY2MTEyOSwianRpIjoiZThiZGQ3MWItYTA2OC00Mjc3LTkyY2UtZWJkYmU2MDVkMzBhIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOlsibXlyZWFsbS1yZWFsbSIsIm1hc3Rlci1yZWFsbSIsImFjY291bnQiXSwic3ViIjoiZTliOGE2OWItM2RlNy00ZDYzLWFjYmItMmYyNTRhMDM1MjVkIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdC13ZWJhcHAiLCJzZXNzaW9uX3N0YXRlIjoiMTQ1OTdhMmUtOGM1Ni00YzkwLWI3NjAtZWFjYzczNWU1Zjc1IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJjcmVhdGUtcmVhbG0iLCJkZWZhdWx0LXJvbGVzLW1hc3RlciIsIm9mZmxpbmVfYWNjZXNzIiwiYWRtaW4iLCJ1bWFfYXV0aG9yaXphdGlvbiIsInVzZXIiXX0sInJlc291cmNlX2FjY2VzcyI6eyJteXJlYWxtLXJlYWxtIjp7InJvbGVzIjpbInZpZXctcmVhbG0iLCJ2aWV3LWlkZW50aXR5LXByb3ZpZGVycyIsIm1hbmFnZS1pZGVudGl0eS1wcm92aWRlcnMiLCJpbXBlcnNvbmF0aW9uIiwiY3JlYXRlLWNsaWVudCIsIm1hbmFnZS11c2VycyIsInF1ZXJ5LXJlYWxtcyIsInZpZXctYXV0aG9yaXphdGlvbiIsInF1ZXJ5LWNsaWVudHMiLCJxdWVyeS11c2VycyIsIm1hbmFnZS1ldmVudHMiLCJtYW5hZ2UtcmVhbG0iLCJ2aWV3LWV2ZW50cyIsInZpZXctdXNlcnMiLCJ2aWV3LWNsaWVudHMiLCJtYW5hZ2UtYXV0aG9yaXphdGlvbiIsIm1hbmFnZS1jbGllbnRzIiwicXVlcnktZ3JvdXBzIl19LCJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsidmlldy1yZWFsbSIsInZpZXctaWRlbnRpdHktcHJvdmlkZXJzIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsImltcGVyc29uYXRpb24iLCJjcmVhdGUtY2xpZW50IiwibWFuYWdlLXVzZXJzIiwicXVlcnktcmVhbG1zIiwidmlldy1hdXRob3JpemF0aW9uIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LXVzZXJzIiwibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1yZWFsbSIsInZpZXctZXZlbnRzIiwidmlldy11c2VycyIsInZpZXctY2xpZW50cyIsIm1hbmFnZS1hdXRob3JpemF0aW9uIiwibWFuYWdlLWNsaWVudHMiLCJxdWVyeS1ncm91cHMiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjE0NTk3YTJlLThjNTYtNGM5MC1iNzYwLWVhY2M3MzVlNWY3NSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxpY2UifQ.hVj6SG-aTcDYhifdljpiBcz4ShCHej3h_4-82rgX0s_oJ-En68Cqt-_DgJLtMdr6dW_gQFFCPYBJfEGvZ8L6b_TwzbdLxyrQrKTOpeG0KJ8VAFlbWum9B1vvES_sav1Gj1sQHlV621EaLISYz7pnknuQEvrB7liJFRRjN9SH30AsAJy6nmKTDHGZ6Eegkveqd_7POaKfsHS3Z0-SGyL5GClXv9yZ1l5Y4VH-rrMUztLPCFH5bJ319-m-7sgizvV-C2EcM37XVAtPRVQbJNRW0wVmLEJKMuLYVnjS1Wn5eU_qnBvVMEaENNG3TzNd6b4YmxMFHFf9tnkb3wkDzdrRTA";
+ performBearerAuthenticationCorsRequest(getOidcConfigurationInputStreamWithEnableCors(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT, wrongToken, ALLOWED_ORIGIN);
+ }
+
+ @Test
+ public void testCorsRequestWithEnableCorsWithInvalidToken() throws Exception {
+ performBearerAuthenticationCorsRequest(getOidcConfigurationInputStreamWithEnableCors(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT, "INVALID_TOKEN", ALLOWED_ORIGIN);
+ }
+
+ @Test
+ public void testCorsRequestWithEnableCorsInvalidOrigin() throws Exception {
+ performBearerAuthenticationCorsRequest(getOidcConfigurationInputStreamWithEnableCors(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT, null, "http://invalidorigin");
+ }
+
+ @Test
+ public void testCorsRequestWithoutEnableCors() throws Exception {
+ performBearerAuthenticationCorsRequest(getOidcConfigurationInputStream(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ SECURED_PAGE_TEXT, null, ALLOWED_ORIGIN);
+ }
+
+ private void performBearerAuthentication(InputStream oidcConfig, String endpoint, String username, String password, String clientPageText) throws Exception {
+ performBearerAuthentication(oidcConfig, endpoint, username, password, clientPageText, null, BearerAuthType.BEARER);
+ }
+
+ private void performBearerAuthentication(InputStream oidcConfig, String endpoint, String username, String password,
+ String clientPageText, String bearerToken, BearerAuthType bearerAuthType) throws Exception {
+ try {
+ Map props = new HashMap<>();
+ OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig);
+ assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls());
+
+ OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration);
+ oidcFactory = new OidcMechanismFactory(oidcClientContext);
+ HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler());
+
+ if (bearerToken != null) { // going to pass an invalid token
+ client.setDispatcher(createAppBearerResponse(mechanism, clientPageText, "invalid_token", null));
+ } else {
+ client.setDispatcher(createAppBearerResponse(mechanism, clientPageText, null, null));
+ }
+
+ URI requestUri;
+ WebClient webClient = getWebClient();
+ switch (bearerAuthType) {
+ case QUERY_PARAM:
+ if (bearerToken == null) {
+ // obtain a bearer token and then try accessing the endpoint with a query param specified
+ requestUri = new URI(getClientUrl() + endpoint + "?access_token="
+ + KeycloakConfiguration.getAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl(), TEST_REALM, username,
+ password, CLIENT_ID, CLIENT_SECRET));
+ } else {
+ // try accessing the endpoint with the given bearer token specified using a query param
+ requestUri = new URI(getClientUrl() + endpoint + "?access_token=" + bearerToken);
+ }
+ break;
+ case BASIC:
+ webClient.addRequestHeader("Authorization",
+ "Basic " + CodePointIterator.ofString(username + ":" + password).asUtf8().base64Encode().drainToString());
+ requestUri = new URI(getClientUrl() + endpoint);
+ break;
+ default:
+ if (bearerToken == null) {
+ // obtain a bearer token and then try accessing the endpoint with the Authorization header specified
+ webClient.addRequestHeader("Authorization", "Bearer " + KeycloakConfiguration.getAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl(), TEST_REALM, username,
+ password, CLIENT_ID, CLIENT_SECRET));
+ } else {
+ // try accessing the endpoint with the given bearer token specified using the Authorization header
+ webClient.addRequestHeader("Authorization", "Bearer " + bearerToken);
+ }
+ requestUri = new URI(getClientUrl() + endpoint);
+ }
+
+ if (bearerToken == null) {
+ TextPage page = webClient.getPage(requestUri.toURL());
+ assertEquals(HttpStatus.SC_OK, page.getWebResponse().getStatusCode());
+ assertTrue(page.getContent().contains(clientPageText));
+ } else {
+ try {
+ webClient.getPage(requestUri.toURL());
+ fail("Expected exception not thrown");
+ } catch (FailingHttpStatusCodeException e) {
+ assertEquals(HttpStatus.SC_UNAUTHORIZED, e.getStatusCode());
+ }
+ }
+ } finally {
+ client.setDispatcher(new QueueDispatcher());
+ }
+ }
+
+ private void performBearerAuthenticationCorsRequest(InputStream oidcConfig, String endpoint, String username, String password,
+ String clientPageText, String bearerToken, String originHeader) throws Exception {
+ try {
+ Map props = new HashMap<>();
+ OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig);
+ assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls());
+
+ OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration);
+ oidcFactory = new OidcMechanismFactory(oidcClientContext);
+ HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler());
+
+ URI requestUri = new URI(getClientUrl() + endpoint);
+
+ // simulate preflight request
+ Map> requestHeaders = new HashMap<>();
+ requestHeaders.put(CorsHeaders.ORIGIN, Collections.singletonList(originHeader));
+ requestHeaders.put(CorsHeaders.ACCESS_CONTROL_REQUEST_HEADERS, Collections.singletonList("authorization"));
+ requestHeaders.put(CorsHeaders.ACCESS_CONTROL_REQUEST_METHOD, Collections.singletonList(HttpMethod.GET.name()));
+ TestingHttpServerRequest request = new TestingHttpServerRequest(requestHeaders, requestUri, HttpMethod.OPTIONS.name());
+ mechanism.evaluateRequest(request);
+ TestingHttpServerResponse response = request.getResponse();
+
+ if (oidcClientConfiguration.isCors()) {
+ assertTrue(Boolean.valueOf(response.getFirstResponseHeaderValue(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)));
+ assertEquals("authorization", response.getFirstResponseHeaderValue(CorsHeaders.ACCESS_CONTROL_ALLOW_HEADERS));
+ assertEquals(HttpMethod.GET.name(), response.getFirstResponseHeaderValue(CorsHeaders.ACCESS_CONTROL_ALLOW_METHODS));
+ assertEquals(originHeader, response.getFirstResponseHeaderValue(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
+
+ if (bearerToken != null) { // going to pass an invalid token
+ client.setDispatcher(createAppBearerResponse(mechanism, clientPageText, "invalid_token", originHeader));
+ } else {
+ client.setDispatcher(createAppBearerResponse(mechanism, clientPageText, null, originHeader));
+ }
+
+ WebClient webClient = getWebClient();
+ webClient.addRequestHeader(CorsHeaders.ORIGIN, originHeader);
+ if (bearerToken == null) {
+ webClient.addRequestHeader("Authorization", "Bearer " + KeycloakConfiguration.getAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl(), TEST_REALM, username,
+ password, CORS_CLIENT_ID, CLIENT_SECRET));
+ } else {
+ webClient.addRequestHeader("Authorization", "Bearer " + bearerToken);
+ }
+ if (bearerToken == null) {
+ try {
+ TextPage page = webClient.getPage(requestUri.toURL());
+ assertEquals(HttpStatus.SC_OK, page.getWebResponse().getStatusCode());
+ assertTrue(page.getContent().contains(clientPageText));
+ } catch (FailingHttpStatusCodeException e) {
+ assertFalse(originHeader.equals(ALLOWED_ORIGIN));
+ assertEquals(HttpStatus.SC_FORBIDDEN, e.getStatusCode());
+ }
+ } else {
+ try {
+ webClient.getPage(requestUri.toURL());
+ fail("Expected exception not thrown");
+ } catch (FailingHttpStatusCodeException e) {
+ assertEquals(HttpStatus.SC_UNAUTHORIZED, e.getStatusCode());
+ }
+ }
+ } else {
+ assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusCode());
+ if (oidcClientConfiguration.getRealm() != null) {
+ // if we have a keycloak realm configured, its name should appear in the challenge
+ assertEquals("Bearer realm=\"" + TEST_REALM + "\"", response.getAuthenticateHeader());
+ } else {
+ assertEquals("Bearer", response.getAuthenticateHeader());
+ }
+ }
+ } finally {
+ client.setDispatcher(new QueueDispatcher());
+ }
+ }
+
+ private void accessAppWithoutToken(String endpoint, InputStream oidcConfigInputStream) throws Exception {
+ accessAppWithoutToken(endpoint, oidcConfigInputStream, null, null, null);
+ }
+
+ private void accessAppWithoutToken(String endpoint, InputStream oidcConfigInputStream, BearerAuthType bearerAuthType, String username, String password) throws Exception {
+ Map props = new HashMap<>();
+ OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfigInputStream);
+ assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls());
+
+ OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration);
+ oidcFactory = new OidcMechanismFactory(oidcClientContext);
+ HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler());
+
+ URI requestUri = new URI(getClientUrl() + endpoint);
+ TestingHttpServerRequest request;
+ if (bearerAuthType == BearerAuthType.BASIC) {
+ request = new TestingHttpServerRequest(new String[] {"Basic "
+ + CodePointIterator.ofString(username + ":" + password).asUtf8().base64Encode().drainToString()}, requestUri);
+ } else {
+ request = new TestingHttpServerRequest(null, requestUri); // no bearer token specified
+ }
+ mechanism.evaluateRequest(request);
+ TestingHttpServerResponse response = request.getResponse();
+
+ if (oidcClientConfiguration.isBearerOnly() || oidcClientConfiguration.isEnableBasicAuth()) {
+ assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusCode());
+ String authenticateHeader = response.getAuthenticateHeader();
+ if ((bearerAuthType == BearerAuthType.BASIC) && password.equals(WRONG_PASSWORD)) {
+ assertTrue(authenticateHeader.startsWith("Bearer error=\"" + "no_token" + "\""));
+ assertTrue(authenticateHeader.contains("error_description"));
+ assertTrue(authenticateHeader.contains(String.valueOf(HttpStatus.SC_UNAUTHORIZED)));
+ } else if (oidcClientConfiguration.getRealm() != null) {
+ // if we have a keycloak realm configured, its name should appear in the challenge
+ assertEquals("Bearer realm=\"" + TEST_REALM + "\"", authenticateHeader);
+ } else {
+ assertEquals("Bearer", authenticateHeader);
+ }
+ } else {
+ // no token provided and bearer-only is not configured, should end up in the OIDC flow
+ assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getStatusCode());
+ assertEquals(Status.NO_AUTH, request.getResult());
+ try {
+ // browser login should succeed
+ client.setDispatcher(createAppResponse(mechanism, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT));
+ TextPage page = loginToKeycloak(KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, requestUri, response.getLocation(),
+ response.getCookies()).click();
+ assertTrue(page.getContent().contains(CLIENT_PAGE_TEXT));
+ } finally {
+ client.setDispatcher(new QueueDispatcher());
+ }
+ }
+ }
+
+ private InputStream getOidcConfigurationInputStream() {
+ return getOidcConfigurationInputStream(KEYCLOAK_CONTAINER.getAuthServerUrl());
+ }
+
+ private InputStream getOidcConfigurationInputStream(String authServerUrl) {
+ String oidcConfig = "{\n" +
+ " \"realm\" : \"" + TEST_REALM + "\",\n" +
+ " \"resource\" : \"" + BEARER_ONLY_CLIENT_ID + "\",\n" +
+ " \"auth-server-url\" : \"" + authServerUrl + "\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"bearer-only\" : \"true\"\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InputStream getOidcConfigurationInputStreamWithProviderUrl() {
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + BEARER_ONLY_CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"bearer-only\" : \"true\"\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InputStream getOidcConfigurationInputStreamWithoutBearerOnly() {
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + BEARER_ONLY_CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\"\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InputStream getRegularOidcConfigurationInputStream() {
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"credentials\" : {\n" +
+ " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " }\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InputStream getOidcConfigurationInputStreamWithEnableBasicAuth() {
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"enable-basic-auth\" : \"true\",\n" +
+ " \"credentials\" : {\n" +
+ " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " }\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InputStream getOidcConfigurationInputStreamWithEnableCors() {
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + BEARER_ONLY_CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"enable-cors\" : \"true\",\n" +
+ " \"bearer-only\" : \"true\"\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+}
\ No newline at end of file
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java
index 0e80a70cf59..5dfa052ed28 100644
--- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java
@@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.keycloak.representations.AccessTokenResponse;
@@ -45,6 +46,7 @@ public class KeycloakConfiguration {
public static final String ALICE_PASSWORD = "alice123+";
private static final String BOB = "bob";
private static final String BOB_PASSWORD = "bob123+";
+ public static final String ALLOWED_ORIGIN = "http://somehost";
/**
* Configure RealmRepresentation as follows:
@@ -62,20 +64,52 @@ public static RealmRepresentation getRealmRepresentation(final String realmName,
return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp);
}
+ public static RealmRepresentation getRealmRepresentation(final String realmName, String clientId, String clientSecret,
+ String clientHostName, int clientPort, String clientApp,
+ boolean directAccessGrantEnabled, String bearerOnlyClientId,
+ String corsClientId) {
+ return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, bearerOnlyClientId, corsClientId);
+ }
+
public static String getAdminAccessToken(String authServerUrl) {
+ return getAdminAccessToken(authServerUrl, "master", KeycloakContainer.KEYCLOAK_ADMIN_USER,
+ KeycloakContainer.KEYCLOAK_ADMIN_PASSWORD, "admin-cli");
+ }
+
+ public static String getAdminAccessToken(String authServerUrl, String realmName, String username, String password, String clientId) {
return RestAssured
.given()
.param("grant_type", "password")
- .param("username", KeycloakContainer.KEYCLOAK_ADMIN_USER)
- .param("password", KeycloakContainer.KEYCLOAK_ADMIN_PASSWORD)
- .param("client_id", "admin-cli")
+ .param("username", username)
+ .param("password", password)
+ .param("client_id", clientId)
.when()
- .post(authServerUrl + "/realms/master/protocol/openid-connect/token")
+ .post(authServerUrl + "/realms/" + realmName + "/protocol/openid-connect/token")
+ .as(AccessTokenResponse.class).getToken();
+ }
+
+ public static String getAccessToken(String authServerUrl, String realmName, String username, String password, String clientId, String clientSecret) {
+ return RestAssured
+ .given()
+ .param("grant_type", "password")
+ .param("username", username)
+ .param("password", password)
+ .param("client_id", clientId)
+ .param("client_secret", clientSecret)
+ .when()
+ .post(authServerUrl + "/realms/" + realmName + "/protocol/openid-connect/token")
.as(AccessTokenResponse.class).getToken();
}
private static RealmRepresentation createRealm(String name, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp) {
+ return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null);
+ }
+
+ private static RealmRepresentation createRealm(String name, String clientId, String clientSecret,
+ String clientHostName, int clientPort, String clientApp,
+ boolean directAccessGrantEnabled, String bearerOnlyClientId,
+ String corsClientId) {
RealmRepresentation realm = new RealmRepresentation();
realm.setRealm(name);
@@ -94,14 +128,27 @@ private static RealmRepresentation createRealm(String name, String clientId, Str
realm.getRoles().getRealm().add(new RoleRepresentation("user", null, false));
realm.getRoles().getRealm().add(new RoleRepresentation("admin", null, false));
- realm.getClients().add(createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp));
+ realm.getClients().add(createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled));
+
+ if (bearerOnlyClientId != null) {
+ realm.getClients().add(createBearerOnlyClient(bearerOnlyClientId));
+ }
+
+ if (corsClientId != null) {
+ realm.getClients().add(createWebAppClient(corsClientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, ALLOWED_ORIGIN));
+ }
realm.getUsers().add(createUser(ALICE, ALICE_PASSWORD, Arrays.asList(USER_ROLE, ADMIN_ROLE)));
realm.getUsers().add(createUser(BOB, BOB_PASSWORD, Arrays.asList(USER_ROLE)));
return realm;
}
- private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp) {
+ private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp, boolean directAccessGrantEnabled) {
+ return createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, null);
+ }
+
+ private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort,
+ String clientApp, boolean directAccessGrantEnabled, String allowedOrigin) {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(clientId);
client.setPublicClient(false);
@@ -109,6 +156,18 @@ private static ClientRepresentation createWebAppClient(String clientId, String c
//client.setRedirectUris(Arrays.asList("*"));
client.setRedirectUris(Arrays.asList("http://" + clientHostName + ":" + clientPort + "/" + clientApp));
client.setEnabled(true);
+ client.setDirectAccessGrantsEnabled(directAccessGrantEnabled);
+ if (allowedOrigin != null) {
+ client.setWebOrigins(Collections.singletonList(allowedOrigin));
+ }
+ return client;
+ }
+
+ private static ClientRepresentation createBearerOnlyClient(String clientId) {
+ ClientRepresentation client = new ClientRepresentation();
+ client.setClientId(clientId);
+ client.setBearerOnly(true);
+ client.setEnabled(true);
return client;
}
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java
new file mode 100644
index 00000000000..b1fb8ea2d2e
--- /dev/null
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java
@@ -0,0 +1,218 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2022 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wildfly.security.http.oidc;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.List;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.AuthorizeCallback;
+
+import org.junit.AfterClass;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.testcontainers.DockerClientFactory;
+import org.wildfly.security.auth.callback.AuthenticationCompleteCallback;
+import org.wildfly.security.auth.callback.EvidenceVerifyCallback;
+import org.wildfly.security.auth.callback.IdentityCredentialCallback;
+import org.wildfly.security.auth.callback.SecurityIdentityCallback;
+import org.wildfly.security.auth.server.SecurityDomain;
+import org.wildfly.security.evidence.Evidence;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
+import org.wildfly.security.http.HttpServerCookie;
+import org.wildfly.security.http.impl.AbstractBaseHttpTest;
+import org.wildfly.security.jose.util.JsonSerialization;
+
+import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlInput;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.javascript.SilentJavaScriptErrorListener;
+
+import io.restassured.RestAssured;
+import okhttp3.mockwebserver.Dispatcher;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+/**
+ * Tests for the OpenID Connect authentication mechanism.
+ *
+ * @author Farah Juma
+ */
+public class OidcBaseTest extends AbstractBaseHttpTest {
+
+ public static final String CLIENT_ID = "test-webapp";
+ public static final String CLIENT_SECRET = "secret";
+ public static KeycloakContainer KEYCLOAK_CONTAINER;
+ public static final String TEST_REALM = "WildFly";
+ public static final String KEYCLOAK_USERNAME = "username";
+ public static final String KEYCLOAK_PASSWORD = "password";
+ public static final String KEYCLOAK_LOGIN = "login";
+ public static final int CLIENT_PORT = 5002;
+ public static final String CLIENT_APP = "clientApp";
+ public static final String CLIENT_PAGE_TEXT = "Welcome page!";
+ public static final String CLIENT_HOST_NAME = "localhost";
+ public static MockWebServer client; // to simulate the application being secured
+
+ protected HttpServerAuthenticationMechanismFactory oidcFactory;
+
+ @AfterClass
+ public static void generalCleanup() throws Exception {
+ if (KEYCLOAK_CONTAINER != null) {
+ RestAssured
+ .given()
+ .auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
+ .when()
+ .delete(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms/" + TEST_REALM).then().statusCode(204);
+ KEYCLOAK_CONTAINER.stop();
+ }
+ if (client != null) {
+ client.shutdown();
+ }
+ }
+
+ protected static void sendRealmCreationRequest(RealmRepresentation realm) {
+ try {
+ RestAssured
+ .given()
+ .auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
+ .contentType("application/json")
+ .body(JsonSerialization.writeValueAsBytes(realm))
+ .when()
+ .post(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms").then()
+ .statusCode(201);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected static boolean isDockerAvailable() {
+ try {
+ DockerClientFactory.instance().client();
+ return true;
+ } catch (Throwable ex) {
+ return false;
+ }
+ }
+
+ protected CallbackHandler getCallbackHandler() {
+ return callbacks -> {
+ for(Callback callback : callbacks) {
+ if (callback instanceof EvidenceVerifyCallback) {
+ Evidence evidence = ((EvidenceVerifyCallback) callback).getEvidence();
+ ((EvidenceVerifyCallback) callback).setVerified(evidence.getDecodedPrincipal() != null);
+ } else if (callback instanceof AuthenticationCompleteCallback) {
+ // NO-OP
+ } else if (callback instanceof IdentityCredentialCallback) {
+ // NO-OP
+ } else if (callback instanceof AuthorizeCallback) {
+ ((AuthorizeCallback) callback).setAuthorized(true);
+ } else if (callback instanceof SecurityIdentityCallback) {
+ ((SecurityIdentityCallback) callback).setSecurityIdentity(SecurityDomain.builder().build().getCurrentSecurityIdentity());
+ } else {
+ throw new UnsupportedCallbackException(callback);
+ }
+ }
+ };
+ }
+
+ protected static Dispatcher createAppResponse(HttpServerAuthenticationMechanism mechanism, int expectedStatusCode, String expectedLocation, String clientPageText) {
+ return new Dispatcher() {
+ @Override
+ public MockResponse dispatch(RecordedRequest recordedRequest) throws InterruptedException {
+ String path = recordedRequest.getPath();
+ if (path.contains("/" + CLIENT_APP) && path.contains("&code=")) {
+ try {
+ TestingHttpServerRequest request = new TestingHttpServerRequest(new String[0],
+ new URI(recordedRequest.getRequestUrl().toString()), recordedRequest.getHeader("Cookie"));
+ mechanism.evaluateRequest(request);
+ TestingHttpServerResponse response = request.getResponse();
+ assertEquals(expectedStatusCode, response.getStatusCode());
+ assertEquals(expectedLocation, response.getLocation());
+ return new MockResponse().setBody(clientPageText);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return new MockResponse()
+ .setBody("");
+ }
+ };
+ }
+
+ protected WebClient getWebClient() {
+ WebClient webClient = new WebClient();
+ webClient.setCssErrorHandler(new SilentCssErrorHandler());
+ webClient.setJavaScriptErrorListener(new SilentJavaScriptErrorListener());
+ return webClient;
+ }
+
+ protected static String getClientUrl() {
+ return "http://" + CLIENT_HOST_NAME + ":" + CLIENT_PORT + "/" + CLIENT_APP;
+ }
+
+ protected HtmlInput loginToKeycloak(String username, String password, URI requestUri, String location, List cookies) throws IOException {
+ WebClient webClient = getWebClient();
+ if (cookies != null) {
+ for (HttpServerCookie cookie : cookies) {
+ webClient.addCookie(getCookieString(cookie), requestUri.toURL(), null);
+ }
+ }
+ HtmlPage keycloakLoginPage = webClient.getPage(location);
+ HtmlForm loginForm = keycloakLoginPage.getForms().get(0);
+ loginForm.getInputByName(KEYCLOAK_USERNAME).setValueAttribute(username);
+ loginForm.getInputByName(KEYCLOAK_PASSWORD).setValueAttribute(password);
+ return loginForm.getInputByName(KEYCLOAK_LOGIN);
+ }
+
+ protected String getCookieString(HttpServerCookie cookie) {
+ final StringBuilder header = new StringBuilder(cookie.getName());
+ header.append("=");
+ if(cookie.getValue() != null) {
+ header.append(cookie.getValue());
+ }
+ if (cookie.getPath() != null) {
+ header.append("; Path=");
+ header.append(cookie.getPath());
+ }
+ if (cookie.getDomain() != null) {
+ header.append("; Domain=");
+ header.append(cookie.getDomain());
+ }
+ if (cookie.isSecure()) {
+ header.append("; Secure");
+ }
+ if (cookie.isHttpOnly()) {
+ header.append("; HttpOnly");
+ }
+ if (cookie.getMaxAge() >= 0) {
+ header.append("; Max-Age=");
+ header.append(cookie.getMaxAge());
+ }
+ return header.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java
index 710e82c99d8..3ae28fc1e05 100644
--- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java
@@ -24,73 +24,31 @@
import static org.wildfly.security.http.oidc.Oidc.OIDC_NAME;
import java.io.ByteArrayInputStream;
-import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.sasl.AuthorizeCallback;
-
import org.apache.http.HttpStatus;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
-import org.keycloak.representations.idm.RealmRepresentation;
-import org.testcontainers.DockerClientFactory;
-import org.wildfly.security.auth.callback.AuthenticationCompleteCallback;
-import org.wildfly.security.auth.callback.EvidenceVerifyCallback;
-import org.wildfly.security.auth.callback.IdentityCredentialCallback;
-import org.wildfly.security.auth.callback.SecurityIdentityCallback;
-import org.wildfly.security.auth.server.SecurityDomain;
-import org.wildfly.security.evidence.Evidence;
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
-import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
-import org.wildfly.security.http.HttpServerCookie;
-import org.wildfly.security.http.impl.AbstractBaseHttpTest;
-import org.wildfly.security.jose.util.JsonSerialization;
-import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
import com.gargoylesoftware.htmlunit.TextPage;
-import com.gargoylesoftware.htmlunit.WebClient;
-import com.gargoylesoftware.htmlunit.html.HtmlForm;
-import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
-import com.gargoylesoftware.htmlunit.javascript.SilentJavaScriptErrorListener;
import io.restassured.RestAssured;
-import okhttp3.mockwebserver.Dispatcher;
-import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.QueueDispatcher;
-import okhttp3.mockwebserver.RecordedRequest;
/**
* Tests for the OpenID Connect authentication mechanism.
*
* @author Farah Juma
*/
-public class OidcTest extends AbstractBaseHttpTest {
-
- public static final String CLIENT_ID = "test-webapp";
- public static final String CLIENT_SECRET = "secret";
- private static KeycloakContainer KEYCLOAK_CONTAINER;
- private static final String TEST_REALM = "WildFly";
- private static final String KEYCLOAK_USERNAME = "username";
- private static final String KEYCLOAK_PASSWORD = "password";
- private static final String KEYCLOAK_LOGIN = "login";
- private static final int CLIENT_PORT = 5002;
- private static final String CLIENT_APP = "clientApp";
- private static final String CLIENT_PAGE_TEXT = "Welcome page!";
- private static final String CLIENT_HOST_NAME = "localhost";
- private static MockWebServer client; // to simulate the application being secured
-
- protected HttpServerAuthenticationMechanismFactory oidcFactory;
+public class OidcTest extends OidcBaseTest {
@BeforeClass
public static void startTestContainers() throws Exception {
@@ -102,30 +60,6 @@ public static void startTestContainers() throws Exception {
client.start(CLIENT_PORT);
}
- private static Dispatcher createAppResponse(HttpServerAuthenticationMechanism mechanism, int expectedStatusCode, String expectedLocation, String clientPageText) {
- return new Dispatcher() {
- @Override
- public MockResponse dispatch(RecordedRequest recordedRequest) throws InterruptedException {
- String path = recordedRequest.getPath();
- if (path.contains("/" + CLIENT_APP) && path.contains("&code=")) {
- try {
- TestingHttpServerRequest request = new TestingHttpServerRequest(null,
- new URI(recordedRequest.getRequestUrl().toString()), recordedRequest.getHeader("Cookie"));
- mechanism.evaluateRequest(request);
- TestingHttpServerResponse response = request.getResponse();
- assertEquals(expectedStatusCode, response.getStatusCode());
- assertEquals(expectedLocation, response.getLocation());
- return new MockResponse().setBody(clientPageText);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- return new MockResponse()
- .setBody("");
- }
- };
- }
-
@AfterClass
public static void generalCleanup() throws Exception {
if (KEYCLOAK_CONTAINER != null) {
@@ -141,21 +75,6 @@ public static void generalCleanup() throws Exception {
}
}
- private static void sendRealmCreationRequest(RealmRepresentation realm) {
- try {
- RestAssured
- .given()
- .auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
- .contentType("application/json")
- .body(JsonSerialization.writeValueAsBytes(realm))
- .when()
- .post(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms").then()
- .statusCode(201);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
@Test
public void testWrongPassword() throws Exception {
Map props = new HashMap<>();
@@ -240,27 +159,6 @@ private void performAuthentication(InputStream oidcConfig, String username, Stri
}
}
- private WebClient getWebClient() {
- WebClient webClient = new WebClient();
- webClient.setCssErrorHandler(new SilentCssErrorHandler());
- webClient.setJavaScriptErrorListener(new SilentJavaScriptErrorListener());
- return webClient;
- }
-
- private HtmlInput loginToKeycloak(String username, String password, URI requestUri, String location, List cookies) throws IOException {
- WebClient webClient = getWebClient();
- if (cookies != null) {
- for (HttpServerCookie cookie : cookies) {
- webClient.addCookie(getCookieString(cookie), requestUri.toURL(), null);
- }
- }
- HtmlPage keycloakLoginPage = webClient.getPage(location);
- HtmlForm loginForm = keycloakLoginPage.getForms().get(0);
- loginForm.getInputByName(KEYCLOAK_USERNAME).setValueAttribute(username);
- loginForm.getInputByName(KEYCLOAK_PASSWORD).setValueAttribute(password);
- return loginForm.getInputByName(KEYCLOAK_LOGIN);
- }
-
private InputStream getOidcConfigurationInputStream() {
return getOidcConfigurationInputStream(CLIENT_SECRET);
}
@@ -321,65 +219,4 @@ private InputStream getOidcConfigurationInputStreamWithTokenSignatureAlgorithm()
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
}
-
- private CallbackHandler getCallbackHandler() {
- return callbacks -> {
- for(Callback callback : callbacks) {
- if (callback instanceof EvidenceVerifyCallback) {
- Evidence evidence = ((EvidenceVerifyCallback) callback).getEvidence();
- ((EvidenceVerifyCallback) callback).setVerified(evidence.getDecodedPrincipal() != null);
- } else if (callback instanceof AuthenticationCompleteCallback) {
- // NO-OP
- } else if (callback instanceof IdentityCredentialCallback) {
- // NO-OP
- } else if (callback instanceof AuthorizeCallback) {
- ((AuthorizeCallback) callback).setAuthorized(true);
- } else if (callback instanceof SecurityIdentityCallback) {
- ((SecurityIdentityCallback) callback).setSecurityIdentity(SecurityDomain.builder().build().getCurrentSecurityIdentity());
- } else {
- throw new UnsupportedCallbackException(callback);
- }
- }
- };
- }
-
- private static boolean isDockerAvailable() {
- try {
- DockerClientFactory.instance().client();
- return true;
- } catch (Throwable ex) {
- return false;
- }
- }
-
- private String getCookieString(HttpServerCookie cookie) {
- final StringBuilder header = new StringBuilder(cookie.getName());
- header.append("=");
- if(cookie.getValue() != null) {
- header.append(cookie.getValue());
- }
- if (cookie.getPath() != null) {
- header.append("; Path=");
- header.append(cookie.getPath());
- }
- if (cookie.getDomain() != null) {
- header.append("; Domain=");
- header.append(cookie.getDomain());
- }
- if (cookie.isSecure()) {
- header.append("; Secure");
- }
- if (cookie.isHttpOnly()) {
- header.append("; HttpOnly");
- }
- if (cookie.getMaxAge() >= 0) {
- header.append("; Max-Age=");
- header.append(cookie.getMaxAge());
- }
- return header.toString();
- }
-
- private static String getClientUrl() {
- return "http://" + CLIENT_HOST_NAME + ":" + CLIENT_PORT + "/" + CLIENT_APP;
- }
}
\ No newline at end of file
diff --git a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java
index a86f86d1443..73171b4fb48 100644
--- a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java
@@ -36,6 +36,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -124,35 +125,52 @@ protected enum Status {
protected static class TestingHttpServerRequest implements HttpServerRequest {
- private String[] authorization;
private Status result;
private HttpServerMechanismsResponder responder;
private String remoteUser;
private URI requestURI;
private List cookies;
+ private String requestMethod = "GET";
+ private Map> requestHeaders = new HashMap<>();
public TestingHttpServerRequest(String[] authorization) {
- this.authorization = authorization;
+ if (authorization != null) {
+ requestHeaders.put(AUTHORIZATION, Arrays.asList(authorization));
+ }
this.remoteUser = null;
this.cookies = new ArrayList<>();
}
public TestingHttpServerRequest(String[] authorization, URI requestURI) {
- this.authorization = authorization;
+ if (authorization != null) {
+ requestHeaders.put(AUTHORIZATION, Arrays.asList(authorization));
+ }
this.remoteUser = null;
this.requestURI = requestURI;
this.cookies = new ArrayList<>();
}
public TestingHttpServerRequest(String[] authorization, URI requestURI, List cookies) {
- this.authorization = authorization;
+ if (authorization != null) {
+ requestHeaders.put(AUTHORIZATION, Arrays.asList(authorization));
+ }
this.remoteUser = null;
this.requestURI = requestURI;
this.cookies = cookies;
}
+ public TestingHttpServerRequest(Map> requestHeaders, URI requestURI, String requestMethod) {
+ this.requestHeaders = requestHeaders;
+ this.remoteUser = null;
+ this.requestURI = requestURI;
+ this.cookies = new ArrayList<>();
+ this.requestMethod = requestMethod;
+ }
+
public TestingHttpServerRequest(String[] authorization, URI requestURI, String cookie) {
- this.authorization = authorization;
+ if (authorization != null) {
+ requestHeaders.put(AUTHORIZATION, Arrays.asList(authorization));
+ }
this.remoteUser = null;
this.requestURI = requestURI;
this.cookies = new ArrayList<>();
@@ -215,14 +233,12 @@ public TestingHttpServerResponse getResponse() throws HttpAuthenticationExceptio
}
public List getRequestHeaderValues(String headerName) {
- if (AUTHORIZATION.equals(headerName)) {
- return authorization == null ? null : Arrays.asList(authorization);
- }
- return null;
+ return requestHeaders.get(headerName);
}
public String getFirstRequestHeaderValue(String headerName) {
- throw new IllegalStateException();
+ List headerValues = requestHeaders.get(headerName);
+ return headerValues != null ? headerValues.get(0) : null;
}
public SSLSession getSSLSession() {
@@ -263,7 +279,7 @@ public void badRequest(HttpAuthenticationException failure, HttpServerMechanisms
}
public String getRequestMethod() {
- return "GET";
+ return requestMethod;
}
public URI getRequestURI() {
@@ -367,9 +383,8 @@ public String getRemoteUser() {
protected static class TestingHttpServerResponse implements HttpServerResponse {
private int statusCode;
- private String authenticate;
- private String location;
private List cookies;
+ private Map> responseHeaders = new HashMap<>();
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
@@ -380,19 +395,22 @@ public int getStatusCode() {
}
public void addResponseHeader(String headerName, String headerValue) {
- if (WWW_AUTHENTICATE.equals(headerName)) {
- authenticate = headerValue;
- } else if (LOCATION.equals(headerName)) {
- location = headerValue;
+ if (headerValue != null) {
+ responseHeaders.put(headerName, Collections.singletonList(headerValue));
}
}
public String getAuthenticateHeader() {
- return authenticate;
+ return getFirstResponseHeaderValue(WWW_AUTHENTICATE);
}
public String getLocation() {
- return location;
+ return getFirstResponseHeaderValue(LOCATION);
+ }
+
+ public String getFirstResponseHeaderValue(String headerName) {
+ List headerValue = responseHeaders.get(headerName);
+ return headerValue == null ? null : headerValue.get(0);
}
public List getCookies() {
@@ -473,11 +491,12 @@ protected CallbackHandler getCallbackHandler(String username, String realm, Stri
public class TestingHttpExchangeSpi implements HttpExchangeSpi {
- private List requestAuthorizationHeaders = Collections.emptyList();
+ private Map> requestHeaders = new HashMap<>();
private List responseAuthenticateHeaders = new LinkedList<>();
private List responseAuthenticationInfoHeaders = new LinkedList<>();
private int statusCode;
private Status result;
+ private String requestMethod = "GET";
public int getStatusCode() {
return statusCode;
@@ -496,17 +515,27 @@ public List getResponseAuthenticationInfoHeaders() {
}
public void setRequestAuthorizationHeaders(List requestAuthorizationHeaders) {
- this.requestAuthorizationHeaders = requestAuthorizationHeaders;
+ requestHeaders.put(AUTHORIZATION, requestAuthorizationHeaders);
+ }
+
+ public void setHeader(String headerName, String headerValue) {
+ if (headerValue != null) {
+ setHeader(headerName, Collections.singletonList(headerValue));
+ }
+ }
+
+ public void setHeader(String headerName, List headerValue) {
+ requestHeaders.put(headerName, headerValue);
+ }
+
+ public void setRequestMethod(String requestMethod) {
+ this.requestMethod = requestMethod;
}
// ------
public List getRequestHeaderValues(String headerName) {
- if (AUTHORIZATION.equals(headerName)) {
- return requestAuthorizationHeaders;
- } else {
- throw new IllegalStateException();
- }
+ return requestHeaders.get(headerName);
}
public void addResponseHeader(String headerName, String headerValue) {
@@ -536,7 +565,7 @@ public void badRequest(HttpAuthenticationException error, String mechanismName)
}
public String getRequestMethod() {
- return "GET";
+ return requestMethod;
}
public URI getRequestURI() {