Terminating all sessions above the session limit (#16068)
Adjusts implementation of UserSessionLimitsAuthenticator to terminate all sessions above the session limit. Closes #14689 Co-authored-by: Marek Posolda <mposolda@gmail.com>
This commit is contained in:
parent
9995a3cdd4
commit
4ff180da64
2 changed files with 32 additions and 12 deletions
|
@ -71,12 +71,12 @@ public class UserSessionLimitsAuthenticator implements Authenticator {
|
|||
if (exceedsLimit(userSessionCountForRealm, userRealmLimit)) {
|
||||
logger.infof("Too many session in this realm for the current user. Session count: %s", userSessionCountForRealm);
|
||||
String eventDetails = String.format(realmEventDetailsTemplate, context.getRealm().getName(), userRealmLimit, userSessionCountForRealm, context.getUser().getId());
|
||||
handleLimitExceeded(context, userSessionsForRealm, eventDetails);
|
||||
handleLimitExceeded(context, userSessionsForRealm, eventDetails, userRealmLimit);
|
||||
} // otherwise if the user is still allowed to create a new session in the realm, check if this applies for this specific client as well.
|
||||
else if (exceedsLimit(userSessionCountForClient, userClientLimit)) {
|
||||
logger.infof("Too many sessions related to the current client for this user. Session count: %s", userSessionCountForRealm);
|
||||
String eventDetails = String.format(clientEventDetailsTemplate, context.getRealm().getName(), userClientLimit, userSessionCountForClient, context.getUser().getId());
|
||||
handleLimitExceeded(context, userSessionsForClient, eventDetails);
|
||||
handleLimitExceeded(context, userSessionsForClient, eventDetails, userClientLimit);
|
||||
} else {
|
||||
context.success();
|
||||
}
|
||||
|
@ -89,7 +89,11 @@ public class UserSessionLimitsAuthenticator implements Authenticator {
|
|||
if (limit <= 0) { // if limit is zero or negative, consider the limit disabled
|
||||
return false;
|
||||
}
|
||||
return count > limit - 1;
|
||||
return getNumberOfSessionsThatNeedToBeLoggedOut(count, limit) > 0;
|
||||
}
|
||||
|
||||
private long getNumberOfSessionsThatNeedToBeLoggedOut(long count, long limit) {
|
||||
return count - (limit - 1);
|
||||
}
|
||||
|
||||
private int getIntConfigProperty(String key, Map<String, String> config) {
|
||||
|
@ -135,7 +139,7 @@ public class UserSessionLimitsAuthenticator implements Authenticator {
|
|||
|
||||
}
|
||||
|
||||
private void handleLimitExceeded(AuthenticationFlowContext context, List<UserSessionModel> userSessions, String eventDetails) {
|
||||
private void handleLimitExceeded(AuthenticationFlowContext context, List<UserSessionModel> userSessions, String eventDetails, long limit) {
|
||||
switch (behavior) {
|
||||
case UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION:
|
||||
logger.info("Denying new session");
|
||||
|
@ -158,15 +162,24 @@ public class UserSessionLimitsAuthenticator implements Authenticator {
|
|||
|
||||
case UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION:
|
||||
logger.info("Terminating oldest session");
|
||||
logoutOldestSession(userSessions);
|
||||
logoutOldestSessions(userSessions, limit);
|
||||
context.success();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void logoutOldestSession(List<UserSessionModel> userSessions) {
|
||||
private void logoutOldestSessions(List<UserSessionModel> userSessions, long limit) {
|
||||
long numberOfSessionsThatNeedToBeLoggedOut = getNumberOfSessionsThatNeedToBeLoggedOut(userSessions.size(), limit);
|
||||
if (numberOfSessionsThatNeedToBeLoggedOut == 1) {
|
||||
logger.info("Logging out oldest session");
|
||||
Optional<UserSessionModel> oldest = userSessions.stream().sorted(Comparator.comparingInt(UserSessionModel::getLastSessionRefresh)).findFirst();
|
||||
oldest.ifPresent(userSession -> AuthenticationManager.backchannelLogout(session, userSession, true));
|
||||
} else {
|
||||
logger.infof("Logging out oldest %s sessions", numberOfSessionsThatNeedToBeLoggedOut);
|
||||
}
|
||||
|
||||
userSessions
|
||||
.stream()
|
||||
.sorted(Comparator.comparingInt(UserSessionModel::getLastSessionRefresh))
|
||||
.limit(numberOfSessionsThatNeedToBeLoggedOut)
|
||||
.forEach(userSession -> AuthenticationManager.backchannelLogout(session, userSession, true));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -212,14 +212,21 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
|
|||
public void testClientSessionCountExceededAndOldestSessionRemovedDirectGrantFlow() throws Exception {
|
||||
try {
|
||||
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION);
|
||||
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "2");
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
}
|
||||
testingClient.server(realmName).run(assertSessionCount(realmName, username, 2));
|
||||
|
||||
response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
|
||||
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "1");
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
testingClient.server(realmName).run(assertSessionCount(realmName, username, 1));
|
||||
} finally {
|
||||
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
|
||||
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "0");
|
||||
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "1");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue