KEYCLOAK-16616 Limit number of authSessios per rootAuthSession

This commit is contained in:
Martin Kanis 2021-05-25 16:55:41 +02:00 committed by Hynek Mlnařík
parent 122fbe1bc6
commit 23aee6c210
16 changed files with 228 additions and 21 deletions

View file

@ -291,4 +291,18 @@ public class AuthenticationSessionAdapter implements AuthenticationSessionModel
update(); update();
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof AuthenticationSessionModel)) return false;
AuthenticationSessionModel that = (AuthenticationSessionModel) o;
return that.getTabId().equals(getTabId());
}
@Override
public int hashCode() {
return getTabId().hashCode();
}
} }

View file

@ -51,13 +51,16 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
private final KeycloakSession session; private final KeycloakSession session;
private final Cache<String, RootAuthenticationSessionEntity> cache; private final Cache<String, RootAuthenticationSessionEntity> cache;
private final InfinispanKeyGenerator keyGenerator; private final InfinispanKeyGenerator keyGenerator;
private final int authSessionsLimit;
protected final InfinispanKeycloakTransaction tx; protected final InfinispanKeycloakTransaction tx;
protected final SessionEventsSenderTransaction clusterEventsSenderTx; protected final SessionEventsSenderTransaction clusterEventsSenderTx;
public InfinispanAuthenticationSessionProvider(KeycloakSession session, InfinispanKeyGenerator keyGenerator, Cache<String, RootAuthenticationSessionEntity> cache) { public InfinispanAuthenticationSessionProvider(KeycloakSession session, InfinispanKeyGenerator keyGenerator,
Cache<String, RootAuthenticationSessionEntity> cache, int authSessionsLimit) {
this.session = session; this.session = session;
this.cache = cache; this.cache = cache;
this.keyGenerator = keyGenerator; this.keyGenerator = keyGenerator;
this.authSessionsLimit = authSessionsLimit;
this.tx = new InfinispanKeycloakTransaction(); this.tx = new InfinispanKeycloakTransaction();
this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session); this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session);
@ -88,7 +91,7 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
private RootAuthenticationSessionAdapter wrap(RealmModel realm, RootAuthenticationSessionEntity entity) { private RootAuthenticationSessionAdapter wrap(RealmModel realm, RootAuthenticationSessionEntity entity) {
return entity==null ? null : new RootAuthenticationSessionAdapter(session, this, cache, realm, entity); return entity==null ? null : new RootAuthenticationSessionAdapter(session, this, cache, realm, entity, authSessionsLimit);
} }

View file

