test sso idle, logout on idle
This commit is contained in:
parent
bc2360e985
commit
67e3e60f28
8 changed files with 251 additions and 46 deletions
|
@ -5,6 +5,7 @@ import org.jboss.resteasy.spi.HttpResponse;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.VerificationException;
|
import org.keycloak.VerificationException;
|
||||||
|
import org.keycloak.audit.Audit;
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
import org.keycloak.models.AuthenticationLinkModel;
|
import org.keycloak.models.AuthenticationLinkModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
@ -62,10 +63,28 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSessionValid(RealmModel realm, UserSessionModel session) {
|
public static boolean isSessionValid(RealmModel realm, UserSessionModel session) {
|
||||||
|
if (session == null) return false;
|
||||||
int currentTime = Time.currentTime();
|
int currentTime = Time.currentTime();
|
||||||
return session == null || session.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() < currentTime || session.getStarted() + realm.getSsoSessionMaxLifespan() < currentTime;
|
int max = session.getStarted() + realm.getSsoSessionMaxLifespan();
|
||||||
|
boolean valid = session != null && session.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime;
|
||||||
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void logout(RealmModel realm, UserSessionModel session, UriInfo uriInfo) {
|
||||||
|
if (session == null) return;
|
||||||
|
UserModel user = session.getUser();
|
||||||
|
|
||||||
|
logger.infov("Logging out: {0} ({1})", user.getLoginName(), session.getId());
|
||||||
|
|
||||||
|
realm.removeUserSession(session);
|
||||||
|
expireIdentityCookie(realm, uriInfo);
|
||||||
|
expireRememberMeCookie(realm, uriInfo);
|
||||||
|
|
||||||
|
new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user.getId(), session.getId());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public AccessToken createIdentityToken(RealmModel realm, UserModel user, UserSessionModel session) {
|
public AccessToken createIdentityToken(RealmModel realm, UserModel user, UserSessionModel session) {
|
||||||
logger.info("createIdentityToken");
|
logger.info("createIdentityToken");
|
||||||
AccessToken token = new AccessToken();
|
AccessToken token = new AccessToken();
|
||||||
|
@ -121,26 +140,26 @@ public class AuthenticationManager {
|
||||||
return encodedToken;
|
return encodedToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void expireIdentityCookie(RealmModel realm, UriInfo uriInfo) {
|
public static void expireIdentityCookie(RealmModel realm, UriInfo uriInfo) {
|
||||||
logger.debug("Expiring identity cookie");
|
logger.debug("Expiring identity cookie");
|
||||||
String path = getIdentityCookiePath(realm, uriInfo);
|
String path = getIdentityCookiePath(realm, uriInfo);
|
||||||
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true);
|
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true);
|
||||||
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false);
|
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false);
|
||||||
expireRememberMeCookie(realm, uriInfo);
|
expireRememberMeCookie(realm, uriInfo);
|
||||||
}
|
}
|
||||||
public void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo) {
|
public static void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo) {
|
||||||
logger.debug("Expiring remember me cookie");
|
logger.debug("Expiring remember me cookie");
|
||||||
String path = getIdentityCookiePath(realm, uriInfo);
|
String path = getIdentityCookiePath(realm, uriInfo);
|
||||||
String cookieName = KEYCLOAK_REMEMBER_ME;
|
String cookieName = KEYCLOAK_REMEMBER_ME;
|
||||||
expireCookie(realm, cookieName, path, true);
|
expireCookie(realm, cookieName, path, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getIdentityCookiePath(RealmModel realm, UriInfo uriInfo) {
|
protected static String getIdentityCookiePath(RealmModel realm, UriInfo uriInfo) {
|
||||||
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getName());
|
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getName());
|
||||||
return uri.getRawPath();
|
return uri.getRawPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly) {
|
public static void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly) {
|
||||||
logger.debugv("Expiring cookie: {0} path: {1}", cookieName, path);
|
logger.debugv("Expiring cookie: {0} path: {1}", cookieName, path);
|
||||||
boolean secureOnly = !realm.isSslNotRequired();
|
boolean secureOnly = !realm.isSslNotRequired();
|
||||||
CookieHelper.addCookie(cookieName, "", path, null, "Expiring cookie", 0, secureOnly, httpOnly);
|
CookieHelper.addCookie(cookieName, "", path, null, "Expiring cookie", 0, secureOnly, httpOnly);
|
||||||
|
@ -196,11 +215,8 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
UserSessionModel session = realm.getUserSession(token.getSessionState());
|
UserSessionModel session = realm.getUserSession(token.getSessionState());
|
||||||
int currentTime = Time.currentTime();
|
if (!isSessionValid(realm, session)) {
|
||||||
if (isSessionValid(realm, session)) {
|
if (session != null) logout(realm, session, uriInfo);
|
||||||
if (session != null) {
|
|
||||||
realm.removeUserSession(session);
|
|
||||||
}
|
|
||||||
logger.info("User session not active");
|
logger.info("User session not active");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.representations.RefreshToken;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -106,7 +107,7 @@ public class TokenManager {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccessToken refreshAccessToken(RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException {
|
public AccessToken refreshAccessToken(UriInfo uriInfo, RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException {
|
||||||
JWSInput jws = new JWSInput(encodedRefreshToken);
|
JWSInput jws = new JWSInput(encodedRefreshToken);
|
||||||
RefreshToken refreshToken = null;
|
RefreshToken refreshToken = null;
|
||||||
try {
|
try {
|
||||||
|
@ -138,10 +139,8 @@ public class TokenManager {
|
||||||
|
|
||||||
UserSessionModel session = realm.getUserSession(refreshToken.getSessionState());
|
UserSessionModel session = realm.getUserSession(refreshToken.getSessionState());
|
||||||
int currentTime = Time.currentTime();
|
int currentTime = Time.currentTime();
|
||||||
if (AuthenticationManager.isSessionValid(realm, session)) {
|
if (!AuthenticationManager.isSessionValid(realm, session)) {
|
||||||
if (session != null) {
|
AuthenticationManager.logout(realm, session, uriInfo);
|
||||||
realm.removeUserSession(session);
|
|
||||||
}
|
|
||||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
|
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -408,7 +408,8 @@ public class RequiredActionsService {
|
||||||
AuthenticationManager authManager = new AuthenticationManager(providerSession);
|
AuthenticationManager authManager = new AuthenticationManager(providerSession);
|
||||||
|
|
||||||
UserSessionModel session = realm.getUserSession(accessCode.getSessionState());
|
UserSessionModel session = realm.getUserSession(accessCode.getSessionState());
|
||||||
if (AuthenticationManager.isSessionValid(realm, session)) {
|
if (!AuthenticationManager.isSessionValid(realm, session)) {
|
||||||
|
AuthenticationManager.logout(realm, session, uriInfo);
|
||||||
return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectError(accessCode.getClient(), "access_denied", accessCode.getState(), accessCode.getRedirectUri());
|
return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectError(accessCode.getClient(), "access_denied", accessCode.getState(), accessCode.getRedirectUri());
|
||||||
}
|
}
|
||||||
audit.session(session);
|
audit.session(session);
|
||||||
|
|
|
@ -296,13 +296,14 @@ public class TokenService {
|
||||||
String refreshToken = form.getFirst(OAuth2Constants.REFRESH_TOKEN);
|
String refreshToken = form.getFirst(OAuth2Constants.REFRESH_TOKEN);
|
||||||
AccessToken accessToken = null;
|
AccessToken accessToken = null;
|
||||||
try {
|
try {
|
||||||
accessToken = tokenManager.refreshAccessToken(realm, client, refreshToken, audit);
|
accessToken = tokenManager.refreshAccessToken(uriInfo, realm, client, refreshToken, audit);
|
||||||
} catch (OAuthErrorException e) {
|
} catch (OAuthErrorException e) {
|
||||||
Map<String, String> error = new HashMap<String, String>();
|
Map<String, String> error = new HashMap<String, String>();
|
||||||
error.put(OAuth2Constants.ERROR, e.getError());
|
error.put(OAuth2Constants.ERROR, e.getError());
|
||||||
if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
|
if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
|
||||||
audit.error(Errors.INVALID_TOKEN);
|
audit.error(Errors.INVALID_TOKEN);
|
||||||
throw new BadRequestException("OAuth Error", e, Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
|
logger.error("OAuth Error", e);
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
|
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
|
||||||
|
@ -635,7 +636,8 @@ public class TokenService {
|
||||||
}
|
}
|
||||||
|
|
||||||
UserSessionModel session = realm.getUserSession(accessCode.getSessionState());
|
UserSessionModel session = realm.getUserSession(accessCode.getSessionState());
|
||||||
if (AuthenticationManager.isSessionValid(realm, session)) {
|
if (!AuthenticationManager.isSessionValid(realm, session)) {
|
||||||
|
AuthenticationManager.logout(realm, session, uriInfo);
|
||||||
Map<String, String> res = new HashMap<String, String>();
|
Map<String, String> res = new HashMap<String, String>();
|
||||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||||
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Session not active");
|
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Session not active");
|
||||||
|
@ -854,15 +856,7 @@ public class TokenService {
|
||||||
|
|
||||||
private void logout(UserSessionModel session) {
|
private void logout(UserSessionModel session) {
|
||||||
UserModel user = session.getUser();
|
UserModel user = session.getUser();
|
||||||
|
authManager.logout(realm, session, uriInfo);
|
||||||
logger.infov("Logging out: {0} ({1})", user.getLoginName(), session.getId());
|
|
||||||
|
|
||||||
realm.removeUserSession(session);
|
|
||||||
authManager.expireIdentityCookie(realm, uriInfo);
|
|
||||||
authManager.expireRememberMeCookie(realm, uriInfo);
|
|
||||||
|
|
||||||
resourceAdminManager.logoutUser(uriInfo.getRequestUri(), realm, user.getId(), session.getId());
|
|
||||||
|
|
||||||
audit.user(user).session(session).success();
|
audit.user(user).session(session).success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -914,8 +908,8 @@ public class TokenService {
|
||||||
}
|
}
|
||||||
|
|
||||||
UserSessionModel session = realm.getUserSession(accessCodeEntry.getSessionState());
|
UserSessionModel session = realm.getUserSession(accessCodeEntry.getSessionState());
|
||||||
int currentTime = Time.currentTime();
|
if (!AuthenticationManager.isSessionValid(realm, session)) {
|
||||||
if (AuthenticationManager.isSessionValid(realm, session)) {
|
AuthenticationManager.logout(realm, session, uriInfo);
|
||||||
audit.error(Errors.INVALID_CODE);
|
audit.error(Errors.INVALID_CODE);
|
||||||
return oauth.forwardToSecurityFailure("Session not active");
|
return oauth.forwardToSecurityFailure("Session not active");
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.junit.Test;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
@ -156,4 +157,67 @@ public class AdapterTest {
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginSSOIdle() throws Exception {
|
||||||
|
// test login to customer-portal which does a bearer request to customer-db
|
||||||
|
driver.navigate().to("http://localhost:8081/customer-portal");
|
||||||
|
System.out.println("Current url: " + driver.getCurrentUrl());
|
||||||
|
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
|
||||||
|
loginPage.login("bburke@redhat.com", "password");
|
||||||
|
System.out.println("Current url: " + driver.getCurrentUrl());
|
||||||
|
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
|
||||||
|
String pageSource = driver.getPageSource();
|
||||||
|
System.out.println(pageSource);
|
||||||
|
Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
|
||||||
|
|
||||||
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
RealmModel realm = session.getRealmByName("demo");
|
||||||
|
int originalIdle = realm.getSsoSessionIdleTimeout();
|
||||||
|
realm.setSsoSessionIdleTimeout(1);
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
||||||
|
Thread.sleep(2000);
|
||||||
|
|
||||||
|
|
||||||
|
// test SSO
|
||||||
|
driver.navigate().to("http://localhost:8081/product-portal");
|
||||||
|
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
|
||||||
|
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
realm = session.getRealmByName("demo");
|
||||||
|
realm.setSsoSessionIdleTimeout(originalIdle);
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void testLoginSSOMax() throws Exception {
|
||||||
|
// test login to customer-portal which does a bearer request to customer-db
|
||||||
|
driver.navigate().to("http://localhost:8081/customer-portal");
|
||||||
|
System.out.println("Current url: " + driver.getCurrentUrl());
|
||||||
|
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
|
||||||
|
loginPage.login("bburke@redhat.com", "password");
|
||||||
|
System.out.println("Current url: " + driver.getCurrentUrl());
|
||||||
|
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
|
||||||
|
String pageSource = driver.getPageSource();
|
||||||
|
System.out.println(pageSource);
|
||||||
|
Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
|
||||||
|
|
||||||
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
RealmModel realm = session.getRealmByName("demo");
|
||||||
|
int original = realm.getSsoSessionMaxLifespan();
|
||||||
|
realm.setSsoSessionMaxLifespan(1);
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
||||||
|
Thread.sleep(2000);
|
||||||
|
|
||||||
|
|
||||||
|
// test SSO
|
||||||
|
driver.navigate().to("http://localhost:8081/product-portal");
|
||||||
|
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
|
||||||
|
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
realm = session.getRealmByName("demo");
|
||||||
|
realm.setSsoSessionMaxLifespan(original);
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,14 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.audit.Details;
|
import org.keycloak.audit.Details;
|
||||||
import org.keycloak.audit.Errors;
|
import org.keycloak.audit.Errors;
|
||||||
import org.keycloak.audit.Event;
|
import org.keycloak.audit.Event;
|
||||||
|
import org.keycloak.models.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.provider.ProviderSession;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.RefreshToken;
|
import org.keycloak.representations.RefreshToken;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
|
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
|
||||||
|
@ -41,9 +47,7 @@ import org.keycloak.testsuite.rule.WebRule;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.allOf;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
|
||||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
@ -162,4 +166,131 @@ public class RefreshTokenTest {
|
||||||
events.clear();
|
events.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserSessionRefreshAndIdle() throws Exception {
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
|
||||||
|
Event loginEvent = events.expectLogin().assertEvent();
|
||||||
|
|
||||||
|
String sessionId = loginEvent.getSessionId();
|
||||||
|
|
||||||
|
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||||
|
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||||
|
|
||||||
|
events.poll();
|
||||||
|
|
||||||
|
String refreshId = oauth.verifyRefreshToken(tokenResponse.getRefreshToken()).getId();
|
||||||
|
|
||||||
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
RealmModel realm = session.getRealmByName("test");
|
||||||
|
UserSessionModel userSession = realm.getUserSession(sessionId);
|
||||||
|
int last = userSession.getLastSessionRefresh();
|
||||||
|
keycloakRule.stopSession(session, false);
|
||||||
|
|
||||||
|
Thread.sleep(2000);
|
||||||
|
|
||||||
|
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||||
|
|
||||||
|
AccessToken refreshedToken = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||||
|
RefreshToken refreshedRefreshToken = oauth.verifyRefreshToken(tokenResponse.getRefreshToken());
|
||||||
|
|
||||||
|
Assert.assertEquals(200, tokenResponse.getStatusCode());
|
||||||
|
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
realm = session.getRealmByName("test");
|
||||||
|
userSession = realm.getUserSession(sessionId);
|
||||||
|
int next = userSession.getLastSessionRefresh();
|
||||||
|
keycloakRule.stopSession(session, false);
|
||||||
|
|
||||||
|
// should not update last refresh because the access token interval is way less than idle timeout
|
||||||
|
Assert.assertEquals(last, next);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
realm = session.getRealmByName("test");
|
||||||
|
int lastAccessTokenLifespan = realm.getAccessTokenLifespan();
|
||||||
|
realm.setAccessTokenLifespan(100000);
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
||||||
|
Thread.sleep(2000);
|
||||||
|
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||||
|
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
realm = session.getRealmByName("test");
|
||||||
|
userSession = realm.getUserSession(sessionId);
|
||||||
|
next = userSession.getLastSessionRefresh();
|
||||||
|
keycloakRule.stopSession(session, false);
|
||||||
|
|
||||||
|
// lastSEssionRefresh should be updated because access code lifespan is higher than sso idle timeout
|
||||||
|
Assert.assertThat(next, allOf(greaterThan(last), lessThan(last + 6)));
|
||||||
|
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
realm = session.getRealmByName("test");
|
||||||
|
int originalIdle = realm.getSsoSessionIdleTimeout();
|
||||||
|
realm.setSsoSessionIdleTimeout(1);
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
Thread.sleep(2000);
|
||||||
|
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||||
|
|
||||||
|
// test idle timeout
|
||||||
|
assertEquals(400, tokenResponse.getStatusCode());
|
||||||
|
assertNull(tokenResponse.getAccessToken());
|
||||||
|
assertNull(tokenResponse.getRefreshToken());
|
||||||
|
|
||||||
|
events.expectRefresh(refreshId, sessionId).error(Errors.INVALID_TOKEN);
|
||||||
|
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
realm = session.getRealmByName("test");
|
||||||
|
realm.setSsoSessionIdleTimeout(originalIdle);
|
||||||
|
realm.setAccessTokenLifespan(lastAccessTokenLifespan);
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void refreshTokenUserSessionMaxLifespan() throws Exception {
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
|
||||||
|
Event loginEvent = events.expectLogin().assertEvent();
|
||||||
|
|
||||||
|
String sessionId = loginEvent.getSessionId();
|
||||||
|
|
||||||
|
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||||
|
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||||
|
|
||||||
|
events.poll();
|
||||||
|
|
||||||
|
String refreshId = oauth.verifyRefreshToken(tokenResponse.getRefreshToken()).getId();
|
||||||
|
|
||||||
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
RealmModel realm = session.getRealmByName("test");
|
||||||
|
int maxLifespan = realm.getSsoSessionMaxLifespan();
|
||||||
|
realm.setSsoSessionMaxLifespan(1);
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||||
|
|
||||||
|
assertEquals(400, tokenResponse.getStatusCode());
|
||||||
|
assertNull(tokenResponse.getAccessToken());
|
||||||
|
assertNull(tokenResponse.getRefreshToken());
|
||||||
|
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
realm = session.getRealmByName("test");
|
||||||
|
realm.setSsoSessionMaxLifespan(maxLifespan);
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
||||||
|
|
||||||
|
events.expectRefresh(refreshId, sessionId).error(Errors.INVALID_TOKEN);
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,4 +133,17 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
|
||||||
byte[] bytes = os.toByteArray();
|
byte[] bytes = os.toByteArray();
|
||||||
return JsonSerialization.readValue(bytes, RealmRepresentation.class);
|
return JsonSerialization.readValue(bytes, RealmRepresentation.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KeycloakSession startSession() {
|
||||||
|
KeycloakSession session = server.getKeycloakSessionFactory().createSession();
|
||||||
|
session.getTransaction().begin();
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopSession(KeycloakSession session, boolean commit) {
|
||||||
|
if (commit) {
|
||||||
|
session.getTransaction().commit();
|
||||||
|
}
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,19 +81,6 @@ public class KeycloakRule extends AbstractKeycloakRule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeycloakSession startSession() {
|
|
||||||
KeycloakSession session = server.getKeycloakSessionFactory().createSession();
|
|
||||||
session.getTransaction().begin();
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopSession(KeycloakSession session, boolean commit) {
|
|
||||||
if (commit) {
|
|
||||||
session.getTransaction().commit();
|
|
||||||
}
|
|
||||||
session.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeUserSession(String sessionId) {
|
public void removeUserSession(String sessionId) {
|
||||||
KeycloakSession keycloakSession = startSession();
|
KeycloakSession keycloakSession = startSession();
|
||||||
RealmModel realm = keycloakSession.getRealm("test");
|
RealmModel realm = keycloakSession.getRealm("test");
|
||||||
|
|
Loading…
Reference in a new issue