Skip to content

Commit 990a5a4

Browse files
committed
feat: add user session removed ext
1 parent a3aca9a commit 990a5a4

File tree

3 files changed

+149
-0
lines changed

3 files changed

+149
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.github.bcgov.keycloak.authenticators;
2+
3+
import org.jboss.logging.Logger;
4+
import org.keycloak.authentication.Authenticator;
5+
import org.keycloak.models.KeycloakSession;
6+
import org.keycloak.models.RealmModel;
7+
import org.keycloak.models.UserModel;
8+
import org.keycloak.models.UserSessionProvider;
9+
import org.keycloak.services.managers.AuthenticationManager;
10+
import org.keycloak.authentication.AuthenticationFlowContext;
11+
import org.keycloak.sessions.AuthenticationSessionModel;
12+
13+
import java.util.Map;
14+
15+
public class UserSessionRemover implements Authenticator {
16+
17+
private static final Logger logger = Logger.getLogger(UserSessionRemover.class);
18+
19+
@Override
20+
public boolean requiresUser() {
21+
return false;
22+
}
23+
24+
@Override
25+
public void authenticate(AuthenticationFlowContext context) {
26+
AuthenticationSessionModel session = context.getAuthenticationSession();
27+
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(
28+
context.getSession(),
29+
context.getRealm(),
30+
true
31+
);
32+
33+
// 1. If no Cookie session, proceed to next step
34+
if (authResult == null) {
35+
context.attempted();
36+
return;
37+
}
38+
39+
// Need to use the KeycloakSession context to get the authenticating client ID. Not available on the AuthenticationFlowContext.
40+
KeycloakSession keycloakSession = context.getSession();
41+
String authenticatingClientUUID = keycloakSession.getContext().getClient().getId();
42+
43+
// Get all existing sessions. If any session is associated with a different client, clear all user sessions.
44+
UserSessionProvider userSessionProvider = keycloakSession.sessions();
45+
Map<String, Long> activeClientSessionStats = userSessionProvider.getActiveClientSessionStats(context.getRealm(), false);
46+
47+
for (String activeSessionClientUUID : activeClientSessionStats.keySet()) {
48+
if (!activeSessionClientUUID.equals(authenticatingClientUUID)) {
49+
userSessionProvider.removeUserSession(context.getRealm(), authResult.getSession());
50+
}
51+
}
52+
53+
context.attempted();
54+
}
55+
56+
@Override
57+
public void action(AuthenticationFlowContext context) {
58+
}
59+
60+
@Override
61+
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
62+
return true;
63+
}
64+
65+
@Override
66+
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
67+
}
68+
69+
@Override
70+
public void close() {
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.github.bcgov.keycloak.authenticators;
2+
3+
import java.util.List;
4+
import org.keycloak.Config;
5+
import org.keycloak.authentication.Authenticator;
6+
import org.keycloak.authentication.AuthenticatorFactory;
7+
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
8+
import org.keycloak.models.KeycloakSession;
9+
import org.keycloak.models.KeycloakSessionFactory;
10+
import org.keycloak.provider.ProviderConfigProperty;
11+
12+
public class UserSessionRemoverFactory implements AuthenticatorFactory {
13+
14+
protected static final Requirement[] REQUIREMENT_CHOICES = {
15+
Requirement.REQUIRED, Requirement.ALTERNATIVE, Requirement.DISABLED
16+
};
17+
18+
private static final Authenticator AUTHENTICATOR_INSTANCE = new UserSessionRemover();
19+
20+
@Override
21+
public String getId() {
22+
return "user-session-remover";
23+
}
24+
25+
@Override
26+
public String getDisplayType() {
27+
return "User Session Remover";
28+
}
29+
30+
@Override
31+
public String getHelpText() {
32+
return "Checks if the user session is realted to any other client, and removes it if so.";
33+
}
34+
35+
@Override
36+
public Authenticator create(KeycloakSession session) {
37+
return AUTHENTICATOR_INSTANCE;
38+
}
39+
40+
@Override
41+
public Requirement[] getRequirementChoices() {
42+
return REQUIREMENT_CHOICES;
43+
}
44+
45+
@Override
46+
public List<ProviderConfigProperty> getConfigProperties() {
47+
return null;
48+
}
49+
50+
@Override
51+
public String getReferenceCategory() {
52+
return null;
53+
}
54+
55+
@Override
56+
public boolean isConfigurable() {
57+
return false;
58+
}
59+
60+
@Override
61+
public boolean isUserSetupAllowed() {
62+
return true;
63+
}
64+
65+
@Override
66+
public void init(Config.Scope config) {
67+
}
68+
69+
@Override
70+
public void postInit(KeycloakSessionFactory factory) {
71+
}
72+
73+
@Override
74+
public void close() {
75+
}
76+
}

docker/keycloak/extensions-24/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ com.github.bcgov.keycloak.authenticators.IdentityProviderStopAuthenticatorFactor
22
com.github.bcgov.keycloak.authenticators.CookieStopAuthenticatorFactory
33
com.github.bcgov.keycloak.authenticators.ClientLoginAuthenticatorFactory
44
com.github.bcgov.keycloak.authenticators.ClientLoginRoleBindingFactory
5+
com.github.bcgov.keycloak.authenticators.UserSessionRemoverFactory
56
com.github.bcgov.keycloak.authenticators.UserAttributeAuthenticatorFactory
67
com.github.bcgov.keycloak.authenticators.broker.IdpDeleteUserIfDuplicateAuthenticatorFactory
78
com.github.bcgov.keycloak.authenticators.browser.IdentityProviderStopFormFactory

0 commit comments

Comments
 (0)