keycloak-aplcache
Changes
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java 5(+5 -0)
model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java 5(+5 -0)
server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java 5(+5 -0)
server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java 9(+8 -1)
Details
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
index d0c2a4a..9e58a2f 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
@@ -225,6 +225,11 @@ public class UserSessionAdapter implements UserSessionModel {
}
@Override
+ public boolean isOffline() {
+ return offline;
+ }
+
+ @Override
public String getNote(String name) {
return entity.getNotes() != null ? entity.getNotes().get(name) : null;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
index 64246e8..dd49136 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
@@ -252,6 +252,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
model.setUserSessionId(entity.getUserSessionId());
model.setLastSessionRefresh(entity.getLastSessionRefresh());
model.setData(entity.getData());
+ model.setOffline(offlineFromString(entity.getOffline()));
Map<String, AuthenticatedClientSessionModel> clientSessions = new HashMap<>();
return new PersistentUserSessionAdapter(model, realm, user, clientSessions);
@@ -287,4 +288,8 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
private String offlineToString(boolean offline) {
return offline ? "1" : "0";
}
+
+ private boolean offlineFromString(String offlineStr) {
+ return "1".equals(offlineStr);
+ }
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java b/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
index 40fdada..5cb9fd3 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -53,6 +53,8 @@ public interface UserSessionModel {
void setLastSessionRefresh(int seconds);
+ boolean isOffline();
+
/**
* Returns map where key is ID of the client (its UUID) and value is ID respective {@link AuthenticatedClientSessionModel} object.
* @return
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
index 095a857..87df79f 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
@@ -157,6 +157,11 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
}
@Override
+ public boolean isOffline() {
+ return model.isOffline();
+ }
+
+ @Override
public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
return authenticatedClientSessions;
}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java
index d7e0a04..ced1768 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java
@@ -24,7 +24,7 @@ public class PersistentUserSessionModel {
private String userSessionId;
private int lastSessionRefresh;
-
+ private boolean offline;
private String data;
public String getUserSessionId() {
@@ -43,6 +43,13 @@ public class PersistentUserSessionModel {
this.lastSessionRefresh = lastSessionRefresh;
}
+ public boolean isOffline() {
+ return offline;
+ }
+
+ public void setOffline(boolean offline) {
+ this.offline = offline;
+ }
public String getData() {
return data;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 47f44e5..dd94094 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -635,11 +635,8 @@ public class TokenManager {
token.setSessionState(session.getId());
+ token.expiration(getTokenExpiration(realm, session, clientSession));
- int tokenLifespan = getTokenLifespan(realm, clientSession);
- if (tokenLifespan > 0) {
- token.expiration(Time.currentTime() + tokenLifespan);
- }
Set<String> allowedOrigins = client.getWebOrigins();
if (allowedOrigins != null) {
token.setAllowedOrigins(WebOriginsUtils.resolveValidWebOrigins(uriInfo, client));
@@ -647,13 +644,22 @@ public class TokenManager {
return token;
}
- private int getTokenLifespan(RealmModel realm, AuthenticatedClientSessionModel clientSession) {
+ private int getTokenExpiration(RealmModel realm, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
boolean implicitFlow = false;
String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
if (responseType != null) {
implicitFlow = OIDCResponseType.parse(responseType).isImplicitFlow();
}
- return implicitFlow ? realm.getAccessTokenLifespanForImplicitFlow() : realm.getAccessTokenLifespan();
+ int tokenLifespan = implicitFlow ? realm.getAccessTokenLifespanForImplicitFlow() : realm.getAccessTokenLifespan();
+
+ int expiration = Time.currentTime() + tokenLifespan;
+
+ if (!userSession.isOffline()) {
+ int sessionExpires = userSession.getStarted() + realm.getSsoSessionMaxLifespan();
+ expiration = expiration <= sessionExpires ? expiration : sessionExpires;
+ }
+
+ return expiration;
}
protected void addComposites(AccessToken token, RoleModel role) {
@@ -765,13 +771,19 @@ public class TokenManager {
sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
} else {
refreshToken = new RefreshToken(accessToken);
- refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
+ refreshToken.expiration(getRefreshExpiration());
}
refreshToken.id(KeycloakModelUtils.generateId());
refreshToken.issuedNow();
return this;
}
+ private int getRefreshExpiration() {
+ int sessionExpires = userSession.getStarted() + realm.getSsoSessionMaxLifespan();
+ int expiration = Time.currentTime() + realm.getSsoSessionIdleTimeout();
+ return expiration <= sessionExpires ? expiration : sessionExpires;
+ }
+
public AccessTokenResponseBuilder generateIDToken() {
if (accessToken == null) {
throw new IllegalStateException("accessToken not set");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/Assert.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/Assert.java
index a9c93f6..4fc045b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/Assert.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/Assert.java
@@ -34,6 +34,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@@ -133,4 +137,9 @@ public class Assert extends org.junit.Assert {
Assert.assertEquals(helpText, property.getHelpText());
Assert.assertEquals(type, property.getType());
}
+
+ public static void assertExpiration(int actual, int expected) {
+ org.junit.Assert.assertThat(actual, allOf(greaterThanOrEqualTo(expected - 50), lessThanOrEqualTo(expected)));
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index 961bf19..cfb97ce 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -34,6 +34,7 @@ import org.keycloak.admin.client.resource.ClientTemplateResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.enums.SslRequired;
+import org.keycloak.common.util.Time;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.jose.jws.JWSHeader;
@@ -80,6 +81,7 @@ import java.io.IOException;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@@ -94,8 +96,8 @@ import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsernameId;
import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
import static org.keycloak.testsuite.util.ProtocolMapperUtil.createRoleNameMapper;
+import static org.keycloak.testsuite.Assert.assertExpiration;
-import org.keycloak.util.TokenUtil;
import org.openqa.selenium.By;
/**
@@ -974,6 +976,46 @@ public class AccessTokenTest extends AbstractKeycloakTest {
}
}
+ // KEYCLOAK-4215
+ @Test
+ public void expiration() throws Exception {
+ int sessionMax = (int) TimeUnit.MINUTES.toSeconds(30);
+ int sessionIdle = (int) TimeUnit.MINUTES.toSeconds(30);
+ int tokenLifespan = (int) TimeUnit.MINUTES.toSeconds(5);
+
+ RealmResource realm = adminClient.realm("test");
+ RealmRepresentation rep = realm.toRepresentation();
+ Integer originalSessionMax = rep.getSsoSessionMaxLifespan();
+ rep.setSsoSessionMaxLifespan(sessionMax);
+ realm.update(rep);
+
+ try {
+ oauth.doLogin("test-user@localhost", "password");
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+ assertEquals(200, response.getStatusCode());
+
+ // Assert refresh expiration equals session idle
+ assertExpiration(response.getRefreshExpiresIn(), sessionIdle);
+
+ // Assert token expiration equals token lifespan
+ assertExpiration(response.getExpiresIn(), tokenLifespan);
+
+ setTimeOffset(sessionMax - 60);
+
+ response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password");
+ assertEquals(200, response.getStatusCode());
+
+ // Assert expiration equals session expiration
+ assertExpiration(response.getRefreshExpiresIn(), 60);
+ assertExpiration(response.getExpiresIn(), 60);
+ } finally {
+ rep.setSsoSessionMaxLifespan(originalSessionMax);
+ realm.update(rep);
+ }
+ }
+
private IDToken getIdToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws JWSInputException {
JWSInput input = new JWSInput(tokenResponse.getIdToken());
return input.readJsonContent(IDToken.class);