Merge pull request #3032 from mposolda/KEYCLOAK-3223
KEYCLOAK-3223 Basic support for acr claim
This commit is contained in:
commit
bffbc9e198
7 changed files with 79 additions and 33 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue