From: markt null.
+ *
+ * @param username Username of the Principal to look up
+ * @param credentials Password or other credentials to use in
+ * authenticating this username
+ */
+ public Principal authenticate(String username, byte[] credentials) {
+ if (isLocked(username)) {
+ // Trying to authenticate a locked user is an automatic failure
+ registerAuthFailure(username);
+
+ log.warn(sm.getString("lockOutRealm.authLockedUser", username));
+ return null;
+ }
+
+ Principal authenticatedUser = super.authenticate(username, credentials);
+
+ if (authenticatedUser == null) {
+ registerAuthFailure(username);
+ } else {
+ registerAuthSuccess(username);
+ }
+ return authenticatedUser;
+ }
+
+
+ /**
+ * Return the Principal associated with the specified username, which
+ * matches the digest calculated using the given parameters using the
+ * method described in RFC 2069; otherwise return null.
+ *
+ * @param username Username of the Principal to look up
+ * @param clientDigest Digest which has been submitted by the client
+ * @param nOnce Unique (or supposedly unique) token which has been used
+ * for this request
+ * @param realm Realm name
+ * @param md5a2 Second MD5 digest used to calculate the digest :
+ * MD5(Method + ":" + uri)
+ */
+ public Principal authenticate(String username, String clientDigest,
+ String once, String nc, String cnonce, String qop,
+ String realmName, String md5a2) {
+
+ if (isLocked(username)) {
+ // Trying to authenticate a locked user is an automatic failure
+ registerAuthFailure(username);
+
+ log.warn(sm.getString("lockOutRealm.authLockedUser", username));
+ return null;
+ }
+
+ Principal authenticatedUser = super.authenticate(username, clientDigest,
+ once, nc, cnonce, qop, realmName, md5a2);
+
+ if (authenticatedUser == null) {
+ registerAuthFailure(username);
+ } else {
+ registerAuthSuccess(username);
+ }
+ return authenticatedUser;
+ }
+
+
+ /**
+ * Return the Principal associated with the specified username and
+ * credentials, if there is one; otherwise return null.
+ *
+ * @param username Username of the Principal to look up
+ * @param credentials Password or other credentials to use in
+ * authenticating this username
+ */
+ public Principal authenticate(String username, String credentials) {
+ if (isLocked(username)) {
+ // Trying to authenticate a locked user is an automatic failure
+ registerAuthFailure(username);
+
+ log.warn(sm.getString("lockOutRealm.authLockedUser", username));
+ return null;
+ }
+
+ Principal authenticatedUser = super.authenticate(username, credentials);
+
+ if (authenticatedUser == null) {
+ registerAuthFailure(username);
+ } else {
+ registerAuthSuccess(username);
+ }
+ return authenticatedUser;
+ }
+
+
+ /**
+ * Return the Principal associated with the specified chain of X509
+ * client certificates. If there is none, return null.
+ *
+ * @param certs Array of client certificates, with the first one in
+ * the array being the certificate of the client itself.
+ */
+ public Principal authenticate(X509Certificate[] certs) {
+ String username = null;
+ if (certs != null && certs.length >0) {
+ username = certs[0].getSubjectDN().getName();
+ }
+
+ if (isLocked(username)) {
+ // Trying to authenticate a locked user is an automatic failure
+ registerAuthFailure(username);
+
+ log.warn(sm.getString("lockOutRealm.authLockedUser", username));
+ return null;
+ }
+
+ Principal authenticatedUser = super.authenticate(certs);
+
+ if (authenticatedUser == null) {
+ registerAuthFailure(username);
+ } else {
+ registerAuthSuccess(username);
+ }
+ return authenticatedUser;
+ }
+
+
+ /**
+ * Unlock the specified username. This will remove all records of
+ * authentication failures for this user.
+ *
+ * @param username The user to unlock
+ */
+ public void unlock(String username) {
+ // Auth success clears the lock record so...
+ registerAuthSuccess(username);
+ }
+
+ /*
+ * Checks to see if the current user is locked. If this is associated with
+ * a login attempt, then the last access time will be recorded and any
+ * attempt to authenticated a locked user will log a warning.
+ */
+ private boolean isLocked(String username) {
+ LockRecord lockRecord = null;
+ synchronized (this) {
+ lockRecord = failedUsers.get(username);
+ }
+
+ // No lock record means user can't be locked
+ if (lockRecord == null) {
+ return false;
+ }
+
+ // Check to see if user is locked
+ if (lockRecord.getFailures() >= failureCount &&
+ (System.currentTimeMillis() -
+ lockRecord.getLastFailureTime())/1000 < lockOutTime) {
+ return true;
+ }
+
+ // User has not, yet, exceeded lock thresholds
+ return false;
+ }
+
+
+ /*
+ * After successful authentication, any record of previous authentication
+ * failure is removed.
+ */
+ private synchronized void registerAuthSuccess(String username) {
+ // Successful authentication means removal from the list of failed users
+ failedUsers.remove(username);
+ }
+
+
+ /*
+ * After a failed authentication, add the record of the failed
+ * authentication.
+ */
+ private void registerAuthFailure(String username) {
+ LockRecord lockRecord = null;
+ synchronized (this) {
+ if (!failedUsers.containsKey(username)) {
+ lockRecord = new LockRecord();
+ failedUsers.put(username, lockRecord);
+ } else {
+ lockRecord = failedUsers.get(username);
+ if (lockRecord.getFailures() >= failureCount &&
+ ((System.currentTimeMillis() -
+ lockRecord.getLastFailureTime())/1000)
+ > lockOutTime) {
+ // User was previously locked out but lockout has now
+ // expired so reset failure count
+ lockRecord.setFailures(0);
+ }
+ }
+ }
+ lockRecord.registerFailure();
+ }
+
+
+ /**
+ * Get the number of failed authentication attempts required to lock the
+ * user account.
+ * @return the failureCount
+ */
+ public int getFailureCount() {
+ return failureCount;
+ }
+
+
+ /**
+ * Set the number of failed authentication attempts required to lock the
+ * user account.
+ * @param failureCount the failureCount to set
+ */
+ public void setFailureCount(int failureCount) {
+ this.failureCount = failureCount;
+ }
+
+
+ /**
+ * Get the period for which an account will be locked.
+ * @return the lockOutTime
+ */
+ public int getLockOutTime() {
+ return lockOutTime;
+ }
+
+
+ /**
+ * Set the period for which an account will be locked.
+ * @param lockOutTime the lockOutTime to set
+ */
+ public void setLockOutTime(int lockOutTime) {
+ this.lockOutTime = lockOutTime;
+ }
+
+
+ /**
+ * Get the maximum number of users for which authentication failure will be
+ * kept in the cache.
+ * @return the cacheSize
+ */
+ public int getCacheSize() {
+ return cacheSize;
+ }
+
+
+ /**
+ * Set the maximum number of users for which authentication failure will be
+ * kept in the cache.
+ * @param cacheSize the cacheSize to set
+ */
+ public void setCacheSize(int cacheSize) {
+ this.cacheSize = cacheSize;
+ }
+
+
+ /**
+ * Get the minimum period a failed authentication must remain in the cache
+ * to avoid generating a warning if it is removed from the cache to make
+ * space for a new entry.
+ * @return the cacheRemovalWarningTime
+ */
+ public int getCacheRemovalWarningTime() {
+ return cacheRemovalWarningTime;
+ }
+
+
+ /**
+ * Set the minimum period a failed authentication must remain in the cache
+ * to avoid generating a warning if it is removed from the cache to make
+ * space for a new entry.
+ * @param cacheRemovalWarningTime the cacheRemovalWarningTime to set
+ */
+ public void setCacheRemovalWarningTime(int cacheRemovalWarningTime) {
+ this.cacheRemovalWarningTime = cacheRemovalWarningTime;
+ }
+
+
+ protected class LockRecord {
+ private AtomicInteger failures = new AtomicInteger(0);
+ private long lastFailureTime = 0;
+
+ public int getFailures() {
+ return failures.get();
+ }
+
+ public void setFailures(int theFailures) {
+ failures.set(theFailures);
+ }
+
+ public long getLastFailureTime() {
+ return lastFailureTime;
+ }
+
+ public void registerFailure() {
+ failures.incrementAndGet();
+ lastFailureTime = System.currentTimeMillis();
+ }
+ }
+}
diff --git a/java/org/apache/catalina/realm/mbeans-descriptors.xml b/java/org/apache/catalina/realm/mbeans-descriptors.xml
index 970064277..a4c9aec35 100644
--- a/java/org/apache/catalina/realm/mbeans-descriptors.xml
+++ b/java/org/apache/catalina/realm/mbeans-descriptors.xml
@@ -34,8 +34,7 @@
type="java.lang.String"/>
The Combined Realm implementation does not support any additional +
The CombinedRealm implementation does not support any additional attributes.
See the Container-Managed Security Guide for more information on setting up container managed security - using the Combined Realm component.
+ using the CombinedRealm component. + +LockOutRealm is an implementation of the Tomcat 6
+ Realm interface that extends the CombinedRealm to provide lock
+ out functionality to provide a user lock out mechanism if there are too many
+ failed authentication attempts in a given period of time.
To ensure correct operation, there is a reasonable degree of + synchronisation in this Realm.
+ +This Realm does not require modification to the underlying Realms or the + associated user storage mecahisms. It achieves this by recording all failed + logins, including those for users that do not exist. To prevent a DOS by + deliberating making requests with invalid users (and hence causing this + cache to grow) the size of the list of users that have failed authentication + is limited.
+ +Sub-realms are defined by nesting Realm elements inside the
+ Realm element that defines the LockOutRealm. Authentication
+ will be attempted against each Realm in the order they are
+ listed. Authentication against any Realm will be sufficient to authenticate
+ the user.
The LockOutRealm implementation supports the following additional + attributes.
+ +If a failed user is removed from the cache because the cache is too + big before it has been in the cache for at least this period of time (in + seconds) a warning message will be logged. Defaults to 3600 (1 hour).
+Number of users that have failed authentication to keep in cache. Over + time the cache will grow to this size and may not shrink. Defaults to + 1000.
+The number of times in a row a user has to fail authentication to be + locked out. Defaults to 5.
+The time (in seconds) a user is locked out for after too many + authentication failures. Defaults to 300 (5 minutes).
+See the Container-Managed Security + Guide for more information on setting up container managed security + using the LockOutRealm component.
@@ -623,9 +679,10 @@If you are using the Combined Realm Implementation +
If you are using the CombinedRealm Implementation or a Realm + that extends the CombinedRealm, e.g. the LockOutRealm, <Realm> elements may be nested inside it.
To configure CombinedRealm, you create a <Realm>
+
To configure a CombinedRealm, you create a <Realm>
element and nest it in your $CATALINA_BASE/conf/server.xml
file within your <Engine> or <Host>.
You can also nest inside a <Context> node in a
@@ -1520,6 +1522,85 @@ UserDatabase Realm and a DataSource Realm.
LockOutRealm is an implementation of the Tomcat 6
+ Realm interface that extends the CombinedRealm to provide lock
+ out functionality to provide a user lock out mechanism if there are too many
+ failed authentication attempts in a given period of time.
To ensure correct operation, there is a reasonable degree of + synchronisation in this Realm.
+ +This Realm does not require modification to the underlying Realms or the + associated user storage mecahisms. It achieves this by recording all failed + logins, including those for users that do not exist. To prevent a DOS by + deliberating making requests with invalid users (and hence causing this + cache to grow) the size of the list of users that have failed authentication + is limited.
+ +Sub-realms are defined by nesting Realm elements inside the
+ Realm element that defines the LockOutRealm. Authentication
+ will be attempted against each Realm in the order they are
+ listed. Authentication against any Realm will be sufficient to authenticate
+ the user.
To configure a LockOutRealm, you create a <Realm>
+ element and nest it in your $CATALINA_BASE/conf/server.xml
+ file within your <Engine> or <Host>.
+ You can also nest inside a <Context> node in a
+ context.xml file. The following attributes are supported by
+ this implementation:
The fully qualified Java class name of this Realm implementation.
+ You MUST specify the value
+ "org.apache.catalina.realm.LockOutRealm" here.
If a failed user is removed from the cache because the cache is too + big before it has been in the cache for at least this period of time (in + seconds) a warning message will be logged. Defaults to 3600 (1 hour).
+Number of users that have failed authentication to keep in cache. Over + time the cache will grow to this size and may not shrink. Defaults to + 1000.
+The number of times in a row a user has to fail authentication to be + locked out. Defaults to 5.
+The time (in seconds) a user is locked out for after too many + authentication failures. Defaults to 300 (5 minutes).
+Here is an example of how your server.xml snippet should look to add lock out +functionality to a UserDatabase Realm.
+ +