Merge pull request #2110 from mposolda/master
KEYCLOAK-2351 Support for response_type=token to be OAuth2 compliant
This commit is contained in:
commit
4baeaded23
7 changed files with 27 additions and 16 deletions
|
@ -28,6 +28,14 @@
|
||||||
and his browser and hence can't rely on sticky sessions.
|
and his browser and hence can't rely on sticky sessions.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<note>
|
||||||
|
<para>
|
||||||
|
To enable distributable (replicated) HTTP Sessions in your application, you may need to do some additional steps. Usually you need to put <![CDATA[<distributable />]]>
|
||||||
|
tag into <literal>WEB-INF/web.xml</literal> 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.
|
||||||
|
</para>
|
||||||
|
</note>
|
||||||
|
|
||||||
<section id="stateless-token-store">
|
<section id="stateless-token-store">
|
||||||
<title>Stateless token store</title>
|
<title>Stateless token store</title>
|
||||||
<para>
|
<para>
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.authentication.authenticators.client;
|
||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -120,7 +121,7 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
|
||||||
// Validate other things
|
// Validate other things
|
||||||
String expectedAudience = Urls.realmIssuer(context.getUriInfo().getBaseUri(), realm.getName());
|
String expectedAudience = Urls.realmIssuer(context.getUriInfo().getBaseUri(), realm.getName());
|
||||||
if (!token.hasAudience(expectedAudience)) {
|
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()) {
|
if (!token.isActive()) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
||||||
|
|
||||||
public static final List<String> DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS);
|
public static final List<String> DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS);
|
||||||
|
|
||||||
public static final List<String> 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<String> 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<String> DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public");
|
public static final List<String> DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public");
|
||||||
|
|
||||||
|
|
|
@ -63,9 +63,11 @@ public class OIDCResponseType {
|
||||||
if (responseTypes.contains(NONE) && responseTypes.size() > 1) {
|
if (responseTypes.contains(NONE) && responseTypes.size() > 1) {
|
||||||
throw new IllegalArgumentException("None not allowed with some other response_type");
|
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() {
|
public boolean isImplicitFlow() {
|
||||||
return hasResponseType(ID_TOKEN) && !hasResponseType(CODE);
|
return (hasResponseType(TOKEN) || hasResponseType(ID_TOKEN)) && !hasResponseType(CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ public class ResponseTypeTest {
|
||||||
assertSuccess("code");
|
assertSuccess("code");
|
||||||
assertSuccess("none");
|
assertSuccess("none");
|
||||||
assertSuccess("id_token");
|
assertSuccess("id_token");
|
||||||
assertFail("token");
|
assertSuccess("token");
|
||||||
assertFail("refresh_token");
|
assertFail("refresh_token");
|
||||||
assertSuccess("id_token token");
|
assertSuccess("id_token token");
|
||||||
assertSuccess("code token");
|
assertSuccess("code token");
|
||||||
|
@ -32,13 +32,13 @@ public class ResponseTypeTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMultipleResponseTypes() {
|
public void testMultipleResponseTypes() {
|
||||||
try {
|
OIDCResponseType responseType = OIDCResponseType.parse(Arrays.asList("code", "token"));
|
||||||
OIDCResponseType.parse(Arrays.asList("code", "token"));
|
Assert.assertTrue(responseType.hasResponseType("code"));
|
||||||
Assert.fail("Not expected to parse with success");
|
Assert.assertFalse(responseType.hasResponseType("none"));
|
||||||
} catch (IllegalArgumentException iae) {
|
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.assertTrue(responseType.hasResponseType("code"));
|
||||||
Assert.assertFalse(responseType.hasResponseType("none"));
|
Assert.assertFalse(responseType.hasResponseType("none"));
|
||||||
Assert.assertFalse(responseType.isImplicitOrHybridFlow());
|
Assert.assertFalse(responseType.isImplicitOrHybridFlow());
|
||||||
|
|
|
@ -90,7 +90,7 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
||||||
try {
|
try {
|
||||||
OIDCClientRepresentation response = create();
|
OIDCClientRepresentation response = create();
|
||||||
reg.auth(Auth.token(response));
|
reg.auth(Auth.token(response));
|
||||||
response.setResponseTypes(Arrays.asList("code", "token"));
|
response.setResponseTypes(Arrays.asList("code", "tokenn"));
|
||||||
reg.oidc().update(response);
|
reg.oidc().update(response);
|
||||||
fail("Not expected to end with success");
|
fail("Not expected to end with success");
|
||||||
} catch (ClientRegistrationException cre) {
|
} catch (ClientRegistrationException cre) {
|
||||||
|
|
|
@ -171,10 +171,10 @@ public class AuthorizationCodeTest {
|
||||||
@Test
|
@Test
|
||||||
public void authorizationRequestInvalidResponseType() throws IOException {
|
public void authorizationRequestInvalidResponseType() throws IOException {
|
||||||
UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl());
|
UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl());
|
||||||
b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "token");
|
b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "tokenn");
|
||||||
driver.navigate().to(b.build().toURL());
|
driver.navigate().to(b.build().toURL());
|
||||||
assertEquals("Invalid parameter: response_type", errorPage.getError());
|
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) {
|
private void assertCode(String expectedCodeId, String actualCode) {
|
||||||
|
|
Loading…
Reference in a new issue