Home » geronimo-2.2-source-release » org.apache.geronimo.security.realm.providers » [javadoc | source]

    1   /**
    2    *  Licensed to the Apache Software Foundation (ASF) under one or more
    3    *  contributor license agreements.  See the NOTICE file distributed with
    4    *  this work for additional information regarding copyright ownership.
    5    *  The ASF licenses this file to You under the Apache License, Version 2.0
    6    *  (the "License"); you may not use this file except in compliance with
    7    *  the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    *  Unless required by applicable law or agreed to in writing, software
   12    *  distributed under the License is distributed on an "AS IS" BASIS,
   13    *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    *  See the License for the specific language governing permissions and
   15    *  limitations under the License.
   16    */
   17   package org.apache.geronimo.security.realm.providers;
   18   
   19   import java.io.Serializable;
   20   import java.util.Arrays;
   21   import java.util.Collections;
   22   import java.util.List;
   23   import java.util.Map;
   24   import java.util.HashMap;
   25   import java.util.LinkedList;
   26   import java.util.Iterator;
   27   import javax.security.auth.Subject;
   28   import javax.security.auth.callback.Callback;
   29   import javax.security.auth.callback.CallbackHandler;
   30   import javax.security.auth.callback.NameCallback;
   31   import javax.security.auth.login.LoginException;
   32   import javax.security.auth.login.FailedLoginException;
   33   import javax.security.auth.spi.LoginModule;
   34   
   35   import org.slf4j.Logger;
   36   import org.slf4j.LoggerFactory;
   37   import org.apache.geronimo.security.jaas.JaasLoginModuleUse;
   38   import org.apache.geronimo.security.jaas.WrappingLoginModule;
   39   
   40   /**
   41    * Tracks the number of recent login failures for each user, and starts
   42    * rejecting login attemps if the number of failures in a certain period for a
   43    * particular user gets too high.  The period, number of failures, and lockout
   44    * duration are configurable, but default to 5 failures in 5 minutes cause all
   45    * subsequent attemps to fail for 30 minutes.
   46    *
   47    * This module does not write any Principals into the Subject.
   48    *
   49    * To enable this login module, set your primary login module and any other
   50    * login modules to REQUIRED or OPTIONAL, and list this module in last place,
   51    * set to REQUISITE.
   52    *
   53    * The parameters used by this module are:
   54    * <ul>
   55    *   <li><b>failureCount</b> - The number of failures to allow before subsequent
   56    *                             login attempts automatically fail</li>
   57    *   <li><b>failurePeriodSecs</b> - The window of time the failures must occur
   58    *                                 in in order to cause the lockout</li>
   59    *   <li><b>lockoutDurationSecs</b> - The duration of a lockout caused by
   60    *                                    exceeding the failureCount in
   61    *                                    failurePeriodSecs.</li>
   62    * </ul>
   63    *
   64    * This login module does not check credentials so it should never be able to cause a login to succeed.
   65    * Therefore the lifecycle methods must return false to indicate success or throw a LoginException to indicate failure.
   66    *
   67    * @version $Rev: 653740 $ $Date: 2008-05-06 03:44:18 -0700 (Tue, 06 May 2008) $
   68    */
   69   public class RepeatedFailureLockoutLoginModule implements LoginModule {
   70       private static final Logger log = LoggerFactory.getLogger(RepeatedFailureLockoutLoginModule.class);
   71       
   72       public static final String FAILURE_COUNT_OPTION = "failureCount";
   73       public static final String FAILURE_PERIOD_OPTION = "failurePeriodSecs";
   74       public static final String LOCKOUT_DURATION_OPTION = "lockoutDurationSecs";
   75       public final static List<String> supportedOptions = Collections.unmodifiableList(Arrays.asList(FAILURE_COUNT_OPTION, FAILURE_PERIOD_OPTION, LOCKOUT_DURATION_OPTION));
   76       
   77       private static final HashMap<String, LoginHistory> userData = new HashMap<String, LoginHistory>();
   78       private CallbackHandler handler;
   79       private String username;
   80       private int failureCount = 5;
   81       private int failurePeriod = 5 * 60 * 1000;
   82       private int lockoutDuration = 30 * 60 * 1000;
   83   
   84       /**
   85        * Reads the configuration settings for this module.
   86        */
   87       public void initialize(Subject subject, CallbackHandler callbackHandler,
   88                              Map sharedState, Map options) {
   89           for(Object option: options.keySet()) {
   90               if(!supportedOptions.contains(option) && !JaasLoginModuleUse.supportedOptions.contains(option)
   91                       && !WrappingLoginModule.supportedOptions.contains(option)) {
   92                   log.warn("Ignoring option: "+option+". Not supported.");
   93               }
   94           }
   95           String fcString = (String) options.get(FAILURE_COUNT_OPTION);
   96           if(fcString != null) {
   97               fcString = fcString.trim();
   98               if(!fcString.equals("")) {
   99                   failureCount = Integer.parseInt(fcString);
  100               }
  101           }
  102           String fpString = (String) options.get(FAILURE_PERIOD_OPTION);
  103           if(fpString != null) {
  104               fpString = fpString.trim();
  105               if(!fpString.equals("")) {
  106                   failurePeriod = Integer.parseInt(fpString) * 1000;
  107               }
  108           }
  109           String ldString = (String) options.get(LOCKOUT_DURATION_OPTION);
  110           if(ldString != null) {
  111               ldString = ldString.trim();
  112               if(!ldString.equals("")) {
  113                   lockoutDuration = Integer.parseInt(ldString) * 1000;
  114               }
  115           }
  116           handler = callbackHandler;
  117       }
  118   
  119       /**
  120        * Checks whether the user should be or has been locked out.
  121        */
  122       public boolean login() throws LoginException {
  123           NameCallback user = new NameCallback("User name:");
  124           Callback[] callbacks = new Callback[]{user};
  125           try {
  126               handler.handle(callbacks);
  127           } catch (Exception e) {
  128               throw (LoginException)new LoginException("Unable to process callback: "+e.getMessage()).initCause(e);
  129           }
  130           if(callbacks.length != 1) {
  131               throw new IllegalStateException("Number of callbacks changed by server!");
  132           }
  133           user = (NameCallback) callbacks[0];
  134           username = user.getName();
  135           if(username != null) {
  136               LoginHistory history;
  137               synchronized (userData) {
  138                   history = userData.get(username);
  139               }
  140               if(history != null && !history.isLoginAllowed(lockoutDuration, failurePeriod, failureCount)) {
  141                   username = null;
  142                   throw new FailedLoginException("Maximum login failures exceeded; try again later");
  143               }
  144           }
  145           return false;
  146       }
  147   
  148       /**
  149        * This module does nothing if a login succeeds.
  150        */
  151       public boolean commit() throws LoginException {
  152           return false;
  153       }
  154   
  155       /**
  156        * Notes that (and when) a login failure occured, used to calculate
  157        * whether the user should be locked out.
  158        */
  159       public boolean abort() throws LoginException {
  160           if(username != null) { //work around initial "fake" login
  161               LoginHistory history;
  162               synchronized (userData) {
  163                   history = userData.get(username);
  164                   if(history == null) {
  165                       history = new LoginHistory(username);
  166                       userData.put(username, history);
  167                   }
  168               }
  169               history.addFailure();
  170               username = null;
  171           }
  172           return false;
  173        }
  174   
  175       /**
  176        * This module does nothing on a logout.
  177        */
  178       public boolean logout() throws LoginException {
  179           username = null;
  180           return false;
  181       }
  182   
  183       /**
  184        * Tracks failure attempts for a user, and calculates lockout
  185        * status and expiry, etc.
  186        */
  187       private static class LoginHistory implements Serializable {
  188           private static final long serialVersionUID = 7792298296084531182L;
  189   
  190           private String user;
  191           private LinkedList<Long> data = new LinkedList<Long>();
  192           private long lockExpires = -1;
  193   
  194           public LoginHistory(String user) {
  195               this.user = user;
  196           }
  197   
  198           public String getUser() {
  199               return user;
  200           }
  201   
  202           /**
  203            * Cleans up the failure history and then calculates whether this user
  204            * is locked out or not.
  205            */
  206           public synchronized boolean isLoginAllowed(int lockoutLengthMillis, int failureAgeMillis, int maxFailures) {
  207               long now = System.currentTimeMillis();
  208               cleanup(now - failureAgeMillis);
  209               if(lockExpires > now) {
  210                   return false;
  211               }
  212               if(data.size() >= maxFailures) {
  213                   lockExpires = data.getLast() + lockoutLengthMillis;
  214                   if(lockExpires > now) {
  215                       return false;
  216                   }
  217               }
  218               return true;
  219           }
  220   
  221           /**
  222            * Notes that a failure occured.
  223            */
  224           public synchronized void addFailure() {
  225               data.add(System.currentTimeMillis());
  226           }
  227   
  228           /**
  229            * Cleans up all failure records outside the window of time we care
  230            * about.
  231            */
  232           public synchronized void cleanup(long ignoreOlderThan) {
  233               for (Iterator it = data.iterator(); it.hasNext();) {
  234                   Long time = (Long) it.next();
  235                   if(time < ignoreOlderThan) {
  236                       it.remove();
  237                   } else {
  238                       break;
  239                   }
  240               }
  241           }
  242       }
  243   }

Home » geronimo-2.2-source-release » org.apache.geronimo.security.realm.providers » [javadoc | source]