KEYCLOAK-16616 Limit number of authSessios per rootAuthSession
This commit is contained in:
parent
122fbe1bc6
commit
23aee6c210
16 changed files with 228 additions and 21 deletions
|
@ -291,4 +291,18 @@ public class AuthenticationSessionAdapter implements AuthenticationSessionModel
|
|||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -51,13 +51,16 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
|
|||
private final KeycloakSession session;
|
||||
private final Cache<String, RootAuthenticationSessionEntity> cache;
|
||||
private final InfinispanKeyGenerator keyGenerator;
|
||||
private final int authSessionsLimit;
|
||||
protected final InfinispanKeycloakTransaction tx;
|
||||
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.cache = cache;
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.authSessionsLimit = authSessionsLimit;
|
||||
|
||||
this.tx = new InfinispanKeycloakTransaction();
|
||||
this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session);
|
||||
|
@ -88,7 +91,7 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
|
|||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -54,8 +54,14 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
|
|||
|
||||
private volatile Cache<String, RootAuthenticationSessionEntity> authSessionsCache;
|
||||
|
||||
private int authSessionsLimit;
|
||||
|
||||
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 REALM_REMOVED_AUTHSESSION_EVENT = "REALM_REMOVED_EVENT_AUTHSESSIONS";
|
||||
|
@ -64,7 +70,10 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
|
|||
|
||||
@Override
|
||||
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
|
||||
public AuthenticationSessionProvider create(KeycloakSession session) {
|
||||
lazyInit(session);
|
||||
return new InfinispanAuthenticationSessionProvider(session, keyGenerator, authSessionsCache);
|
||||
return new InfinispanAuthenticationSessionProvider(session, keyGenerator, authSessionsCache, authSessionsLimit);
|
||||
}
|
||||
|
||||
private void updateAuthNotes(ClusterEvent clEvent) {
|
||||
|
|
|
@ -17,11 +17,13 @@
|
|||
|
||||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -37,20 +39,26 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
|
|||
*/
|
||||
public class RootAuthenticationSessionAdapter implements RootAuthenticationSessionModel {
|
||||
|
||||
private static final Logger log = Logger.getLogger(RootAuthenticationSessionAdapter.class);
|
||||
|
||||
private KeycloakSession session;
|
||||
private InfinispanAuthenticationSessionProvider provider;
|
||||
private Cache<String, RootAuthenticationSessionEntity> cache;
|
||||
private RealmModel realm;
|
||||
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,
|
||||
Cache<String, RootAuthenticationSessionEntity> cache, RealmModel realm,
|
||||
RootAuthenticationSessionEntity entity) {
|
||||
RootAuthenticationSessionEntity entity, int authSessionsLimt) {
|
||||
this.session = session;
|
||||
this.provider = provider;
|
||||
this.cache = cache;
|
||||
this.realm = realm;
|
||||
this.entity = entity;
|
||||
this.authSessionsLimit = authSessionsLimt;
|
||||
}
|
||||
|
||||
void update() {
|
||||
|
@ -109,14 +117,29 @@ public class RootAuthenticationSessionAdapter implements RootAuthenticationSessi
|
|||
|
||||
@Override
|
||||
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();
|
||||
authSessionEntity.setClientUUID(client.getId());
|
||||
|
||||
int timestamp = Time.currentTime();
|
||||
authSessionEntity.setTimestamp(timestamp);
|
||||
|
||||
String tabId = provider.generateTabId();
|
||||
entity.getAuthenticationSessions().put(tabId, authSessionEntity);
|
||||
authenticationSessions.put(tabId, authSessionEntity);
|
||||
|
||||
// Update our timestamp when adding new authenticationSession
|
||||
entity.setTimestamp(Time.currentTime());
|
||||
entity.setTimestamp(timestamp);
|
||||
|
||||
update();
|
||||
|
||||
|
|
|
@ -42,6 +42,8 @@ public class AuthenticationSessionEntity implements Serializable {
|
|||
|
||||
private String authUserId;
|
||||
|
||||
private int timestamp;
|
||||
|
||||
private String redirectUri;
|
||||
private String action;
|
||||
private Set<String> clientScopes;
|
||||
|
@ -60,9 +62,20 @@ public class AuthenticationSessionEntity implements Serializable {
|
|||
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(
|
||||
String clientUUID,
|
||||
String authUserId,
|
||||
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 = clientUUID;
|
||||
|
||||
this.authUserId = authUserId;
|
||||
|
@ -96,6 +109,14 @@ public class AuthenticationSessionEntity implements Serializable {
|
|||
this.authUserId = authUserId;
|
||||
}
|
||||
|
||||
public int getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(int timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
@ -171,6 +192,7 @@ public class AuthenticationSessionEntity implements Serializable {
|
|||
public static class ExternalizerImpl implements Externalizer<AuthenticationSessionEntity> {
|
||||
|
||||
private static final int VERSION_1 = 1;
|
||||
private static final int VERSION_2 = 2;
|
||||
|
||||
public static final ExternalizerImpl INSTANCE = new ExternalizerImpl();
|
||||
|
||||
|
@ -196,12 +218,14 @@ public class AuthenticationSessionEntity implements Serializable {
|
|||
|
||||
@Override
|
||||
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.authUserId, output);
|
||||
|
||||
output.writeInt(value.timestamp);
|
||||
|
||||
MarshallUtil.marshallString(value.redirectUri, output);
|
||||
MarshallUtil.marshallString(value.action, output);
|
||||
KeycloakMarshallUtil.writeCollection(value.clientScopes, KeycloakMarshallUtil.STRING_EXT, output);
|
||||
|
@ -220,6 +244,8 @@ public class AuthenticationSessionEntity implements Serializable {
|
|||
switch (input.readByte()) {
|
||||
case VERSION_1:
|
||||
return readObjectVersion1(input);
|
||||
case VERSION_2:
|
||||
return readObjectVersion2(input);
|
||||
default:
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ public class MapAuthenticationSessionEntity {
|
|||
|
||||
private String authUserId;
|
||||
|
||||
private int timestamp;
|
||||
|
||||
private String redirectUri;
|
||||
private String action;
|
||||
private Set<String> clientScopes = new HashSet<>();
|
||||
|
@ -68,6 +70,14 @@ public class MapAuthenticationSessionEntity {
|
|||
this.authUserId = authUserId;
|
||||
}
|
||||
|
||||
public int getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(int timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
|
|
@ -82,11 +82,14 @@ public abstract class MapRootAuthenticationSessionAdapter<K> extends AbstractRoo
|
|||
MapAuthenticationSessionEntity authSessionEntity = new MapAuthenticationSessionEntity();
|
||||
authSessionEntity.setClientUUID(client.getId());
|
||||
|
||||
int timestamp = Time.currentTime();
|
||||
authSessionEntity.setTimestamp(timestamp);
|
||||
|
||||
String tabId = generateTabId();
|
||||
entity.getAuthenticationSessions().put(tabId, authSessionEntity);
|
||||
|
||||
// Update our timestamp when adding new authenticationSession
|
||||
entity.setTimestamp(Time.currentTime());
|
||||
entity.setTimestamp(timestamp);
|
||||
|
||||
MapAuthenticationSessionAdapter authSession = new MapAuthenticationSessionAdapter(session, this, tabId, authSessionEntity);
|
||||
session.getContext().setAuthenticationSession(authSession);
|
||||
|
|
|
@ -204,5 +204,4 @@ public interface AuthenticationSessionModel extends CommonClientSessionModel {
|
|||
* @param clientScopes {@code Set<String>} Can't be {@code null}.
|
||||
*/
|
||||
void setClientScopes(Set<String> clientScopes);
|
||||
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ public interface RootAuthenticationSessionModel {
|
|||
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}.
|
||||
* @return {@code AuthenticationSessionModel} non-null fresh authentication session. Never returns {@code null}.
|
||||
*/
|
||||
|
|
|
@ -31,7 +31,6 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
|
|||
import org.keycloak.sessions.StickySessionEncoderProvider;
|
||||
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
@ -44,7 +43,7 @@ public class AuthenticationSessionManager {
|
|||
|
||||
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);
|
||||
|
||||
|
@ -189,7 +188,7 @@ public class AuthenticationSessionManager {
|
|||
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()) {
|
||||
log.debugf("Not found AUTH_SESSION_ID cookie");
|
||||
|
|
|
@ -62,12 +62,9 @@ import org.keycloak.models.UserSessionModel;
|
|||
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.LoginProtocolFactory;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
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.preprocessor.SamlAuthenticationPreprocessor;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
|
|
@ -72,7 +72,10 @@
|
|||
},
|
||||
|
||||
"authenticationSessions": {
|
||||
"provider": "${keycloak.authSession.provider:infinispan}"
|
||||
"provider": "${keycloak.authSession.provider:infinispan}",
|
||||
"infinispan": {
|
||||
"authSessionsLimit": "${keycloak.authSessions.limit:300}"
|
||||
}
|
||||
},
|
||||
|
||||
"userSessions": {
|
||||
|
|
|
@ -20,8 +20,10 @@ import org.keycloak.cluster.infinispan.InfinispanClusterProviderFactory;
|
|||
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionSpi;
|
||||
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.InfinispanUserSessionProviderFactory;
|
||||
import org.keycloak.sessions.AuthenticationSessionSpi;
|
||||
import org.keycloak.sessions.StickySessionEncoderProviderFactory;
|
||||
import org.keycloak.sessions.StickySessionEncoderSpi;
|
||||
import org.keycloak.testsuite.model.KeycloakModelParameters;
|
||||
|
@ -47,6 +49,7 @@ public class Infinispan extends KeycloakModelParameters {
|
|||
private static final AtomicInteger NODE_COUNTER = new AtomicInteger();
|
||||
|
||||
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
|
||||
.add(AuthenticationSessionSpi.class)
|
||||
.add(CacheRealmProviderSpi.class)
|
||||
.add(CacheUserProviderSpi.class)
|
||||
.add(InfinispanConnectionSpi.class)
|
||||
|
@ -56,6 +59,7 @@ public class Infinispan extends KeycloakModelParameters {
|
|||
.build();
|
||||
|
||||
static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder()
|
||||
.add(InfinispanAuthenticationSessionProviderFactory.class)
|
||||
.add(InfinispanCacheRealmProviderFactory.class)
|
||||
.add(InfinispanClusterProviderFactory.class)
|
||||
.add(InfinispanConnectionProviderFactory.class)
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderF
|
|||
import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory;
|
||||
import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory;
|
||||
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
|
||||
import org.keycloak.sessions.AuthenticationSessionSpi;
|
||||
import org.keycloak.testsuite.model.KeycloakModelParameters;
|
||||
import org.keycloak.models.map.client.MapClientProviderFactory;
|
||||
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.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
import org.keycloak.sessions.AuthenticationSessionSpi;
|
||||
import org.keycloak.testsuite.model.Config;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.util.Set;
|
||||
|
@ -49,6 +49,7 @@ import java.util.Set;
|
|||
public class Map extends KeycloakModelParameters {
|
||||
|
||||
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
|
||||
.add(AuthenticationSessionSpi.class)
|
||||
.add(MapStorageSpi.class)
|
||||
|
||||
.build();
|
||||
|
@ -76,7 +77,7 @@ public class Map extends KeycloakModelParameters {
|
|||
|
||||
@Override
|
||||
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("clientScope").defaultProvider(MapClientScopeProviderFactory.PROVIDER_ID)
|
||||
.spi("group").defaultProvider(MapGroupProviderFactory.PROVIDER_ID)
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -46,7 +46,10 @@
|
|||
},
|
||||
|
||||
"authenticationSessions": {
|
||||
"provider": "${keycloak.authSession.provider:infinispan}"
|
||||
"provider": "${keycloak.authSession.provider:infinispan}",
|
||||
"infinispan": {
|
||||
"authSessionsLimit": "${keycloak.authSessions.limit:300}"
|
||||
}
|
||||
},
|
||||
|
||||
"userSessions": {
|
||||
|
|
Loading…
Reference in a new issue