Merge pull request #2110 from mposolda/master

KEYCLOAK-2351 Support for response_type=token to be OAuth2 compliant
This commit is contained in:
Marek Posolda 2016-01-26 17:54:08 +01:00
commit 4baeaded23
7 changed files with 27 additions and 16 deletions

View file

@ -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>

View file

@ -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()) {

View file

@ -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");

View file

@ -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);
} }

View file

@ -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());

View file

@ -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) {

View file

@ -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) {