KEYCLOAK-3223 Basic support for acr claim

This commit is contained in:
mposolda 2016-07-14 10:43:08 +02:00
parent 9ddec7d1eb
commit ee3ac3fdaf
7 changed files with 79 additions and 33 deletions

View file

@ -47,6 +47,8 @@ public class IDToken extends JsonWebToken {
public static final String ADDRESS = "address";
public static final String UPDATED_AT = "updated_at";
public static final String CLAIMS_LOCALES = "claims_locales";
public static final String ACR = "acr";
// NOTE!!! WE used to use @JsonUnwrapped on a UserClaimSet object. This screws up otherClaims and the won't work
// anymore. So don't have any @JsonUnwrapped!
@JsonProperty(NONCE)
@ -118,6 +120,9 @@ public class IDToken extends JsonWebToken {
@JsonProperty(CLAIMS_LOCALES)
protected String claimsLocales;
@JsonProperty(ACR)
protected String acr;
public String getNonce() {
return nonce;
}
@ -302,4 +307,11 @@ public class IDToken extends JsonWebToken {
this.claimsLocales = claimsLocales;
}
public String getAcr() {
return acr;
}
public void setAcr(String acr) {
this.acr = acr;
}
}

View file

@ -54,7 +54,7 @@ public class CookieAuthenticator implements Authenticator {
context.attempted();
} else {
ClientSessionModel clientSession = context.getClientSession();
clientSession.setNote(AuthenticationManager.SKIP_AUTH_TIME_UPDATE, "true");
clientSession.setNote(AuthenticationManager.SSO_AUTH, "true");
context.setUser(authResult.getUser());
context.attachUserSession(authResult.getSession());

View file

@ -524,6 +524,11 @@ public class TokenManager {
token.issuer(clientSession.getNote(OIDCLoginProtocol.ISSUER));
token.setNonce(clientSession.getNote(OIDCLoginProtocol.NONCE_PARAM));
// Best effort for "acr" value. Use 0 if clientSession was authenticated through cookie ( SSO )
// TODO: Add better acr support. See KEYCLOAK-3314
String acr = (AuthenticationManager.isSSOAuthentication(clientSession)) ? "0" : "1";
token.setAcr(acr);
String authTime = session.getNote(AuthenticationManager.AUTH_TIME);
if (authTime != null) {
token.setAuthTime(Integer.parseInt(authTime));
@ -673,6 +678,7 @@ public class TokenManager {
idToken.setAuthTime(accessToken.getAuthTime());
idToken.setSessionState(accessToken.getSessionState());
idToken.expiration(accessToken.getExpiration());
idToken.setAcr(accessToken.getAcr());
transformIDToken(session, idToken, realm, client, userSession.getUser(), userSession, clientSession);
return this;
}

View file

@ -64,8 +64,8 @@ public class AuthenticationManager {
// userSession note with authTime (time when authentication flow including requiredActions was finished)
public static final String AUTH_TIME = "AUTH_TIME";
// clientSession note with flag that authTime update should be skipped
public static final String SKIP_AUTH_TIME_UPDATE = "SKIP_AUTH_TIME_UPDATE";
// clientSession note with flag that clientSession was authenticated through SSO cookie
public static final String SSO_AUTH = "SSO_AUTH";
protected static ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
public static final String FORM_USERNAME = "username";
@ -410,9 +410,8 @@ public class AuthenticationManager {
if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
// Update userSession note with authTime. But just if flag SKIP_AUTH_TIME_UPDATE is not set
String skipAuthTimeUpdate = clientSession.getNote(SKIP_AUTH_TIME_UPDATE);
if (skipAuthTimeUpdate == null || !Boolean.parseBoolean(skipAuthTimeUpdate)) {
// Update userSession note with authTime. But just if flag SSO_AUTH is not set
if (!isSSOAuthentication(clientSession)) {
int authTime = Time.currentTime();
userSession.setNote(AUTH_TIME, String.valueOf(authTime));
}
@ -420,6 +419,13 @@ public class AuthenticationManager {
return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
}
public static boolean isSSOAuthentication(ClientSessionModel clientSession) {
String ssoAuth = clientSession.getNote(SSO_AUTH);
return Boolean.parseBoolean(ssoAuth);
}
public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
ClientConnection clientConnection,
HttpRequest request, UriInfo uriInfo, EventBuilder event) {

View file

@ -17,12 +17,19 @@
package org.keycloak.testsuite;
import org.keycloak.OAuth2Constants;
import org.keycloak.common.util.reflections.Reflections;
import org.keycloak.events.Details;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import java.lang.reflect.Field;
import java.util.List;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.util.OAuthClient;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
@ -74,4 +81,23 @@ public abstract class TestRealmKeycloakTest extends AbstractKeycloakTest {
*/
public abstract void configureTestRealm(RealmRepresentation testRealm);
protected IDToken sendTokenRequestAndGetIDToken(EventRepresentation loginEvent) {
String sessionId = loginEvent.getSessionId();
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
Assert.assertEquals(200, response.getStatusCode());
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
Field eventsField = Reflections.findDeclaredField(this.getClass(), "events");
if (eventsField != null) {
AssertEvents events = Reflections.getFieldValue(eventsField, this, AssertEvents.class);
events.expectCodeToToken(codeId, sessionId).assertEvent();
}
return idToken;
}
}

View file

@ -23,6 +23,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.events.Details;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AssertEvents;
@ -73,7 +74,11 @@ public class SSOTest extends TestRealmKeycloakTest {
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
String sessionId = events.expectLogin().assertEvent().getSessionId();
EventRepresentation loginEvent = events.expectLogin().assertEvent();
String sessionId = loginEvent.getSessionId();
IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent);
Assert.assertEquals("1", idToken.getAcr());
appPage.open();
@ -81,14 +86,18 @@ public class SSOTest extends TestRealmKeycloakTest {
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
profilePage.open();
assertTrue(profilePage.isCurrent());
String sessionId2 = events.expectLogin().removeDetail(Details.USERNAME).client("test-app").assertEvent().getSessionId();
loginEvent = events.expectLogin().removeDetail(Details.USERNAME).client("test-app").assertEvent();
String sessionId2 = loginEvent.getSessionId();
assertEquals(sessionId, sessionId2);
// acr is 0 as we authenticated through SSO cookie
idToken = sendTokenRequestAndGetIDToken(loginEvent);
Assert.assertEquals("0", idToken.getAcr());
profilePage.open();
assertTrue(profilePage.isCurrent());
// Expire session
testingClient.testing().removeUserSession("test", sessionId);

View file

@ -31,24 +31,25 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.TestRealmKeycloakTest;
import org.keycloak.testsuite.admin.AbstractAdminTest;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder;
/**
* Test for supporting advanced parameters of OIDC specs (max_age, nonce, prompt, ...)
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OIDCAdvancedRequestParamsTest extends AbstractKeycloakTest {
public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest {
@Rule
public AssertEvents events = new AssertEvents(this);
@Override
public void beforeAbstractKeycloakTest() throws Exception {
super.beforeAbstractKeycloakTest();
public void configureTestRealm(RealmRepresentation testRealm) {
}
@Before
@ -76,7 +77,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractKeycloakTest {
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
IDToken idToken = retrieveIDToken(loginEvent);
IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent);
// Check that authTime is available and set to current time
int authTime = idToken.getAuthTime();
@ -93,7 +94,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractKeycloakTest {
oauth.doLogin("test-user@localhost", "password");
loginEvent = events.expectLogin().assertEvent();
idToken = retrieveIDToken(loginEvent);
idToken = sendTokenRequestAndGetIDToken(loginEvent);
// Assert that authTime was updated
int authTimeUpdated = idToken.getAuthTime();
@ -106,7 +107,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractKeycloakTest {
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
IDToken idToken = retrieveIDToken(loginEvent);
IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent);
// Check that authTime is available and set to current time
int authTime = idToken.getAuthTime();
@ -123,25 +124,11 @@ public class OIDCAdvancedRequestParamsTest extends AbstractKeycloakTest {
oauth.openLoginForm();
loginEvent = events.expectLogin().assertEvent();
idToken = retrieveIDToken(loginEvent);
idToken = sendTokenRequestAndGetIDToken(loginEvent);
// Assert that authTime is still the same
int authTimeUpdated = idToken.getAuthTime();
Assert.assertEquals(authTime, authTimeUpdated);
}
private IDToken retrieveIDToken(EventRepresentation loginEvent) {
String sessionId = loginEvent.getSessionId();
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
Assert.assertEquals(200, response.getStatusCode());
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
events.expectCodeToToken(codeId, sessionId).assertEvent();
return idToken;
}
}