keycloak-memoizeit

more brute force detection

4/14/2014 7:58:45 PM

Changes

Details

diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
index 4802f1f..14b58b5 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
@@ -639,6 +639,15 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'RealmRevocationCtrl'
         })
+        .when('/realms/:realm/sessions/brute-force', {
+            templateUrl : 'partials/session-brute-force.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                }
+            },
+            controller : 'RealmBruteForceCtrl'
+        })
         .when('/realms/:realm/sessions/realm', {
             templateUrl : 'partials/session-realm.html',
             resolve : {
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
index 9bbea17..04be127 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
@@ -1118,3 +1118,33 @@ module.controller('RealmAuditEventsCtrl', function($scope, RealmAuditEvents, rea
 
     $scope.update();
 });
+
+module.controller('RealmBruteForceCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications) {
+    console.log('RealmBruteForceCtrl');
+
+    $scope.realm = realm;
+
+    var oldCopy = angular.copy($scope.realm);
+    $scope.changed = false;
+
+    $scope.$watch('realm', function() {
+        if (!angular.equals($scope.realm, oldCopy)) {
+            $scope.changed = true;
+        }
+    }, true);
+
+    $scope.save = function() {
+        var realmCopy = angular.copy($scope.realm);
+        $scope.changed = false;
+        Realm.update(realmCopy, function () {
+            $location.url("/realms/" + realm.realm + "/sessions/brute-force");
+            Notifications.success("Your changes have been saved to the realm.");
+        });
+    };
+
+    $scope.reset = function() {
+        $scope.realm = angular.copy(oldCopy);
+        $scope.changed = false;
+    };
+});
+
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html
index 2e8d158..ba455e4 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html
@@ -4,6 +4,7 @@
         <li><a href="#/realms/{{realm.realm}}/sessions/realm">Realm Sessions</a></li>
         <li><a href="#/realms/{{realm.realm}}/token-settings">Token Settings</a></li>
         <li class="active"><a href="#/realms/{{realm.realm}}/sessions/revocation">Revocation</a></li>
+        <li><a href="#/realms/{{realm.realm}}/sessions/brute-force">Brute Force</a></li>
     </ul>
     <div id="content">
         <ol class="breadcrumb">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/session-brute-force.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/session-brute-force.html
new file mode 100755
index 0000000..ae45712
--- /dev/null
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/session-brute-force.html
@@ -0,0 +1,30 @@
+<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
+<div id="content-area" class="col-sm-9" role="main">
+    <ul class="nav nav-tabs nav-tabs-pf"  data-ng-show="!create">
+        <li><a href="#/realms/{{realm.realm}}/sessions/realm">Realm Sessions</a></li>
+        <li><a href="#/realms/{{realm.realm}}/token-settings">Token Settings</a></li>
+        <li><a href="#/realms/{{realm.realm}}/sessions/revocation">Revocation</a></li>
+        <li class="active"><a href="#/realms/{{realm.realm}}/sessions/brute-force">Brute Force</a></li>
+    </ul>
+    <div id="content">
+        <ol class="breadcrumb">
+            <li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
+            <li class="active">Brute Force</li>
+        </ol>
+        <h2><span>{{realm.realm}}</span> Brute Force Protection Settings</h2>
+        <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
+            <fieldset class="border-top">
+                <div class="form-group">
+                    <label class="col-sm-2 control-label" for="bruteForceProtected">Enabled</label>
+                    <div class="col-sm-4">
+                        <input ng-model="realm.bruteForceProtected" name="bruteForceProtected" id="bruteForceProtected" onoffswitch />
+                    </div>
+                </div>
+            </fieldset>
+            <div class="pull-right form-actions" data-ng-show="access.manageRealm">
+                <button kc-reset data-ng-show="changed">Clear changes</button>
+                <button kc-save data-ng-show="changed">Save</button>
+            </div>
+        </form>
+    </div>
+</div>
\ No newline at end of file
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/session-realm.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/session-realm.html
index addf466..0d0ba49 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/session-realm.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/session-realm.html
@@ -4,6 +4,7 @@
         <li class="active"><a href="#/realms/{{realm.realm}}/sessions/realm">Realm Sessions</a></li>
         <li><a href="#/realms/{{realm.realm}}/token-settings">Token Settings</a></li>
         <li><a href="#/realms/{{realm.realm}}/sessions/revocation">Revocation</a></li>
+        <li><a href="#/realms/{{realm.realm}}/sessions/brute-force">Brute Force</a></li>
     </ul>
     <div id="content">
         <ol class="breadcrumb">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/session-revocation.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/session-revocation.html
index c1d451d..285903c 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/session-revocation.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/session-revocation.html
@@ -4,6 +4,7 @@
         <li><a href="#/realms/{{realm.realm}}/sessions/realm">Realm Sessions</a></li>
         <li><a href="#/realms/{{realm.realm}}/token-settings">Token Settings</a></li>
         <li class="active"><a href="#/realms/{{realm.realm}}/sessions/revocation">Revocation</a></li>
+        <li><a href="#/realms/{{realm.realm}}/sessions/brute-force">Brute Force</a></li>
     </ul>
     <div id="content">
         <ol class="breadcrumb">
diff --git a/audit/api/src/main/java/org/keycloak/audit/Errors.java b/audit/api/src/main/java/org/keycloak/audit/Errors.java
old mode 100644
new mode 100755
index b343714..1ab7d4a
--- a/audit/api/src/main/java/org/keycloak/audit/Errors.java
+++ b/audit/api/src/main/java/org/keycloak/audit/Errors.java
@@ -13,6 +13,7 @@ public interface Errors {
 
     String USER_NOT_FOUND = "user_not_found";
     String USER_DISABLED = "user_disabled";
+    String USER_TEMPORARILY_DISABLED = "user_temporarily_disabled";
     String INVALID_USER_CREDENTIALS = "invalid_user_credentials";
 
     String USERNAME_MISSING = "username_missing";
diff --git a/bundled-war-example/src/main/resources/META-INF/persistence.xml b/bundled-war-example/src/main/resources/META-INF/persistence.xml
index b9dbe7b..3eeed1f 100755
--- a/bundled-war-example/src/main/resources/META-INF/persistence.xml
+++ b/bundled-war-example/src/main/resources/META-INF/persistence.xml
@@ -15,6 +15,7 @@
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
 
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index ac57c7e..0228a8b 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -28,6 +28,7 @@ public class RealmRepresentation {
     protected Boolean resetPasswordAllowed;
     protected Boolean social;
     protected Boolean updateProfileOnInitialSocialLogin;
+    protected Boolean bruteForceProtected;
     protected String privateKey;
     protected String publicKey;
     protected RolesRepresentation roles;
@@ -375,4 +376,12 @@ public class RealmRepresentation {
     public void setNotBefore(Integer notBefore) {
         this.notBefore = notBefore;
     }
+
+    public Boolean isBruteForceProtected() {
+        return bruteForceProtected;
+    }
+
+    public void setBruteForceProtected(Boolean bruteForceProtected) {
+        this.bruteForceProtected = bruteForceProtected;
+    }
 }
diff --git a/examples/demo-template/testrealm.json b/examples/demo-template/testrealm.json
index 81af756..2e35b12 100755
--- a/examples/demo-template/testrealm.json
+++ b/examples/demo-template/testrealm.json
@@ -7,6 +7,7 @@
     "sslNotRequired": true,
     "registrationAllowed": false,
     "social": false,
+    "bruteForceProtected": true,
     "updateProfileOnInitialSocialLogin": false,
     "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
     "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties
old mode 100644
new mode 100755
index b68a25e..1945c64
--- a/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties
+++ b/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties
@@ -33,4 +33,5 @@ socialLinkNotActive=This social link is not active anymore
 socialRedirectError=Failed to redirect to social provider
 socialProviderRemoved=Social provider removed successfully
 
-accountDisabled=Account is disabled, contact admin
\ No newline at end of file
+accountDisabled=Account is disabled, contact admin\
+accountTemporarilyDisabled=Account is temporarily disabled, contact admin or try again later
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
index 9aa907e..5bac22e 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
+++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
@@ -30,6 +30,7 @@ clientCertificate=Client Certificate
 invalidUser=Invalid username or password.
 invalidPassword=Invalid username or password.
 accountDisabled=Account is disabled, contact admin
+accountTemporarilyDisabled=Account is temporarily disabled, contact admin or try again later
 
 missingFirstName=Please specify first name
 missingLastName=Please specify last name
diff --git a/model/jpa/src/test/resources/META-INF/persistence.xml b/model/jpa/src/test/resources/META-INF/persistence.xml
index e84acc8..558aa89 100755
--- a/model/jpa/src/test/resources/META-INF/persistence.xml
+++ b/model/jpa/src/test/resources/META-INF/persistence.xml
@@ -17,6 +17,7 @@
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
+        <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
 
         <exclude-unlisted-classes>true</exclude-unlisted-classes>
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java
index b6f8413..880ce14 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java
@@ -1,6 +1,7 @@
 package org.keycloak.model.test;
 
 import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -11,8 +12,10 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.models.utils.TimeBasedOTP;
 import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.ClientConnection;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
+import org.keycloak.services.managers.BruteForceProtector;
 
 import javax.ws.rs.core.MultivaluedMap;
 
@@ -26,10 +29,29 @@ public class AuthenticationManagerTest extends AbstractModelTest {
     private TimeBasedOTP otp;
     private RealmModel realm;
     private UserModel user;
+    private BruteForceProtector protector;
+    private ClientConnection dummyConnection = new ClientConnection() {
+        @Override
+        public String getRemoteAddr() {
+            return "127.0.0.1";
+
+        }
+
+        @Override
+        public String getRemoteHost() {
+            return "localhost";
+
+        }
+
+        @Override
+        public int getReportPort() {
+            return 8080;
+        }
+    };
 
     @Test
     public void authForm() {
-        AuthenticationStatus status = am.authenticateForm(null, realm, formData);
+        AuthenticationStatus status = am.authenticateForm(dummyConnection, realm, formData);
         Assert.assertEquals(AuthenticationStatus.SUCCESS, status);
     }
 
@@ -38,7 +60,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         formData.remove(CredentialRepresentation.PASSWORD);
         formData.add(CredentialRepresentation.PASSWORD, "invalid");
 
-        AuthenticationStatus status = am.authenticateForm(null, realm, formData);
+        AuthenticationStatus status = am.authenticateForm(dummyConnection, realm, formData);
         Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
     }
 
@@ -46,7 +68,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
     public void authFormMissingUsername() {
         formData.remove("username");
 
-        AuthenticationStatus status = am.authenticateForm(null, realm, formData);
+        AuthenticationStatus status = am.authenticateForm(dummyConnection, realm, formData);
         Assert.assertEquals(AuthenticationStatus.INVALID_USER, status);
     }
 
@@ -54,7 +76,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
     public void authFormMissingPassword() {
         formData.remove(CredentialRepresentation.PASSWORD);
 
-        AuthenticationStatus status = am.authenticateForm(null, realm, formData);
+        AuthenticationStatus status = am.authenticateForm(dummyConnection, realm, formData);
         Assert.assertEquals(AuthenticationStatus.MISSING_PASSWORD, status);
     }
 
@@ -62,8 +84,8 @@ public class AuthenticationManagerTest extends AbstractModelTest {
     public void authFormRequiredAction() {
         realm.addRequiredCredential(CredentialRepresentation.TOTP);
         user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
-        
-        AuthenticationStatus status = am.authenticateForm(null, realm, formData);
+
+        AuthenticationStatus status = am.authenticateForm(dummyConnection, realm, formData);
         Assert.assertEquals(AuthenticationStatus.ACTIONS_REQUIRED, status);
     }
 
@@ -71,7 +93,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
     public void authFormUserDisabled() {
         user.setEnabled(false);
 
-        AuthenticationStatus status = am.authenticateForm(null, realm, formData);
+        AuthenticationStatus status = am.authenticateForm(dummyConnection, realm, formData);
         Assert.assertEquals(AuthenticationStatus.ACCOUNT_DISABLED, status);
     }
 
@@ -93,7 +115,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
 
         formData.add(CredentialRepresentation.TOTP, token);
 
-        AuthenticationStatus status = am.authenticateForm(null, realm, formData);
+        AuthenticationStatus status = am.authenticateForm(dummyConnection, realm, formData);
         Assert.assertEquals(AuthenticationStatus.SUCCESS, status);
     }
 
@@ -104,7 +126,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         formData.remove(CredentialRepresentation.PASSWORD);
         formData.add(CredentialRepresentation.PASSWORD, "invalid");
 
-        AuthenticationStatus status = am.authenticateForm(null, realm, formData);
+        AuthenticationStatus status = am.authenticateForm(dummyConnection, realm, formData);
         Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
     }
 
@@ -115,7 +137,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         formData.remove(CredentialRepresentation.TOTP);
         formData.add(CredentialRepresentation.TOTP, "invalid");
 
-        AuthenticationStatus status = am.authenticateForm(null, realm, formData);
+        AuthenticationStatus status = am.authenticateForm(dummyConnection, realm, formData);
         Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
     }
 
@@ -125,7 +147,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
 
         formData.remove(CredentialRepresentation.TOTP);
 
-        AuthenticationStatus status = am.authenticateForm(null, realm, formData);
+        AuthenticationStatus status = am.authenticateForm(dummyConnection, realm, formData);
         Assert.assertEquals(AuthenticationStatus.MISSING_TOTP, status);
     }
 
@@ -142,8 +164,9 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         realm.setAccessTokenLifespan(1000);
         realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
         realm.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
-
-        am = new AuthenticationManager(providerSession);
+        protector = new BruteForceProtector(factory);
+        protector.start();
+        am = new AuthenticationManager(providerSession, protector);
 
         user = realm.addUser("test");
         user.setEnabled(true);
@@ -161,4 +184,12 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         otp = new TimeBasedOTP();
     }
 
