mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -03:30
Add logout event to UserSessionLimitsAuthenticator
Closes #44843 Signed-off-by: Robin Meese <39960884+robson90@users.noreply.github.com> Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com> Co-authored-by: Alexander Schwartz <alexander.schwartz@ibm.com>
This commit is contained in:
parent
ac557234a2
commit
35ee49b5d4
@ -39,11 +39,13 @@ only the basic attributes in representations or all of them.
|
|||||||
The `UserProfile` interface is a private API and should not be implemented by custom code. However, if you have extensions that
|
The `UserProfile` interface is a private API and should not be implemented by custom code. However, if you have extensions that
|
||||||
implement this interface, you will need to update your code to accommodate this new method.
|
implement this interface, you will need to update your code to accommodate this new method.
|
||||||
|
|
||||||
=== LOGOUT events when logging out sessions via the Account Console
|
=== Additional LOGOUT events
|
||||||
|
|
||||||
When logging out sessions via the Account Console {project_name} now creates LOGOUT user events to track this activity.
|
When logging out sessions via the Account Console {project_name} now creates LOGOUT user events to track this activity.
|
||||||
The events are connected to the `account` client.
|
The events are connected to the `account` client.
|
||||||
|
|
||||||
|
When the number of sessions for a user is limited, and you have configured the oldest session to be logged out once the limit is reached, {project_name} now creates LOGOUT user events to track this. The events are connected to the client that triggered the new login.
|
||||||
|
|
||||||
=== Identity Provider refactoring
|
=== Identity Provider refactoring
|
||||||
|
|
||||||
The private SPI for identity providers has been refactored. This is to allow identity providers to support more use
|
The private SPI for identity providers has been refactored. This is to allow identity providers to support more use
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import org.keycloak.authentication.AuthenticationFlowError;
|
|||||||
import org.keycloak.authentication.AuthenticationFlowException;
|
import org.keycloak.authentication.AuthenticationFlowException;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.AuthenticatorConfigModel;
|
import org.keycloak.models.AuthenticatorConfigModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
@ -179,7 +181,7 @@ public class UserSessionLimitsAuthenticator implements Authenticator {
|
|||||||
|
|
||||||
case UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION:
|
case UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION:
|
||||||
logger.info("Terminating oldest session");
|
logger.info("Terminating oldest session");
|
||||||
var removedSessions = logoutOldestSessions(userSessions, limit);
|
var removedSessions = logoutOldestSessions(userSessions, limit, context.getEvent());
|
||||||
context.success();
|
context.success();
|
||||||
return removedSessions;
|
return removedSessions;
|
||||||
}
|
}
|
||||||
@ -190,7 +192,7 @@ public class UserSessionLimitsAuthenticator implements Authenticator {
|
|||||||
/**
|
/**
|
||||||
* @return A list of logged-out user sessions, if any.
|
* @return A list of logged-out user sessions, if any.
|
||||||
*/
|
*/
|
||||||
private List<UserSessionModel> logoutOldestSessions(List<UserSessionModel> userSessions, long limit) {
|
private List<UserSessionModel> logoutOldestSessions(List<UserSessionModel> userSessions, long limit, EventBuilder eventBuilder) {
|
||||||
long numberOfSessionsThatNeedToBeLoggedOut = getNumberOfSessionsThatNeedToBeLoggedOut(userSessions.size(), limit);
|
long numberOfSessionsThatNeedToBeLoggedOut = getNumberOfSessionsThatNeedToBeLoggedOut(userSessions.size(), limit);
|
||||||
if (numberOfSessionsThatNeedToBeLoggedOut == 1) {
|
if (numberOfSessionsThatNeedToBeLoggedOut == 1) {
|
||||||
logger.info("Logging out oldest session");
|
logger.info("Logging out oldest session");
|
||||||
@ -206,6 +208,11 @@ public class UserSessionLimitsAuthenticator implements Authenticator {
|
|||||||
|
|
||||||
for (UserSessionModel userSession : userSessionsToBeRemoved) {
|
for (UserSessionModel userSession : userSessionsToBeRemoved) {
|
||||||
AuthenticationManager.backchannelLogout(session, userSession, true);
|
AuthenticationManager.backchannelLogout(session, userSession, true);
|
||||||
|
eventBuilder.clone()
|
||||||
|
.event(EventType.LOGOUT)
|
||||||
|
.user(userSession.getUser())
|
||||||
|
.session(userSession.getId())
|
||||||
|
.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
return userSessionsToBeRemoved;
|
return userSessionsToBeRemoved;
|
||||||
|
|||||||
@ -46,6 +46,7 @@ import org.keycloak.testsuite.util.GreenMailRule;
|
|||||||
import org.keycloak.testsuite.util.MailUtils;
|
import org.keycloak.testsuite.util.MailUtils;
|
||||||
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||||
|
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
@ -171,14 +172,24 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
|
|||||||
// Login and verify login was successful
|
// Login and verify login was successful
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
events.expectLogin().assertEvent();
|
EventRepresentation initialLoginEvent = events.expectLogin().assertEvent();
|
||||||
|
String userId = initialLoginEvent.getUserId();
|
||||||
|
String initialLoginSessionID = initialLoginEvent.getSessionId();
|
||||||
|
|
||||||
// Delete the cookies, while maintaining the server side session active
|
// Delete the cookies, while maintaining the server side session active
|
||||||
super.deleteCookies();
|
super.deleteCookies();
|
||||||
|
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
events.expectLogin().assertEvent();
|
// assert we have a logout session event, as the authenticator should have deleted the first session.
|
||||||
|
events.expect(EventType.LOGOUT)
|
||||||
|
.user(userId)
|
||||||
|
.session(initialLoginSessionID)
|
||||||
|
.assertEvent();
|
||||||
|
// User is first logged out, then logged in with a fresh sessionId
|
||||||
|
events.expectLogin()
|
||||||
|
.session(Matchers.not(initialLoginSessionID))
|
||||||
|
.assertEvent();
|
||||||
testingClient.server(realmName).run(assertSessionCount(realmName, username, 1));
|
testingClient.server(realmName).run(assertSessionCount(realmName, username, 1));
|
||||||
} finally {
|
} finally {
|
||||||
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
|
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
|
||||||
@ -217,14 +228,24 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
|
|||||||
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "0");
|
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "0");
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
events.expectLogin().assertEvent();
|
EventRepresentation initialLoginEvent = events.expectLogin().assertEvent();
|
||||||
|
String userId = initialLoginEvent.getUserId();
|
||||||
|
String initialLoginSessionID = initialLoginEvent.getSessionId();
|
||||||
|
|
||||||
// Delete the cookies, while maintaining the server side session active
|
// Delete the cookies, while maintaining the server side session active
|
||||||
super.deleteCookies();
|
super.deleteCookies();
|
||||||
|
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
events.expectLogin().assertEvent();
|
// assert we have a logout session event, as the authenticator should have deleted the first session.
|
||||||
|
events.expect(EventType.LOGOUT)
|
||||||
|
.user(userId)
|
||||||
|
.session(initialLoginSessionID)
|
||||||
|
.assertEvent();
|
||||||
|
// User is first logged out, then logged in with a fresh sessionId
|
||||||
|
events.expectLogin()
|
||||||
|
.session(Matchers.not(initialLoginSessionID))
|
||||||
|
.assertEvent();
|
||||||
testingClient.server(realmName).run(assertSessionCount(realmName, username, 1));
|
testingClient.server(realmName).run(assertSessionCount(realmName, username, 1));
|
||||||
} finally {
|
} finally {
|
||||||
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
|
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user