brute force refactr, mv protocol

This commit is contained in:
Bill Burke 2016-01-15 19:25:28 -05:00
parent 5017d71383
commit 007e9530ec
33 changed files with 159 additions and 88 deletions

View file

@ -32,8 +32,8 @@ import java.util.List;
public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
@Override
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
return new SamlService(realm, event, authManager);
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event) {
return new SamlService(realm, event);
}
@Override

View file

@ -61,8 +61,8 @@ public class SamlService extends AuthorizationEndpointBase {
protected static final Logger logger = Logger.getLogger(SamlService.class);
public SamlService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
super(realm, event, authManager);
public SamlService(RealmModel realm, EventBuilder event) {
super(realm, event);
}
public abstract class BindingProtocol {
@ -556,7 +556,7 @@ public class SamlService extends AuthorizationEndpointBase {
@POST
@Consumes("application/soap+xml")
public Response soapBinding(InputStream inputStream) {
SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event, authManager);
SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event);
ResteasyProviderFactory.getInstance().injectProperties(bindingService);

View file

@ -36,8 +36,8 @@ public class SamlEcpProfileService extends SamlService {
private static final String NS_PREFIX_SAML_PROTOCOL = "samlp";
private static final String NS_PREFIX_SAML_ASSERTION = "saml";
public SamlEcpProfileService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
super(realm, event, authManager);
public SamlEcpProfileService(RealmModel realm, EventBuilder event) {
super(realm, event);
}
public Response authenticate(InputStream inputStream) {

View file

@ -30,7 +30,7 @@ public interface LoginProtocolFactory extends ProviderFactory<LoginProtocol> {
*/
List<ProtocolMapperModel> getDefaultBuiltinMappers();
Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager);
Object createProtocolEndpoint(RealmModel realm, EventBuilder event);
/**
* Setup default values for new clients. This expects that the representation has already set up the client

View file

@ -0,0 +1,16 @@
package org.keycloak.services.managers;
import org.keycloak.common.ClientConnection;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface BruteForceProtector extends Provider {
void failedLogin(RealmModel realm, String username, ClientConnection clientConnection);
boolean isTemporarilyDisabled(KeycloakSession session, RealmModel realm, String username);
}

View file

@ -0,0 +1,10 @@
package org.keycloak.services.managers;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface BruteForceProtectorFactory extends ProviderFactory<BruteForceProtector> {
}

View file

@ -0,0 +1,34 @@
package org.keycloak.services.managers;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class BruteForceProtectorSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "bruteForceProtector";
}
@Override
public Class<? extends Provider> getProviderClass() {
return BruteForceProtector.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return BruteForceProtectorFactory.class;
}
}

View file

@ -11,3 +11,8 @@ org.keycloak.events.EventStoreSpi
org.keycloak.exportimport.ExportSpi
org.keycloak.exportimport.ImportSpi
org.keycloak.timer.TimerSpi
org.keycloak.services.managers.BruteForceProtectorSpi
org.keycloak.protocol.ClientInstallationSpi
org.keycloak.protocol.LoginProtocolSpi
org.keycloak.protocol.ProtocolMapperSpi

View file

@ -84,6 +84,13 @@ public class AuthenticationProcessor {
return this;
}
public BruteForceProtector getBruteForceProtector() {
if (protector == null) {
protector = session.getProvider(BruteForceProtector.class);
}
return protector;
}
public RealmModel getRealm() {
return realm;
}
@ -149,11 +156,6 @@ public class AuthenticationProcessor {
return this;
}
public AuthenticationProcessor setProtector(BruteForceProtector protector) {
this.protector = protector;
return this;
}
public AuthenticationProcessor setEventBuilder(EventBuilder eventBuilder) {
this.event = eventBuilder;
return this;
@ -405,7 +407,7 @@ public class AuthenticationProcessor {
@Override
public BruteForceProtector getProtector() {
return AuthenticationProcessor.this.protector;
return AuthenticationProcessor.this.getBruteForceProtector();
}
@Override
@ -571,7 +573,6 @@ public class AuthenticationProcessor {
.setForwardedSuccessMessage(reset.getSuccessMessage())
.setConnection(connection)
.setEventBuilder(event)
.setProtector(protector)
.setRealm(realm)
.setSession(session)
.setUriInfo(uriInfo)

View file

@ -48,10 +48,9 @@ public abstract class AuthorizationEndpointBase {
@Context
protected ClientConnection clientConnection;
public AuthorizationEndpointBase(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
public AuthorizationEndpointBase(RealmModel realm, EventBuilder event) {
this.realm = realm;
this.event = event;
this.authManager = authManager;
}
protected AuthenticationProcessor createProcessor(ClientSessionModel clientSession, String flowId, String flowPath) {
@ -62,7 +61,6 @@ public abstract class AuthorizationEndpointBase {
.setBrowserFlow(true)
.setConnection(clientConnection)
.setEventBuilder(event)
.setProtector(authManager.getProtector())
.setRealm(realm)
.setSession(session)
.setUriInfo(uriInfo)

View file

@ -160,8 +160,8 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
}
@Override
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
return new OIDCLoginProtocolService(realm, event, authManager);
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event) {
return new OIDCLoginProtocolService(realm, event);
}
@Override

View file

@ -45,7 +45,6 @@ public class OIDCLoginProtocolService {
private RealmModel realm;
private TokenManager tokenManager;
private EventBuilder event;
private AuthenticationManager authManager;
@Context
private UriInfo uriInfo;
@ -56,11 +55,10 @@ public class OIDCLoginProtocolService {
@Context
private HttpHeaders headers;
public OIDCLoginProtocolService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
public OIDCLoginProtocolService(RealmModel realm, EventBuilder event) {
this.realm = realm;
this.tokenManager = new TokenManager();
this.event = event;
this.authManager = authManager;
}
public static UriBuilder tokenServiceBaseUrl(UriInfo uriInfo) {
@ -117,7 +115,7 @@ public class OIDCLoginProtocolService {
*/
@Path("auth")
public Object auth() {
AuthorizationEndpoint endpoint = new AuthorizationEndpoint(authManager, realm, event);
AuthorizationEndpoint endpoint = new AuthorizationEndpoint(realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint;
}
@ -127,7 +125,7 @@ public class OIDCLoginProtocolService {
*/
@Path("registrations")
public Object registerPage() {
AuthorizationEndpoint endpoint = new AuthorizationEndpoint(authManager, realm, event);
AuthorizationEndpoint endpoint = new AuthorizationEndpoint(realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.register();
}
@ -137,7 +135,7 @@ public class OIDCLoginProtocolService {
*/
@Path("forgot-credentials")
public Object forgotCredentialsPage() {
AuthorizationEndpoint endpoint = new AuthorizationEndpoint(authManager, realm, event);
AuthorizationEndpoint endpoint = new AuthorizationEndpoint(realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.forgotCredentials();
}
@ -147,7 +145,7 @@ public class OIDCLoginProtocolService {
*/
@Path("token")
public Object token() {
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint;
}
@ -155,7 +153,7 @@ public class OIDCLoginProtocolService {
@Path("login")
@Deprecated
public Object loginPage() {
AuthorizationEndpoint endpoint = new AuthorizationEndpoint(authManager, realm, event);
AuthorizationEndpoint endpoint = new AuthorizationEndpoint(realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.legacy(OIDCLoginProtocol.CODE_PARAM);
}
@ -170,7 +168,7 @@ public class OIDCLoginProtocolService {
@Path("grants/access")
@Deprecated
public Object grantAccessToken() {
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.legacy(OAuth2Constants.PASSWORD);
}
@ -178,7 +176,7 @@ public class OIDCLoginProtocolService {
@Path("refresh")
@Deprecated
public Object refreshAccessToken() {
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.legacy(OAuth2Constants.REFRESH_TOKEN);
}
@ -186,7 +184,7 @@ public class OIDCLoginProtocolService {
@Path("access/codes")
@Deprecated
public Object accessCodeToToken() {
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.legacy(OAuth2Constants.AUTHORIZATION_CODE);
}
@ -225,7 +223,7 @@ public class OIDCLoginProtocolService {
@Path("logout")
public Object logout() {
LogoutEndpoint endpoint = new LogoutEndpoint(tokenManager, authManager, realm, event);
LogoutEndpoint endpoint = new LogoutEndpoint(tokenManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint;
}

View file

@ -63,8 +63,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
private String legacyResponseType;
public AuthorizationEndpoint(AuthenticationManager authManager, RealmModel realm, EventBuilder event) {
super(realm, event, authManager);
public AuthorizationEndpoint(RealmModel realm, EventBuilder event) {
super(realm, event);
event.event(EventType.LOGIN);
}

View file

@ -61,13 +61,11 @@ public class LogoutEndpoint {
private UriInfo uriInfo;
private TokenManager tokenManager;
private AuthenticationManager authManager;
private RealmModel realm;
private EventBuilder event;
public LogoutEndpoint(TokenManager tokenManager, AuthenticationManager authManager, RealmModel realm, EventBuilder event) {
public LogoutEndpoint(TokenManager tokenManager, RealmModel realm, EventBuilder event) {
this.tokenManager = tokenManager;
this.authManager = authManager;
this.realm = realm;
this.event = event;
}
@ -117,7 +115,7 @@ public class LogoutEndpoint {
}
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, false);
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false);
if (authResult != null) {
userSession = userSession != null ? userSession : authResult.getSession();
if (redirect != null) userSession.setNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, redirect);
@ -129,7 +127,7 @@ public class LogoutEndpoint {
return response;
} else if (userSession != null) { // non browser logout
event.event(EventType.LOGOUT);
authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
event.user(userSession.getUser()).session(userSession).success();
}
@ -185,7 +183,7 @@ public class LogoutEndpoint {
}
private void logout(UserSessionModel userSession) {
authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
event.user(userSession.getUser()).session(userSession).success();
}

View file

@ -76,7 +76,6 @@ public class TokenEndpoint {
private ClientConnection clientConnection;
private final TokenManager tokenManager;
private final AuthenticationManager authManager;
private final RealmModel realm;
private final EventBuilder event;
@ -86,9 +85,8 @@ public class TokenEndpoint {
private String legacyGrantType;
public TokenEndpoint(TokenManager tokenManager, AuthenticationManager authManager, RealmModel realm, EventBuilder event) {
public TokenEndpoint(TokenManager tokenManager, RealmModel realm, EventBuilder event) {
this.tokenManager = tokenManager;
this.authManager = authManager;
this.realm = realm;
this.event = event;
}
@ -372,7 +370,6 @@ public class TokenEndpoint {
.setFlowId(flowId)
.setConnection(clientConnection)
.setEventBuilder(event)
.setProtector(authManager.getProtector())
.setRealm(realm)
.setSession(session)
.setUriInfo(uriInfo)

View file

@ -1,7 +1,6 @@
package org.keycloak.services.listeners;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.services.managers.BruteForceProtector;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
@ -17,10 +16,6 @@ public class KeycloakSessionDestroyListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent sce) {
BruteForceProtector protector = (BruteForceProtector) sce.getServletContext().getAttribute(BruteForceProtector.class.getName());
if (protector != null) {
protector.shutdown();
}
KeycloakSessionFactory sessionFactory = (KeycloakSessionFactory) sce.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());
if (sessionFactory != null) {
sessionFactory.close();

View file

@ -70,19 +70,6 @@ public class AuthenticationManager {
public static final String KEYCLOAK_LOGOUT_PROTOCOL = "KEYCLOAK_LOGOUT_PROTOCOL";
public static final String CURRENT_REQUIRED_ACTION = "CURRENT_REQUIRED_ACTION";
protected BruteForceProtector protector;
public AuthenticationManager() {
}
public AuthenticationManager(BruteForceProtector protector) {
this.protector = protector;
}
public BruteForceProtector getProtector() {
return protector;
}
public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
if (userSession == null) {
logger.debug("No user session");

View file

@ -20,8 +20,8 @@ import java.util.concurrent.TimeUnit;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class BruteForceProtector implements Runnable {
protected static Logger logger = Logger.getLogger(BruteForceProtector.class);
public class DefaultBruteForceProtector implements Runnable, BruteForceProtector {
protected static Logger logger = Logger.getLogger(DefaultBruteForceProtector.class);
protected volatile boolean run = true;
protected int maxDeltaTimeSeconds = 60 * 60 * 12; // 12 hours
@ -67,7 +67,7 @@ public class BruteForceProtector implements Runnable {
}
}
public BruteForceProtector(KeycloakSessionFactory factory) {
public DefaultBruteForceProtector(KeycloakSessionFactory factory) {
this.factory = factory;
}
@ -204,6 +204,7 @@ public class BruteForceProtector implements Runnable {
}
}
@Override
public void failedLogin(RealmModel realm, String username, ClientConnection clientConnection) {
try {
FailedLogin event = new FailedLogin(realm.getId(), username, clientConnection.getRemoteAddr());
@ -217,6 +218,7 @@ public class BruteForceProtector implements Runnable {
}
}
@Override
public boolean isTemporarilyDisabled(KeycloakSession session, RealmModel realm, String username) {
UsernameLoginFailureModel failure = session.sessions().getUserLoginFailure(realm, username.toLowerCase());
if (failure == null) {
@ -231,4 +233,8 @@ public class BruteForceProtector implements Runnable {
return false;
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,41 @@
package org.keycloak.services.managers;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class DefaultBruteForceProtectorFactory implements BruteForceProtectorFactory {
DefaultBruteForceProtector protector;
@Override
public BruteForceProtector create(KeycloakSession session) {
return protector;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
protector = new DefaultBruteForceProtector(factory);
protector.start();
}
@Override
public void close() {
protector.shutdown();
}
@Override
public String getId() {
return "default-brute-force-detector";
}
}

View file

@ -699,7 +699,6 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
.setFlowId(flowId)
.setConnection(clientConnection)
.setEventBuilder(event)
.setProtector(protector)
.setRealm(realmModel)
.setSession(session)
.setUriInfo(uriInfo)

View file

@ -17,7 +17,6 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.services.filters.KeycloakTransactionCommitter;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.UsersSyncManager;
import org.keycloak.services.resources.admin.AdminRoot;
@ -60,12 +59,7 @@ public class KeycloakApplication extends Application {
this.sessionFactory = createSessionFactory();
dispatcher.getDefaultContextObjects().put(KeycloakApplication.class, this);
BruteForceProtector protector = new BruteForceProtector(sessionFactory);
dispatcher.getDefaultContextObjects().put(BruteForceProtector.class, protector);
ResteasyProviderFactory.pushContext(BruteForceProtector.class, protector); // for injection
ResteasyProviderFactory.pushContext(KeycloakApplication.class, this); // for injection
protector.start();
context.setAttribute(BruteForceProtector.class.getName(), protector);
context.setAttribute(KeycloakSessionFactory.class.getName(), this.sessionFactory);
singletons.add(new ServerVersionResource());

View file

@ -21,6 +21,7 @@
*/
package org.keycloak.services.resources;
import org.apache.http.auth.AUTH;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
@ -121,8 +122,6 @@ public class LoginActionsService {
@Context
protected KeycloakSession session;
private AuthenticationManager authManager;
private EventBuilder event;
public static UriBuilder loginActionsBaseUrl(UriInfo uriInfo) {
@ -154,9 +153,8 @@ public class LoginActionsService {
return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
}
public LoginActionsService(RealmModel realm, AuthenticationManager authManager, EventBuilder event) {
public LoginActionsService(RealmModel realm, EventBuilder event) {
this.realm = realm;
this.authManager = authManager;
this.event = event;
}
@ -293,7 +291,6 @@ public class LoginActionsService {
.setFlowId(flow.getId())
.setConnection(clientConnection)
.setEventBuilder(event)
.setProtector(authManager.getProtector())
.setRealm(realm)
.setSession(session)
.setUriInfo(uriInfo)
@ -454,7 +451,7 @@ public class LoginActionsService {
ClientSessionModel clientSession = clientSessionCode.getClientSession();
authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
return processRegistration(execution, clientSession, null);
}
@ -648,7 +645,7 @@ public class LoginActionsService {
event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED);
event.success();
return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection, event);
return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection, event);
}
@Path("email-verification")

View file

@ -81,10 +81,9 @@ public class RealmsResource {
RealmModel realm = init(name);
EventBuilder event = new EventBuilder(realm, session, clientConnection);
AuthenticationManager authManager = new AuthenticationManager(protector);
LoginProtocolFactory factory = (LoginProtocolFactory)session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, OIDCLoginProtocol.LOGIN_PROTOCOL);
OIDCLoginProtocolService endpoint = (OIDCLoginProtocolService)factory.createProtocolEndpoint(realm, event, authManager);
OIDCLoginProtocolService endpoint = (OIDCLoginProtocolService)factory.createProtocolEndpoint(realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.getLoginStatusIframe();
@ -97,10 +96,9 @@ public class RealmsResource {
RealmModel realm = init(name);
EventBuilder event = new EventBuilder(realm, session, clientConnection);
AuthenticationManager authManager = new AuthenticationManager(protector);
LoginProtocolFactory factory = (LoginProtocolFactory)session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, protocol);
Object endpoint = factory.createProtocolEndpoint(realm, event, authManager);
Object endpoint = factory.createProtocolEndpoint(realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint;
@ -117,8 +115,7 @@ public class RealmsResource {
public LoginActionsService getLoginActionsService(final @PathParam("realm") String name) {
RealmModel realm = init(name);
EventBuilder event = new EventBuilder(realm, session, clientConnection);
AuthenticationManager authManager = new AuthenticationManager(protector);
LoginActionsService service = new LoginActionsService(realm, authManager, event);
LoginActionsService service = new LoginActionsService(realm, event);
ResteasyProviderFactory.getInstance().injectProperties(service);
return service;
}

View file

@ -1,5 +1,3 @@
org.keycloak.protocol.LoginProtocolSpi
org.keycloak.protocol.ProtocolMapperSpi
org.keycloak.exportimport.ClientDescriptionConverterSpi
org.keycloak.wellknown.WellKnownSpi
org.keycloak.messages.MessagesSpi
@ -9,4 +7,3 @@ org.keycloak.authentication.RequiredActionSpi
org.keycloak.authentication.FormAuthenticatorSpi
org.keycloak.authentication.FormActionSpi
org.keycloak.services.clientregistration.ClientRegistrationSpi
org.keycloak.protocol.ClientInstallationSpi

View file

@ -0,0 +1 @@
org.keycloak.services.managers.DefaultBruteForceProtectorFactory