+    @After
+    public void after() throws Exception {
+        protector.shutdown();
+        super.after();
+
+
+    }
+
 }
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java
index 58bb294..5d28b60 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java
@@ -45,7 +45,10 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
 
         // Create 2 realms and user in realm1
         realm1 = realmManager.createRealm("realm1");
+        realm1.setBruteForceProtected(false);
         realm2 = realmManager.createRealm("realm2");
+        realm2.setBruteForceProtected(false);
+
         realm1.addRequiredCredential(CredentialRepresentation.PASSWORD);
         realm2.addRequiredCredential(CredentialRepresentation.PASSWORD);
         realm1.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
index dae3862..5498f78 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
@@ -48,6 +48,7 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
 
         // Create realm and configure ldap
         realm = realmManager.createRealm("realm");
+        realm.setBruteForceProtected(false);
         realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
         this.embeddedServer.setupLdapInRealm(realm);
 
diff --git a/server/src/main/resources/META-INF/persistence.xml b/server/src/main/resources/META-INF/persistence.xml
index b9dbe7b..3eeed1f 100755
--- a/server/src/main/resources/META-INF/persistence.xml
+++ b/server/src/main/resources/META-INF/persistence.xml
@@ -15,6 +15,7 @@
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
 
diff --git a/services/src/main/java/org/keycloak/services/listeners/KeycloakSessionDestroyListener.java b/services/src/main/java/org/keycloak/services/listeners/KeycloakSessionDestroyListener.java
old mode 100644
new mode 100755
index 7825c04..0c74c9d
--- a/services/src/main/java/org/keycloak/services/listeners/KeycloakSessionDestroyListener.java
+++ b/services/src/main/java/org/keycloak/services/listeners/KeycloakSessionDestroyListener.java
@@ -2,6 +2,7 @@ package org.keycloak.services.listeners;
 
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.provider.ProviderSessionFactory;
+import org.keycloak.services.managers.BruteForceProtector;
 
 import javax.servlet.ServletContextEvent;
 import javax.servlet.ServletContextListener;
