diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/application-clustering.xml b/docbook/auth-server-docs/reference/en/en-US/modules/application-clustering.xml index c4246d2f12..5dab883901 100644 --- a/docbook/auth-server-docs/reference/en/en-US/modules/application-clustering.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/application-clustering.xml @@ -28,6 +28,14 @@ and his browser and hence can't rely on sticky sessions. + + + To enable distributable (replicated) HTTP Sessions in your application, you may need to do some additional steps. Usually you need to put ]]> + tag into WEB-INF/web.xml file of your application and possibly do some additional steps to configure underlying cluster cache (In case of + Wildfly, the implementation of cluster cache is based on Infinispan). These steps are server specific, so consult documentation of your application server for more details. + + +
Stateless token store diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java index 4cb728f67a..a1a847dd07 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java @@ -2,6 +2,7 @@ package org.keycloak.authentication.authenticators.client; import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -120,7 +121,7 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator { // Validate other things String expectedAudience = Urls.realmIssuer(context.getUriInfo().getBaseUri(), realm.getName()); if (!token.hasAudience(expectedAudience)) { - throw new RuntimeException("Token audience doesn't match domain. Realm audience is '" + expectedAudience + "' but audience from token is '" + token.getAudience() + "'"); + throw new RuntimeException("Token audience doesn't match domain. Realm audience is '" + expectedAudience + "' but audience from token is '" + Arrays.asList(token.getAudience()).toString() + "'"); } if (!token.isActive()) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java index 8e0cdbf0e2..4f6a5cc20b 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java @@ -26,7 +26,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider { public static final List DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS); - public static final List DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, "id_token token", "code id_token", "code token", "code id_token token"); + public static final List DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, OIDCResponseType.TOKEN, "id_token token", "code id_token", "code token", "code id_token token"); public static final List DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public"); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java index 9313aa60bc..e8327ecd50 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java @@ -63,9 +63,11 @@ public class OIDCResponseType { if (responseTypes.contains(NONE) && responseTypes.size() > 1) { throw new IllegalArgumentException("None not allowed with some other response_type"); } - if (responseTypes.contains(TOKEN) && responseTypes.size() == 1) { - throw new IllegalArgumentException("Not supported to use response_type=token alone"); - } + + // response_type value "token" alone is not mentioned in OIDC specification, however it is supported by OAuth2. We allow it just to be compatible with pure OAuth2 clients like swagger.ui +// if (responseTypes.contains(TOKEN) && responseTypes.size() == 1) { +// throw new IllegalArgumentException("Not supported to use response_type=token alone"); +// } } @@ -79,7 +81,7 @@ public class OIDCResponseType { } public boolean isImplicitFlow() { - return hasResponseType(ID_TOKEN) && !hasResponseType(CODE); + return (hasResponseType(TOKEN) || hasResponseType(ID_TOKEN)) && !hasResponseType(CODE); } diff --git a/services/src/test/java/org/keycloak/test/ResponseTypeTest.java b/services/src/test/java/org/keycloak/test/ResponseTypeTest.java index dd15aa90e9..42d852a17d 100644 --- a/services/src/test/java/org/keycloak/test/ResponseTypeTest.java +++ b/services/src/test/java/org/keycloak/test/ResponseTypeTest.java @@ -20,7 +20,7 @@ public class ResponseTypeTest { assertSuccess("code"); assertSuccess("none"); assertSuccess("id_token"); - assertFail("token"); + assertSuccess("token"); assertFail("refresh_token"); assertSuccess("id_token token"); assertSuccess("code token"); @@ -32,13 +32,13 @@ public class ResponseTypeTest { @Test public void testMultipleResponseTypes() { - try { - OIDCResponseType.parse(Arrays.asList("code", "token")); - Assert.fail("Not expected to parse with success"); - } catch (IllegalArgumentException iae) { - } + OIDCResponseType responseType = OIDCResponseType.parse(Arrays.asList("code", "token")); + Assert.assertTrue(responseType.hasResponseType("code")); + Assert.assertFalse(responseType.hasResponseType("none")); + Assert.assertTrue(responseType.isImplicitOrHybridFlow()); + Assert.assertFalse(responseType.isImplicitFlow()); - OIDCResponseType responseType = OIDCResponseType.parse(Collections.singletonList("code")); + responseType = OIDCResponseType.parse(Collections.singletonList("code")); Assert.assertTrue(responseType.hasResponseType("code")); Assert.assertFalse(responseType.hasResponseType("none")); Assert.assertFalse(responseType.isImplicitOrHybridFlow()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java index f0fbe2bedc..81a69af78c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java @@ -90,7 +90,7 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { try { OIDCClientRepresentation response = create(); reg.auth(Auth.token(response)); - response.setResponseTypes(Arrays.asList("code", "token")); + response.setResponseTypes(Arrays.asList("code", "tokenn")); reg.oidc().update(response); fail("Not expected to end with success"); } catch (ClientRegistrationException cre) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java index 12d443a031..232c57835b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java @@ -171,10 +171,10 @@ public class AuthorizationCodeTest { @Test public void authorizationRequestInvalidResponseType() throws IOException { UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl()); - b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "token"); + b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "tokenn"); driver.navigate().to(b.build().toURL()); assertEquals("Invalid parameter: response_type", errorPage.getError()); - events.expectLogin().error(Errors.INVALID_REQUEST).client((String) null).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "token").assertEvent(); + events.expectLogin().error(Errors.INVALID_REQUEST).client((String) null).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "tokenn").assertEvent(); } private void assertCode(String expectedCodeId, String actualCode) {