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 c6f87b8f1c..b0240e8d0d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -648,12 +648,16 @@ public class TokenManager {
token.setSessionState(session.getId());
- token.expiration(getTokenExpiration(realm, client, session, clientSession));
+ ClientScopeModel offlineAccessScope = KeycloakModelUtils.getClientScopeByName(realm, OAuth2Constants.OFFLINE_ACCESS);
+ boolean offlineTokenRequested = offlineAccessScope == null ? false
+ : clientSessionCtx.getClientScopeIds().contains(offlineAccessScope.getId());
+ token.expiration(getTokenExpiration(realm, client, session, clientSession, offlineTokenRequested));
return token;
}
- private int getTokenExpiration(RealmModel realm, ClientModel client, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
+ private int getTokenExpiration(RealmModel realm, ClientModel client, UserSessionModel userSession,
+ AuthenticatedClientSessionModel clientSession, boolean offlineTokenRequested) {
boolean implicitFlow = false;
String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
if (responseType != null) {
@@ -681,9 +685,16 @@ public class TokenManager {
expiration = Time.currentTime() + tokenLifespan;
}
- if (!userSession.isOffline()) {
- int sessionExpires = userSession.getStarted() + (userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ?
- realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan());
+ if (userSession.isOffline() || offlineTokenRequested) {
+ if (realm.isOfflineSessionMaxLifespanEnabled()) {
+ int sessionExpires = userSession.getStarted() + realm.getOfflineSessionMaxLifespan();
+ expiration = expiration <= sessionExpires ? expiration : sessionExpires;
+ }
+ } else {
+ int sessionExpires = userSession.getStarted()
+ + (userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0
+ ? realm.getSsoSessionMaxLifespanRememberMe()
+ : realm.getSsoSessionMaxLifespan());
expiration = expiration <= sessionExpires ? expiration : sessionExpires;
}
@@ -779,6 +790,8 @@ public class TokenManager {
refreshToken = new RefreshToken(accessToken);
refreshToken.type(TokenUtil.TOKEN_TYPE_OFFLINE);
+ if (realm.isOfflineSessionMaxLifespanEnabled())
+ refreshToken.expiration(getOfflineExpiration());
sessionManager.createOrUpdateOfflineSession(clientSessionCtx.getClientSession(), userSession);
} else {
refreshToken = new RefreshToken(accessToken);
@@ -828,6 +841,13 @@ public class TokenManager {
return expiration <= sessionExpires ? expiration : sessionExpires;
}
+ private int getOfflineExpiration() {
+ int expiration = Time.currentTime() + realm.getOfflineSessionIdleTimeout();
+ int sessionExpires = userSession.getStarted() + realm.getOfflineSessionMaxLifespan();
+
+ 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/oauth/OfflineTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
index a2cbcb5990..4a3ff18c94 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
@@ -70,6 +70,9 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertFalse;
@@ -81,6 +84,9 @@ import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsernameId;
import static org.keycloak.testsuite.util.OAuthClient.APP_ROOT;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
/**
* @author Marek Posolda
*/
@@ -557,7 +563,9 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
// Login as admin and see consents of user
UserResource user = ApiUtil.findUserByUsernameId(appRealm, "test-user@localhost");
List