@@ -17,6 +18,10 @@ 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();
+        }
         ProviderSessionFactory providerSessionFactory = (ProviderSessionFactory) sce.getServletContext().getAttribute(ProviderSessionFactory.class.getName());
         KeycloakSessionFactory kcSessionFactory = (KeycloakSessionFactory) sce.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());
         if (providerSessionFactory != null) {
@@ -25,6 +30,7 @@ public class KeycloakSessionDestroyListener implements ServletContextListener {
         if (kcSessionFactory != null) {
             kcSessionFactory.close();
         }
+
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 3be892c..f276f8f 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -50,7 +50,8 @@ public class AuthenticationManager {
         this.providerSession = providerSession;
     }
 
-    public AuthenticationManager(BruteForceProtector protector) {
+    public AuthenticationManager(ProviderSession providerSession, BruteForceProtector protector) {
+        this.providerSession = providerSession;
         this.protector = protector;
     }
 
@@ -199,6 +200,12 @@ public class AuthenticationManager {
             return AuthenticationStatus.INVALID_USER;
         }
 
+        if (realm.isBruteForceProtected()) {
+            if (protector.isTemporarilyDisabled(realm, username)) {
+                return AuthenticationStatus.ACCOUNT_TEMPORARILY_DISABLED;
+            }
+        }
+
         AuthenticationStatus status = authenticateInternal(realm, formData, username);
         if (realm.isBruteForceProtected()) {
             switch (status) {
@@ -313,7 +320,7 @@ public class AuthenticationManager {
     }
 
     public enum AuthenticationStatus {
-        SUCCESS, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
+        SUCCESS, ACCOUNT_TEMPORARILY_DISABLED, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/BruteForceProtector.java b/services/src/main/java/org/keycloak/services/managers/BruteForceProtector.java
index c8190f4..8692bf1 100755
--- a/services/src/main/java/org/keycloak/services/managers/BruteForceProtector.java
+++ b/services/src/main/java/org/keycloak/services/managers/BruteForceProtector.java
@@ -5,7 +5,6 @@ import org.jboss.resteasy.logging.Logger;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
 import org.keycloak.models.UsernameLoginFailureModel;
 import org.keycloak.services.ClientConnection;
 
@@ -28,8 +27,8 @@ public class BruteForceProtector implements Runnable {
     protected int minimumQuickLoginWaitSeconds = 60;
     protected int waitIncrementSeconds = 60;
     protected long quickLoginCheckMilliSeconds = 1000;
-    protected int maxDeltaTime = 60 * 60 * 24 * 1000;
-    protected int failureFactor = 10;
+    protected long maxDeltaTimeMilliSeconds = 60 * 60 * 12 * 1000; // 12 hours
+    protected int failureFactor = 30;
     protected volatile boolean run = true;
     protected KeycloakSessionFactory factory;
     protected CountDownLatch shutdownLatch = new CountDownLatch(1);
@@ -65,6 +64,12 @@ public class BruteForceProtector implements Runnable {
         }
     }
 
+    protected class ShutdownEvent extends LoginEvent {
+        public ShutdownEvent() {
+            super(null, null, null);
+        }
+    }
+
     protected class FailedLogin extends LoginEvent {
         protected final CountDownLatch latch = new CountDownLatch(1);
 
@@ -90,7 +95,7 @@ public class BruteForceProtector implements Runnable {
         user.setLastFailure(currentTime);
         if (deltaTime > 0) {
             // if last failure was more than MAX_DELTA clear failures
-            if (deltaTime > maxDeltaTime) {
+            if (deltaTime > maxDeltaTimeMilliSeconds) {
                 user.clearFailures();
             }
         }
@@ -109,20 +114,27 @@ public class BruteForceProtector implements Runnable {
     }
 
     protected UsernameLoginFailureModel getUserModel(KeycloakSession session, LoginEvent event) {
-        RealmModel realm = session.getRealm(event.realmId);
+        RealmModel realm = getRealmModel(session, event);
         if (realm == null) return null;
         UsernameLoginFailureModel user = realm.getUserLoginFailure(event.username);
         if (user == null) return null;
         return user;
     }
 
+    protected RealmModel getRealmModel(KeycloakSession session, LoginEvent event) {
+        RealmModel realm = session.getRealm(event.realmId);
+        if (realm == null) return null;
+        return realm;
+    }
+
     public void start() {
-        new Thread(this).start();
+        new Thread(this, "Brute Force Protector").start();
     }
 
     public void shutdown() {
         run = false;
         try {
+            queue.offer(new ShutdownEvent());
             shutdownLatch.await(5, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
@@ -144,7 +156,7 @@ public class BruteForceProtector implements Runnable {
                     for (LoginEvent event : events) {
                         if (event instanceof FailedLogin) {
                             logFailure(event);
-                        } else {
+                        } else if (event instanceof SuccessfulLogin) {
                             logSuccess(event);
                         }
                     }
@@ -191,7 +203,7 @@ public class BruteForceProtector implements Runnable {
         long delta = 0;
         if (lastFailure > 0) {
             delta = System.currentTimeMillis() - lastFailure;
-            if (delta > maxDeltaTime) {
+            if (delta > maxDeltaTimeMilliSeconds) {
                 totalTime = 0;
 
             } else {
@@ -221,4 +233,73 @@ public class BruteForceProtector implements Runnable {
         } catch (InterruptedException e) {
         }
     }
+
+    public boolean isTemporarilyDisabled(RealmModel realm, String username) {
+        UsernameLoginFailureModel failure = realm.getUserLoginFailure(username);
+        if (failure == null) {
+            return false;
+        }
+
+        int currTime = (int)(System.currentTimeMillis()/1000);
+        if (currTime < failure.getFailedLoginNotBefore()) {
+            return true;
+        }
+        return false;
+    }
+
+    public long getFailures() {
+        return failures;
+    }
+
+    public long getLastFailure() {
+        return lastFailure;
+    }
+
+    public int getMaxFailureWaitSeconds() {
+        return maxFailureWaitSeconds;
+    }
+
+    public void setMaxFailureWaitSeconds(int maxFailureWaitSeconds) {
+        this.maxFailureWaitSeconds = maxFailureWaitSeconds;
+    }
+
+    public int getMinimumQuickLoginWaitSeconds() {
+        return minimumQuickLoginWaitSeconds;
+    }
+
+    public void setMinimumQuickLoginWaitSeconds(int minimumQuickLoginWaitSeconds) {
+        this.minimumQuickLoginWaitSeconds = minimumQuickLoginWaitSeconds;
+    }
+
+    public int getWaitIncrementSeconds() {
+        return waitIncrementSeconds;
+    }
+
+    public void setWaitIncrementSeconds(int waitIncrementSeconds) {
+        this.waitIncrementSeconds = waitIncrementSeconds;
+    }
+
+    public long getQuickLoginCheckMilliSeconds() {
+        return quickLoginCheckMilliSeconds;
+    }
+
+    public void setQuickLoginCheckMilliSeconds(long quickLoginCheckMilliSeconds) {
+        this.quickLoginCheckMilliSeconds = quickLoginCheckMilliSeconds;
+    }
+
+    public long getMaxDeltaTimeMilliSeconds() {
+        return maxDeltaTimeMilliSeconds;
+    }
+
+    public void setMaxDeltaTimeMilliSeconds(long maxDeltaTimeMilliSeconds) {
+        this.maxDeltaTimeMilliSeconds = maxDeltaTimeMilliSeconds;
+    }
+
+    public int getFailureFactor() {
+        return failureFactor;
+    }
+
+    public void setFailureFactor(int failureFactor) {
+        this.failureFactor = failureFactor;
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
index 8562bd8..24c09b2 100755
--- a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
+++ b/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java
@@ -78,6 +78,7 @@ public class ModelToRepresentation {
         rep.setPrivateKey(realm.getPrivateKeyPem());
         rep.setRegistrationAllowed(realm.isRegistrationAllowed());
         rep.setRememberMe(realm.isRememberMe());
+        rep.setBruteForceProtected(realm.isBruteForceProtected());
         rep.setVerifyEmail(realm.isVerifyEmail());
         rep.setResetPasswordAllowed(realm.isResetPasswordAllowed());
         rep.setAccessTokenLifespan(realm.getAccessTokenLifespan());
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 1e5bb84..b710af6 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -79,6 +79,7 @@ public class RealmManager {
         if (id == null) id = KeycloakModelUtils.generateId();
         RealmModel realm = identitySession.createRealm(id, name);
         realm.setName(name);
+        realm.setBruteForceProtected(false); // default settings off for now todo set it on
 
         setupAdminManagement(realm);
         setupAccountManagement(realm);
@@ -121,6 +122,7 @@ public class RealmManager {
         }
         if (rep.isEnabled() != null) realm.setEnabled(rep.isEnabled());
         if (rep.isSocial() != null) realm.setSocial(rep.isSocial());
+        if (rep.isBruteForceProtected() != null) realm.setBruteForceProtected(rep.isBruteForceProtected());
         if (rep.isRegistrationAllowed() != null) realm.setRegistrationAllowed(rep.isRegistrationAllowed());
         if (rep.isRememberMe() != null) realm.setRememberMe(rep.isRememberMe());
         if (rep.isVerifyEmail() != null) realm.setVerifyEmail(rep.isVerifyEmail());
@@ -227,6 +229,7 @@ public class RealmManager {
         newRealm.setName(rep.getRealm());
         if (rep.isEnabled() != null) newRealm.setEnabled(rep.isEnabled());
         if (rep.isSocial() != null) newRealm.setSocial(rep.isSocial());
+        if (rep.isBruteForceProtected() != null) newRealm.setBruteForceProtected(rep.isBruteForceProtected());
 
         if (rep.getNotBefore() != null) newRealm.setNotBefore(rep.getNotBefore());
 
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
old mode 100644
new mode 100755
index e5a9e59..cc0ab1d
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -27,6 +27,7 @@ package org.keycloak.services.messages;
 public class Messages {
 
     public static final String ACCOUNT_DISABLED = "accountDisabled";
+    public static final String ACCOUNT_TEMPORARILY_DISABLED = "accountTemporarilyDisabled";
 
     public static final String INVALID_PASSWORD = "invalidPassword";
 
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 7b8b3dd..7f49ff5 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -2,6 +2,7 @@ package org.keycloak.services.resources;
 
 import org.jboss.resteasy.core.Dispatcher;
 import org.jboss.resteasy.logging.Logger;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.SkeletonKeyContextResolver;
 import org.keycloak.audit.AuditListener;
 import org.keycloak.audit.AuditListenerFactory;
@@ -22,6 +23,7 @@ import org.keycloak.picketlink.IdentityManagerProvider;
 import org.keycloak.picketlink.IdentityManagerProviderFactory;
 import org.keycloak.provider.ProviderSessionFactory;
 import org.keycloak.services.managers.ApplianceBootstrap;
+import org.keycloak.services.managers.BruteForceProtector;
 import org.keycloak.services.managers.SocialRequestManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.resources.admin.AdminService;
@@ -57,6 +59,11 @@ public class KeycloakApplication extends Application {
         dispatcher.getDefaultContextObjects().put(KeycloakApplication.class, this);
         this.contextPath = context.getContextPath();
         this.factory = createSessionFactory();
+        BruteForceProtector protector = new BruteForceProtector(factory);
+        dispatcher.getDefaultContextObjects().put(BruteForceProtector.class, protector);
+        ResteasyProviderFactory.pushContext(BruteForceProtector.class, protector); // for injection
+        protector.start();
+        context.setAttribute(BruteForceProtector.class.getName(), protector);
         this.providerSessionFactory = createProviderSessionFactory();
         context.setAttribute(KeycloakSessionFactory.class.getName(), factory);
         //classes.add(KeycloakSessionCleanupFilter.class);
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index cea5959..0b8126c 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -12,6 +12,7 @@ import org.keycloak.services.ClientConnection;
 import org.keycloak.provider.ProviderSession;
 import org.keycloak.services.managers.AuditManager;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.BruteForceProtector;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.SocialRequestManager;
 import org.keycloak.services.managers.TokenManager;
@@ -51,6 +52,9 @@ public class RealmsResource {
     @Context
     protected ClientConnection clientConnection;
 
+    @Context
+    protected BruteForceProtector protector;
+
     protected TokenManager tokenManager;
     protected SocialRequestManager socialRequestManager;
 
@@ -68,7 +72,7 @@ public class RealmsResource {
         RealmManager realmManager = new RealmManager(session);
         RealmModel realm = locateRealm(name, realmManager);
         Audit audit = new AuditManager(realm, providers, clientConnection).createAudit();
-        AuthenticationManager authManager = new AuthenticationManager(providers);
+        AuthenticationManager authManager = new AuthenticationManager(providers, protector);
         TokenService tokenService = new TokenService(realm, tokenManager, audit, authManager);
         ResteasyProviderFactory.getInstance().injectProperties(tokenService);
         //resourceContext.initResource(tokenService);
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index c00b09c..ed223ce 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -179,9 +179,20 @@ public class TokenService {
             throw new UnauthorizedException("Disabled realm");
         }
 
-        if (authManager.authenticateForm(clientConnection, realm, form) != AuthenticationStatus.SUCCESS) {
-            audit.error(Errors.INVALID_USER_CREDENTIALS);
-            throw new UnauthorizedException("Auth failed");
+        AuthenticationStatus authenticationStatus = authManager.authenticateForm(clientConnection, realm, form);
+
+        switch (authenticationStatus) {
+            case SUCCESS:
+                break;
+            case ACCOUNT_TEMPORARILY_DISABLED:
+            case ACTIONS_REQUIRED:
+                audit.error(Errors.USER_TEMPORARILY_DISABLED);
+                return Response.status(503).type(MediaType.TEXT_PLAIN).entity("Account temporarily disabled").build();
+            case ACCOUNT_DISABLED:
+                return Response.status(403).type(MediaType.TEXT_PLAIN).entity("Account disabled").build();
+            default:
+                audit.error(Errors.INVALID_USER_CREDENTIALS);
+                throw new UnauthorizedException("Auth failed");
         }
 
         UserModel user = realm.getUser(form.getFirst(AuthenticationManager.FORM_USERNAME));
@@ -303,6 +314,9 @@ public class TokenService {
                 UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
 		        audit.user(user);
                 return oauth.processAccessCode(scopeParam, state, redirect, client, user, username, remember, "form", audit);
+            case ACCOUNT_TEMPORARILY_DISABLED:
+                audit.error(Errors.USER_TEMPORARILY_DISABLED);
+                return Flows.forms(realm, uriInfo).setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).setFormData(formData).createLogin();
             case ACCOUNT_DISABLED:
                 audit.error(Errors.USER_DISABLED);
                 return Flows.forms(realm, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
diff --git a/testsuite/integration/src/main/resources/META-INF/persistence.xml b/testsuite/integration/src/main/resources/META-INF/persistence.xml
index 56a5ed4..1d2f21a 100755
--- a/testsuite/integration/src/main/resources/META-INF/persistence.xml
+++ b/testsuite/integration/src/main/resources/META-INF/persistence.xml
@@ -16,6 +16,7 @@
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
 
diff --git a/testsuite/integration/src/test/resources/testcomposite.json b/testsuite/integration/src/test/resources/testcomposite.json
index 61038ea..dd4e378 100755
--- a/testsuite/integration/src/test/resources/testcomposite.json
+++ b/testsuite/integration/src/test/resources/testcomposite.json
@@ -6,6 +6,7 @@
     "accessCodeLifespan": 600,
     "accessCodeLifespanUserAction": 600,
     "sslNotRequired": true,
+    "bruteForceProtected": true,
     "registrationAllowed": true,
     "resetPasswordAllowed": true,
     "requiredCredentials": [ "password" ],
diff --git a/testsuite/integration/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json
index a7cf0d4..d3ea49f 100755
--- a/testsuite/integration/src/test/resources/testrealm.json
+++ b/testsuite/integration/src/test/resources/testrealm.json
@@ -5,6 +5,7 @@
     "sslNotRequired": true,
     "registrationAllowed": true,
     "resetPasswordAllowed": true,
+    "bruteForceProtected": true,
     "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
     "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "requiredCredentials": [ "password" ],
diff --git a/testsuite/performance/src/test/resources/META-INF/persistence.xml b/testsuite/performance/src/test/resources/META-INF/persistence.xml
index 5ca96af..29af038 100755
--- a/testsuite/performance/src/test/resources/META-INF/persistence.xml
+++ b/testsuite/performance/src/test/resources/META-INF/persistence.xml
@@ -16,6 +16,7 @@
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>