@ -54,8 +54,14 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
private volatile Cache<String, RootAuthenticationSessionEntity> authSessionsCache; private volatile Cache<String, RootAuthenticationSessionEntity> authSessionsCache;
private int authSessionsLimit;
public static final String PROVIDER_ID = "infinispan"; public static final String PROVIDER_ID = "infinispan";
public static final String AUTH_SESSIONS_LIMIT = "authSessionsLimit";
public static final int DEFAULT_AUTH_SESSIONS_LIMIT = 300;
public static final String AUTHENTICATION_SESSION_EVENTS = "AUTHENTICATION_SESSION_EVENTS"; public static final String AUTHENTICATION_SESSION_EVENTS = "AUTHENTICATION_SESSION_EVENTS";
public static final String REALM_REMOVED_AUTHSESSION_EVENT = "REALM_REMOVED_EVENT_AUTHSESSIONS"; public static final String REALM_REMOVED_AUTHSESSION_EVENT = "REALM_REMOVED_EVENT_AUTHSESSIONS";
@ -64,7 +70,10 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
@Override @Override
public void init(Config.Scope config) { public void init(Config.Scope config) {
// get auth sessions limit from config or use default if not provided
int configInt = config.getInt(AUTH_SESSIONS_LIMIT, DEFAULT_AUTH_SESSIONS_LIMIT);
// use default if provided value is not a positive number
authSessionsLimit = (configInt <= 0) ? DEFAULT_AUTH_SESSIONS_LIMIT : configInt;
} }
@ -115,7 +124,7 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
@Override @Override
public AuthenticationSessionProvider create(KeycloakSession session) { public AuthenticationSessionProvider create(KeycloakSession session) {
lazyInit(session); lazyInit(session);
return new InfinispanAuthenticationSessionProvider(session, keyGenerator, authSessionsCache); return new InfinispanAuthenticationSessionProvider(session, keyGenerator, authSessionsCache, authSessionsLimit);
} }
private void updateAuthNotes(ClusterEvent clEvent) { private void updateAuthNotes(ClusterEvent clEvent) {

View file

@ -17,11 +17,13 @@
package org.keycloak.models.sessions.infinispan; package org.keycloak.models.sessions.infinispan;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.infinispan.Cache; import org.infinispan.Cache;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -37,20 +39,26 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
*/ */
public class RootAuthenticationSessionAdapter implements RootAuthenticationSessionModel { public class RootAuthenticationSessionAdapter implements RootAuthenticationSessionModel {
private static final Logger log = Logger.getLogger(RootAuthenticationSessionAdapter.class);
private KeycloakSession session; private KeycloakSession session;
private InfinispanAuthenticationSessionProvider provider; private InfinispanAuthenticationSessionProvider provider;
private Cache<String, RootAuthenticationSessionEntity> cache; private Cache<String, RootAuthenticationSessionEntity> cache;
private RealmModel realm; private RealmModel realm;
private RootAuthenticationSessionEntity entity; private RootAuthenticationSessionEntity entity;
private final int authSessionsLimit;
private static Comparator<Map.Entry<String, AuthenticationSessionEntity>> TIMESTAMP_COMPARATOR =
Comparator.comparingInt(e -> e.getValue().getTimestamp());
public RootAuthenticationSessionAdapter(KeycloakSession session, InfinispanAuthenticationSessionProvider provider, public RootAuthenticationSessionAdapter(KeycloakSession session, InfinispanAuthenticationSessionProvider provider,
Cache<String, RootAuthenticationSessionEntity> cache, RealmModel realm, Cache<String, RootAuthenticationSessionEntity> cache, RealmModel realm,
RootAuthenticationSessionEntity entity) { RootAuthenticationSessionEntity entity, int authSessionsLimt) {
this.session = session; this.session = session;
this.provider = provider; this.provider = provider;
this.cache = cache; this.cache = cache;
this.realm = realm; this.realm = realm;
this.entity = entity; this.entity = entity;
this.authSessionsLimit = authSessionsLimt;
} }
void update() { void update() {
@ -109,14 +117,29 @@ public class RootAuthenticationSessionAdapter implements RootAuthenticationSessi
@Override @Override
public AuthenticationSessionModel createAuthenticationSession(ClientModel client) { public AuthenticationSessionModel createAuthenticationSession(ClientModel client) {
Map<String, AuthenticationSessionEntity> authenticationSessions = entity.getAuthenticationSessions();
if (authenticationSessions.size() >= authSessionsLimit) {
String tabId = authenticationSessions.entrySet().stream().min(TIMESTAMP_COMPARATOR).map(Map.Entry::getKey).orElse(null);
if (tabId != null) {
log.debugf("Reached limit (%s) of active authentication sessions per a root authentication session. Removing oldest authentication session with TabId %s.", authSessionsLimit, tabId);
// remove the oldest authentication session
authenticationSessions.remove(tabId);
}
}
AuthenticationSessionEntity authSessionEntity = new AuthenticationSessionEntity(); AuthenticationSessionEntity authSessionEntity = new AuthenticationSessionEntity();
authSessionEntity.setClientUUID(client.getId()); authSessionEntity.setClientUUID(client.getId());
int timestamp = Time.currentTime();
authSessionEntity.setTimestamp(timestamp);
String tabId = provider.generateTabId(); String tabId = provider.generateTabId();
entity.getAuthenticationSessions().put(tabId, authSessionEntity); authenticationSessions.put(tabId, authSessionEntity);
// Update our timestamp when adding new authenticationSession // Update our timestamp when adding new authenticationSession
entity.setTimestamp(Time.currentTime()); entity.setTimestamp(timestamp);
update(); update();

View file

@ -42,6 +42,8 @@ public class AuthenticationSessionEntity implements Serializable {
private String authUserId; private String authUserId;
private int timestamp;
private String redirectUri; private String redirectUri;
private String action; private String action;
private Set<String> clientScopes; private Set<String> clientScopes;
@ -57,6 +59,17 @@ public class AuthenticationSessionEntity implements Serializable {
public AuthenticationSessionEntity() { public AuthenticationSessionEntity() {
} }
public AuthenticationSessionEntity(
String clientUUID,
String authUserId,
int timestamp,
String redirectUri, String action, Set<String> clientScopes,
Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus, String protocol,
Map<String, String> clientNotes, Map<String, String> authNotes, Set<String> requiredActions, Map<String, String> userSessionNotes) {
this(clientUUID, authUserId, redirectUri, action, clientScopes, executionStatus, protocol, clientNotes, authNotes, requiredActions, userSessionNotes);
this.timestamp = timestamp;
}
public AuthenticationSessionEntity( public AuthenticationSessionEntity(
String clientUUID, String clientUUID,
String authUserId, String authUserId,
@ -96,6 +109,14 @@ public class AuthenticationSessionEntity implements Serializable {
this.authUserId = authUserId; this.authUserId = authUserId;
} }
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.timestamp = timestamp;
}
public String getRedirectUri() { public String getRedirectUri() {
return redirectUri; return redirectUri;
} }
@ -171,6 +192,7 @@ public class AuthenticationSessionEntity implements Serializable {
public static class ExternalizerImpl implements Externalizer<AuthenticationSessionEntity> { public static class ExternalizerImpl implements Externalizer<AuthenticationSessionEntity> {
private static final int VERSION_1 = 1; private static final int VERSION_1 = 1;
private static final int VERSION_2 = 2;
public static final ExternalizerImpl INSTANCE = new ExternalizerImpl(); public static final ExternalizerImpl INSTANCE = new ExternalizerImpl();
@ -196,12 +218,14 @@ public class AuthenticationSessionEntity implements Serializable {
@Override @Override
public void writeObject(ObjectOutput output, AuthenticationSessionEntity value) throws IOException { public void writeObject(ObjectOutput output, AuthenticationSessionEntity value) throws IOException {
output.writeByte(VERSION_1); output.writeByte(VERSION_2);
MarshallUtil.marshallString(value.clientUUID, output); MarshallUtil.marshallString(value.clientUUID, output);
MarshallUtil.marshallString(value.authUserId, output); MarshallUtil.marshallString(value.authUserId, output);
output.writeInt(value.timestamp);
MarshallUtil.marshallString(value.redirectUri, output); MarshallUtil.marshallString(value.redirectUri, output);
MarshallUtil.marshallString(value.action, output); MarshallUtil.marshallString(value.action, output);
KeycloakMarshallUtil.writeCollection(value.clientScopes, KeycloakMarshallUtil.STRING_EXT, output); KeycloakMarshallUtil.writeCollection(value.clientScopes, KeycloakMarshallUtil.STRING_EXT, output);
@ -220,6 +244,8 @@ public class AuthenticationSessionEntity implements Serializable {
switch (input.readByte()) { switch (input.readByte()) {
case VERSION_1: case VERSION_1:
return readObjectVersion1(input); return readObjectVersion1(input);
case VERSION_2:
return readObjectVersion2(input);
default: default:
throw new IOException("Unknown version"); throw new IOException("Unknown version");
} }
@ -244,5 +270,27 @@ public class AuthenticationSessionEntity implements Serializable {
KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, size -> new ConcurrentHashMap<>(size)) // userSessionNotes KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, size -> new ConcurrentHashMap<>(size)) // userSessionNotes
); );
} }
public AuthenticationSessionEntity readObjectVersion2(ObjectInput input) throws IOException, ClassNotFoundException {
return new AuthenticationSessionEntity(
MarshallUtil.unmarshallString(input), // clientUUID
MarshallUtil.unmarshallString(input), // authUserId
input.readInt(), // timestamp
MarshallUtil.unmarshallString(input), // redirectUri
MarshallUtil.unmarshallString(input), // action
KeycloakMarshallUtil.readCollection(input, KeycloakMarshallUtil.STRING_EXT, ConcurrentHashMap::newKeySet), // clientScopes
KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, EXECUTION_STATUS_EXT, size -> new ConcurrentHashMap<>(size)), // executionStatus
MarshallUtil.unmarshallString(input), // protocol
KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, size -> new ConcurrentHashMap<>(size)), // clientNotes
KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, size -> new ConcurrentHashMap<>(size)), // authNotes
KeycloakMarshallUtil.readCollection(input, KeycloakMarshallUtil.STRING_EXT, ConcurrentHashMap::newKeySet), // requiredActions
KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, size -> new ConcurrentHashMap<>(size)) // userSessionNotes
);
}
} }
} }

View file

@ -32,6 +32,8 @@ public class MapAuthenticationSessionEntity {
private String authUserId; private String authUserId;
private int timestamp;
private String redirectUri; private String redirectUri;
private String action; private String action;
private Set<String> clientScopes = new HashSet<>(); private Set<String> clientScopes = new HashSet<>();
@ -68,6 +70,14 @@ public class MapAuthenticationSessionEntity {
this.authUserId = authUserId; this.authUserId = authUserId;
} }
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.timestamp = timestamp;
}
public String getRedirectUri() { public String getRedirectUri() {
return redirectUri; return redirectUri;
} }

View file

@ -82,11 +82,14 @@ public abstract class MapRootAuthenticationSessionAdapter<K> extends AbstractRoo
MapAuthenticationSessionEntity authSessionEntity = new MapAuthenticationSessionEntity(); MapAuthenticationSessionEntity authSessionEntity = new MapAuthenticationSessionEntity();
authSessionEntity.setClientUUID(client.getId()); authSessionEntity.setClientUUID(client.getId());
int timestamp = Time.currentTime();
authSessionEntity.setTimestamp(timestamp);
String tabId = generateTabId(); String tabId = generateTabId();
entity.getAuthenticationSessions().put(tabId, authSessionEntity); entity.getAuthenticationSessions().put(tabId, authSessionEntity);
// Update our timestamp when adding new authenticationSession // Update our timestamp when adding new authenticationSession
entity.setTimestamp(Time.currentTime()); entity.setTimestamp(timestamp);
MapAuthenticationSessionAdapter authSession = new MapAuthenticationSessionAdapter(session, this, tabId, authSessionEntity); MapAuthenticationSessionAdapter authSession = new MapAuthenticationSessionAdapter(session, this, tabId, authSessionEntity);
session.getContext().setAuthenticationSession(authSession); session.getContext().setAuthenticationSession(authSession);

View file

@ -204,5 +204,4 @@ public interface AuthenticationSessionModel extends CommonClientSessionModel {
* @param clientScopes {@code Set<String>} Can't be {@code null}. * @param clientScopes {@code Set<String>} Can't be {@code null}.
*/ */
void setClientScopes(Set<String> clientScopes); void setClientScopes(Set<String> clientScopes);
} }

View file

@ -78,7 +78,7 @@ public interface RootAuthenticationSessionModel {
AuthenticationSessionModel getAuthenticationSession(ClientModel client, String tabId); AuthenticationSessionModel getAuthenticationSession(ClientModel client, String tabId);
/** /**
* Create a new authentication session and returns it. Overwrites existing session for particular client if already exists. * Create a new authentication session and returns it.
* @param client {@code ClientModel} Can't be {@code null}. * @param client {@code ClientModel} Can't be {@code null}.
* @return {@code AuthenticationSessionModel} non-null fresh authentication session. Never returns {@code null}. * @return {@code AuthenticationSessionModel} non-null fresh authentication session. Never returns {@code null}.
*/ */

View file

@ -31,7 +31,6 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.sessions.StickySessionEncoderProvider; import org.keycloak.sessions.StickySessionEncoderProvider;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.util.AbstractMap.SimpleEntry;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -44,7 +43,7 @@ public class AuthenticationSessionManager {
public static final String AUTH_SESSION_ID = "AUTH_SESSION_ID"; public static final String AUTH_SESSION_ID = "AUTH_SESSION_ID";
public static final int AUTH_SESSION_LIMIT = 3; public static final int AUTH_SESSION_COOKIE_LIMIT = 3;
private static final Logger log = Logger.getLogger(AuthenticationSessionManager.class); private static final Logger log = Logger.getLogger(AuthenticationSessionManager.class);
@ -189,7 +188,7 @@ public class AuthenticationSessionManager {
AuthenticationManager.expireOldAuthSessionCookie(realm, session.getContext().getUri(), session.getContext().getConnection()); AuthenticationManager.expireOldAuthSessionCookie(realm, session.getContext().getUri(), session.getContext().getConnection());
} }
List<String> authSessionIds = cookiesVal.stream().limit(AUTH_SESSION_LIMIT).collect(Collectors.toList()); List<String> authSessionIds = cookiesVal.stream().limit(AUTH_SESSION_COOKIE_LIMIT).collect(Collectors.toList());
if (authSessionIds.isEmpty()) { if (authSessionIds.isEmpty()) {
log.debugf("Not found AUTH_SESSION_ID cookie"); log.debugf("Not found AUTH_SESSION_ID cookie");

View file

@ -62,12 +62,9 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.AuthenticationFlowResolver; import org.keycloak.models.utils.AuthenticationFlowResolver;
import org.keycloak.models.utils.FormMessage; import org.keycloak.models.utils.FormMessage;
import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.protocol.saml.SamlService;
import org.keycloak.protocol.saml.SamlSessionUtils; import org.keycloak.protocol.saml.SamlSessionUtils;
import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor; import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;

View file

@ -72,7 +72,10 @@
}, },
"authenticationSessions": { "authenticationSessions": {
"provider": "${keycloak.authSession.provider:infinispan}" "provider": "${keycloak.authSession.provider:infinispan}",
"infinispan": {
"authSessionsLimit": "${keycloak.authSessions.limit:300}"
}
}, },
"userSessions": { "userSessions": {

View file

@ -20,8 +20,10 @@ import org.keycloak.cluster.infinispan.InfinispanClusterProviderFactory;
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory; import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
import org.keycloak.connections.infinispan.InfinispanConnectionSpi; import org.keycloak.connections.infinispan.InfinispanConnectionSpi;
import org.keycloak.models.session.UserSessionPersisterSpi; import org.keycloak.models.session.UserSessionPersisterSpi;
import org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory;
import org.keycloak.models.sessions.infinispan.InfinispanUserLoginFailureProviderFactory; import org.keycloak.models.sessions.infinispan.InfinispanUserLoginFailureProviderFactory;
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory; import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
import org.keycloak.sessions.AuthenticationSessionSpi;
import org.keycloak.sessions.StickySessionEncoderProviderFactory; import org.keycloak.sessions.StickySessionEncoderProviderFactory;
import org.keycloak.sessions.StickySessionEncoderSpi; import org.keycloak.sessions.StickySessionEncoderSpi;
import org.keycloak.testsuite.model.KeycloakModelParameters; import org.keycloak.testsuite.model.KeycloakModelParameters;
@ -47,6 +49,7 @@ public class Infinispan extends KeycloakModelParameters {
private static final AtomicInteger NODE_COUNTER = new AtomicInteger(); private static final AtomicInteger NODE_COUNTER = new AtomicInteger();
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder() static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
.add(AuthenticationSessionSpi.class)
.add(CacheRealmProviderSpi.class) .add(CacheRealmProviderSpi.class)
.add(CacheUserProviderSpi.class) .add(CacheUserProviderSpi.class)
.add(InfinispanConnectionSpi.class) .add(InfinispanConnectionSpi.class)
@ -56,6 +59,7 @@ public class Infinispan extends KeycloakModelParameters {
.build(); .build();
static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder() static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder()
.add(InfinispanAuthenticationSessionProviderFactory.class)
.add(InfinispanCacheRealmProviderFactory.class) .add(InfinispanCacheRealmProviderFactory.class)
.add(InfinispanClusterProviderFactory.class) .add(InfinispanClusterProviderFactory.class)
.add(InfinispanConnectionProviderFactory.class) .add(InfinispanConnectionProviderFactory.class)

View file

@ -25,6 +25,7 @@ import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderF
import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory; import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory; import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory;
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
import org.keycloak.sessions.AuthenticationSessionSpi;
import org.keycloak.testsuite.model.KeycloakModelParameters; import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.keycloak.models.map.client.MapClientProviderFactory; import org.keycloak.models.map.client.MapClientProviderFactory;
import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory; import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory;
@ -37,7 +38,6 @@ import org.keycloak.models.map.storage.MapStorageSpi;
import org.keycloak.models.map.user.MapUserProviderFactory; import org.keycloak.models.map.user.MapUserProviderFactory;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi; import org.keycloak.provider.Spi;
import org.keycloak.sessions.AuthenticationSessionSpi;
import org.keycloak.testsuite.model.Config; import org.keycloak.testsuite.model.Config;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import java.util.Set; import java.util.Set;
@ -49,6 +49,7 @@ import java.util.Set;
public class Map extends KeycloakModelParameters { public class Map extends KeycloakModelParameters {
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder() static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
.add(AuthenticationSessionSpi.class)
.add(MapStorageSpi.class) .add(MapStorageSpi.class)
.build(); .build();
@ -76,7 +77,7 @@ public class Map extends KeycloakModelParameters {
@Override @Override
public void updateConfig(Config cf) { public void updateConfig(Config cf) {
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).defaultProvider(MapClientProviderFactory.PROVIDER_ID) cf.spi(AuthenticationSessionSpi.PROVIDER_ID).defaultProvider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID)
.spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID) .spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID)
.spi("clientScope").defaultProvider(MapClientScopeProviderFactory.PROVIDER_ID) .spi("clientScope").defaultProvider(MapClientScopeProviderFactory.PROVIDER_ID)
.spi("group").defaultProvider(MapGroupProviderFactory.PROVIDER_ID) .spi("group").defaultProvider(MapGroupProviderFactory.PROVIDER_ID)

View file

@ -0,0 +1,91 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.model.session;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.testsuite.model.KeycloakModelTest;
import org.keycloak.testsuite.model.RequireProvider;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.keycloak.testsuite.model.session.UserSessionPersisterProviderTest.createClients;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
@RequireProvider(value = AuthenticationSessionProvider.class, only = InfinispanAuthenticationSessionProviderFactory.PROVIDER_ID)
public class AuthenticationSessionTest extends KeycloakModelTest {
private String realmId;
@Override
public void createEnvironment(KeycloakSession s) {
RealmModel realm = s.realms().createRealm("test");
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
this.realmId = realm.getId();
createClients(s, realm);
}
@Override
public void cleanEnvironment(KeycloakSession s) {
s.realms().removeRealm(realmId);
}
@Test
public void testLimitAuthSessions() {
RootAuthenticationSessionModel ras = withRealm(realmId, (session, realm) -> session.authenticationSessions().createRootAuthenticationSession(realm));
List<String> tabIds = withRealm(realmId, (session, realm) -> {
ClientModel client = realm.getClientByClientId("test-app");
return IntStream.range(0, 300)
.mapToObj(i -> {
Time.setOffset(i);
return ras.createAuthenticationSession(client);
})
.map(AuthenticationSessionModel::getTabId)
.collect(Collectors.toList());
});
withRealm(realmId, (session, realm) -> {
ClientModel client = realm.getClientByClientId("test-app");
// create 301st auth session
AuthenticationSessionModel as = ras.createAuthenticationSession(client);
Assert.assertEquals(as, ras.getAuthenticationSession(client, as.getTabId()));
// assert the first authentication session was deleted
Assert.assertNull(ras.getAuthenticationSession(client, tabIds.get(0)));
return null;
});
}
}

View file

@ -46,7 +46,10 @@
}, },
"authenticationSessions": { "authenticationSessions": {
"provider": "${keycloak.authSession.provider:infinispan}" "provider": "${keycloak.authSession.provider:infinispan}",
"infinispan": {
"authSessionsLimit": "${keycloak.authSessions.limit:300}"
}
}, },
"userSessions": { "userSessions": {