Save This Page
Home » JBoss-5.1.0 » org » jboss » security » auth » spi » [javadoc | source]
    1   /*
    2   * JBoss, Home of Professional Open Source
    3   * Copyright 2005, JBoss Inc., and individual contributors as indicated
    4   * by the @authors tag. See the copyright.txt in the distribution for a
    5   * full listing of individual contributors.
    6   *
    7   * This is free software; you can redistribute it and/or modify it
    8   * under the terms of the GNU Lesser General Public License as
    9   * published by the Free Software Foundation; either version 2.1 of
   10   * the License, or (at your option) any later version.
   11   *
   12   * This software is distributed in the hope that it will be useful,
   13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   15   * Lesser General Public License for more details.
   16   *
   17   * You should have received a copy of the GNU Lesser General Public
   18   * License along with this software; if not, write to the Free
   19   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
   20   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
   21   */
   22   package org.jboss.security.auth.spi;
   23   
   24   import java.io.IOException;
   25   import java.lang.reflect.InvocationTargetException;
   26   import java.lang.reflect.Method;
   27   import java.security.Principal;
   28   import java.util.HashMap;
   29   import java.util.Map;
   30   
   31   import javax.security.auth.Subject;
   32   import javax.security.auth.callback.Callback;
   33   import javax.security.auth.callback.CallbackHandler;
   34   import javax.security.auth.callback.NameCallback;
   35   import javax.security.auth.callback.PasswordCallback;
   36   import javax.security.auth.callback.UnsupportedCallbackException;
   37   import javax.security.auth.login.FailedLoginException;
   38   import javax.security.auth.login.LoginException;
   39   
   40   import org.jboss.crypto.digest.DigestCallback;
   41   
   42   
   43   /** An abstract subclass of AbstractServerLoginModule that imposes
   44    * an identity == String username, credentials == String password view on
   45    * the login process.
   46    * <p>
   47    * Subclasses override the <code>getUsersPassword()</code>
   48    * and <code>getRoleSets()</code> methods to return the expected password and roles
   49    * for the user.
   50    *
   51    * @see #getUsername()
   52    * @see #getUsersPassword()
   53    * @see #getRoleSets()
   54    * @see #createIdentity(String)
   55    
   56    @author Scott.Stark@jboss.org
   57    @version $Revision: 86122 $
   58    */
   59   public abstract class UsernamePasswordLoginModule extends AbstractServerLoginModule
   60   {
   61      /** The login identity */
   62      private Principal identity;
   63      /** The proof of login identity */
   64      private char[] credential;
   65      /** the message digest algorithm used to hash passwords. If null then
   66       plain passwords will be used. */
   67      private String hashAlgorithm = null;
   68     /** the name of the charset/encoding to use when converting the password
   69      String to a byte array. Default is the platform's default encoding.
   70      */
   71      private String hashCharset = null;
   72      /** the string encoding format to use. Defaults to base64. */
   73      private String hashEncoding = null;
   74      /** A flag indicating if the password comparison should ignore case */
   75      private boolean ignorePasswordCase;
   76      /** A flag indicating if the store password should be hashed using the hashAlgorithm  */
   77      private boolean hashStorePassword;
   78   
   79      /** A flag indicating if the user supplied password should be hashed using the hashAlgorithm */
   80      private boolean hashUserPassword = true;
   81      /** A flag that restores the ability to override the createPasswordHash(String,String) */
   82      private boolean legacyCreatePasswordHash;
   83      
   84      /** A flag that indicates whether validation errors should be exposed to clients or not */
   85      private boolean throwValidateError = false;
   86      /** A {@code Throwable} representing the validation error */
   87      private Throwable validateError; 
   88   
   89      /** The input validator instance used to validate the username and password supplied by the client. */
   90      private InputValidator inputValidator = null;
   91      
   92      /** Override the superclass method to look for the following options after
   93       first invoking the super version.
   94       @param options :
   95       option: hashAlgorithm - the message digest algorithm used to hash passwords.
   96       If null then plain passwords will be used.
   97       option: hashCharset - the name of the charset/encoding to use when converting
   98       the password String to a byte array. Default is the platform's default
   99       encoding.
  100       option: hashEncoding - the string encoding format to use. Defaults to base64.
  101       option: ignorePasswordCase: A flag indicating if the password comparison
  102         should ignore case.
  103       option: digestCallback - The class name of the DigestCallback {@link org.jboss.crypto.digest.DigestCallback}
  104         implementation that includes pre/post digest content like salts for hashing
  105         the input password. Only used if hashAlgorithm has been specified.
  106       option: hashStorePassword - A flag indicating if the store password returned
  107         from #getUsersPassword() should be hashed .
  108       option: hashUserPassword - A flag indicating if the user entered password should be hashed.
  109       option: storeDigestCallback - The class name of the DigestCallback {@link org.jboss.crypto.digest.DigestCallback}
  110         implementation that includes pre/post digest content like salts for hashing
  111         the store/expected password. Only used if hashStorePassword or hashUserPassword is true and
  112         hashAlgorithm has been specified.
  113       */
  114      @Override
  115      public void initialize(Subject subject, CallbackHandler callbackHandler,
  116         Map<String,?> sharedState, Map<String,?> options)
  117      {
  118         super.initialize(subject, callbackHandler, sharedState, options);
  119   
  120         // Check to see if password hashing has been enabled.
  121         // If an algorithm is set, check for a format and charset.
  122         hashAlgorithm = (String) options.get("hashAlgorithm");
  123         if( hashAlgorithm != null )
  124         {
  125            hashEncoding = (String) options.get("hashEncoding");
  126            if( hashEncoding == null )
  127               hashEncoding = Util.BASE64_ENCODING;
  128            hashCharset = (String) options.get("hashCharset");
  129            if( log.isTraceEnabled() )
  130            {
  131               log.trace("Password hashing activated: algorithm = " + hashAlgorithm
  132                  + ", encoding = " + hashEncoding
  133                  + ", charset = " + (hashCharset == null ? "{default}" : hashCharset)
  134                  + ", callback = " + options.get("digestCallback")
  135                  + ", storeCallback = " + options.get("storeDigestCallback")
  136               );
  137            }
  138         }
  139         String flag = (String) options.get("ignorePasswordCase");
  140         ignorePasswordCase = Boolean.valueOf(flag).booleanValue();
  141         flag = (String) options.get("hashStorePassword");
  142         hashStorePassword = Boolean.valueOf(flag).booleanValue();
  143         flag = (String) options.get("hashUserPassword");
  144         if( flag != null )
  145            hashUserPassword = Boolean.valueOf(flag).booleanValue();
  146         flag = (String) options.get("legacyCreatePasswordHash");
  147         if( flag != null )
  148            legacyCreatePasswordHash = Boolean.valueOf(flag).booleanValue();
  149         flag = (String) options.get("throwValidateError");
  150         if(flag != null)
  151            this.throwValidateError = Boolean.valueOf(flag).booleanValue();
  152         // instantiate the input validator class.
  153         flag = (String) options.get("inputValidator");
  154         if(flag != null)
  155         {
  156            try
  157            {
  158               Class<?> validatorClass = SecurityActions.loadClass(flag); 
  159               this.inputValidator = (InputValidator) validatorClass.newInstance();
  160            }
  161            catch(Exception e)
  162            {
  163               this.log.debug("Unable to instantiate input validator class: " + flag);
  164            }
  165         }
  166      }
  167   
  168      /** Perform the authentication of the username and password.
  169       */
  170      @Override
  171      @SuppressWarnings("unchecked")
  172      public boolean login() throws LoginException
  173      {
  174         // See if shared credentials exist
  175         if( super.login() == true )
  176         {
  177            // Setup our view of the user
  178            Object username = sharedState.get("javax.security.auth.login.name");
  179            if( username instanceof Principal )
  180               identity = (Principal) username;
  181            else
  182            {
  183               String name = username.toString();
  184               try
  185               {
  186                  identity = createIdentity(name);
  187               }
  188               catch(Exception e)
  189               {
  190                  log.debug("Failed to create principal", e);
  191                  throw new LoginException("Failed to create principal: "+ e.getMessage());
  192               }
  193            }
  194            Object password = sharedState.get("javax.security.auth.login.password");
  195            if( password instanceof char[] )
  196               credential = (char[]) password;
  197            else if( password != null )
  198            {
  199               String tmp = password.toString();
  200               credential = tmp.toCharArray();
  201            }
  202            return true;
  203         }
  204   
  205         super.loginOk = false;
  206         String[] info = getUsernameAndPassword();
  207         String username = info[0];
  208         String password = info[1];
  209         
  210         // validate the retrieved username and password.
  211         if(this.inputValidator != null)
  212         {
  213            try
  214            {
  215               this.inputValidator.validateUsernameAndPassword(username, password);
  216            }
  217            catch(InputValidationException ive)
  218            {
  219               throw new FailedLoginException(ive.getMessage());
  220            }
  221         }
  222   
  223         if( username == null && password == null )
  224         {
  225            identity = unauthenticatedIdentity;
  226            super.log.trace("Authenticating as unauthenticatedIdentity="+identity);
  227         }
  228   
  229         if( identity == null )
  230         {
  231            try
  232            {
  233               identity = createIdentity(username);
  234            }
  235            catch(Exception e)
  236            {
  237               log.debug("Failed to create principal", e);
  238               throw new LoginException("Failed to create principal: "+ e.getMessage());
  239            }
  240   
  241            // Hash the user entered password if password hashing is in use
  242            if( hashAlgorithm != null && hashUserPassword == true )
  243               password = createPasswordHash(username, password, "digestCallback");
  244            // Validate the password supplied by the subclass
  245            String expectedPassword = getUsersPassword();
  246            // Allow the storeDigestCallback to hash the expected password
  247            if( hashAlgorithm != null && hashStorePassword == true )
  248               expectedPassword = createPasswordHash(username, expectedPassword, "storeDigestCallback");
  249            if( validatePassword(password, expectedPassword) == false )
  250            {
  251               Throwable ex = getValidateError();
  252               FailedLoginException fle = new FailedLoginException("Password Incorrect/Password Required");
  253               if( ex != null && this.throwValidateError == true)
  254               {
  255                  log.debug("Bad password for username="+username, ex);
  256                  fle.initCause(ex);
  257               }
  258               else
  259               {
  260                  log.debug("Bad password for username="+username);
  261               }
  262               throw fle;
  263            }
  264         }
  265   
  266         if( getUseFirstPass() == true )
  267         {    // Add the username and password to the shared state map
  268            sharedState.put("javax.security.auth.login.name", username);
  269            sharedState.put("javax.security.auth.login.password", credential);
  270         }
  271         super.loginOk = true;
  272         super.log.trace("User '" + identity + "' authenticated, loginOk="+loginOk);
  273         return true;
  274      }
  275   
  276      @Override
  277      protected Principal getIdentity()
  278      {
  279         return identity;
  280      }
  281      @Override
  282      protected Principal getUnauthenticatedIdentity()
  283      {
  284         return unauthenticatedIdentity;
  285      }
  286   
  287      protected Object getCredentials()
  288      {
  289         return credential;
  290      }
  291      protected String getUsername()
  292      {
  293         String username = null;
  294         if( getIdentity() != null )
  295            username = getIdentity().getName();
  296         return username;
  297      }
  298   
  299      /** Called by login() to acquire the username and password strings for
  300       authentication. This method does no validation of either.
  301       @return String[], [0] = username, [1] = password
  302       @exception LoginException thrown if CallbackHandler is not set or fails.
  303       */
  304      protected String[] getUsernameAndPassword() throws LoginException
  305      {
  306         String[] info = {null, null};
  307         // prompt for a username and password
  308         if( callbackHandler == null )
  309         {
  310            throw new LoginException("Error: no CallbackHandler available " +
  311            "to collect authentication information");
  312         }
  313         
  314         NameCallback nc = new NameCallback("User name: ", "guest");
  315         PasswordCallback pc = new PasswordCallback("Password: ", false);
  316         Callback[] callbacks = {nc, pc};
  317         String username = null;
  318         String password = null;
  319         try
  320         {
  321            callbackHandler.handle(callbacks);
  322            username = nc.getName();
  323            char[] tmpPassword = pc.getPassword();
  324            if( tmpPassword != null )
  325            {
  326               credential = new char[tmpPassword.length];
  327               System.arraycopy(tmpPassword, 0, credential, 0, tmpPassword.length);
  328               pc.clearPassword();
  329               password = new String(credential);
  330            }
  331         }
  332         catch(IOException e)
  333         {
  334            LoginException le = new LoginException("Failed to get username/password");
  335            le.initCause(e);
  336            throw le;
  337         }
  338         catch(UnsupportedCallbackException e)
  339         {
  340            LoginException le = new LoginException("CallbackHandler does not support: " + e.getCallback());
  341            le.initCause(e);
  342            throw le;
  343         }
  344         info[0] = username;
  345         info[1] = password;
  346         return info;
  347      }
  348   
  349     /**
  350      * If hashing is enabled, this method is called from <code>login()</code>
  351      * prior to password validation.
  352      * <p>
  353      * Subclasses may override it to provide customized password hashing,
  354      * for example by adding user-specific information or salting. If the
  355      * legacyCreatePasswordHash option is set, this method tries to delegate
  356      * to the legacy createPasswordHash(String, String) method via reflection
  357      * and this is the value returned.
  358      * <p>
  359      * The default version calculates the hash based on the following options:
  360      * <ul>
  361      * <li><em>hashAlgorithm</em>: The digest algorithm to use.
  362      * <li><em>hashEncoding</em>: The format used to store the hashes (base64 or hex)
  363      * <li><em>hashCharset</em>: The encoding used to convert the password to bytes
  364      * for hashing.
  365      * <li><em>digestCallback</em>: The class name of the
  366      * org.jboss.security.auth.spi.DigestCallback implementation that includes
  367      * pre/post digest content like salts.
  368      * </ul>
  369      * It will return null if the hash fails for any reason, which will in turn
  370      * cause <code>validatePassword()</code> to fail.
  371      * 
  372      * @param username ignored in default version
  373      * @param password the password string to be hashed
  374      * @param digestOption - the login module option name of the DigestCallback
  375      * @throws SecurityException - thrown if there is a failure to load the
  376      *  digestOption DigestCallback
  377      */
  378      @SuppressWarnings("unchecked")
  379      protected String createPasswordHash(String username, String password,
  380        String digestOption)
  381        throws LoginException
  382      {
  383         // Support for 4.0.2 createPasswordHash(String, String) override
  384         if( legacyCreatePasswordHash )
  385         {
  386            try
  387            {
  388               // Try to invoke the subclass createPasswordHash(String, String)
  389               Class<?>[] sig = {String.class, String.class};
  390               Method createPasswordHash = getClass().getMethod("createPasswordHash", sig);
  391               Object[] args = {username, password};
  392               String passwordHash = (String) createPasswordHash.invoke(this, args);
  393               return passwordHash;
  394            }
  395            catch (InvocationTargetException e)
  396            {
  397               LoginException le = new LoginException("Failed to delegate createPasswordHash");
  398               le.initCause(e.getTargetException());
  399               throw le;
  400            }
  401            catch(Exception e)
  402            {
  403               LoginException le = new LoginException("Failed to delegate createPasswordHash");
  404               le.initCause(e);
  405               throw le;            
  406            }
  407         }
  408   
  409         DigestCallback callback = null;
  410         String callbackClassName = (String) options.get(digestOption);
  411         if( callbackClassName != null )
  412         {
  413            try
  414            {
  415               ClassLoader loader = SecurityActions.getContextClassLoader();
  416               Class<?> callbackClass = loader.loadClass(callbackClassName);
  417               callback = (DigestCallback) callbackClass.newInstance();
  418               if( log.isTraceEnabled() )
  419                  log.trace("Created DigestCallback: "+callback);
  420            }
  421            catch (Exception e)
  422            {
  423               if( log.isTraceEnabled() )
  424                  log.trace("Failed to load DigestCallback", e);
  425               SecurityException ex = new SecurityException("Failed to load DigestCallback");
  426               ex.initCause(e);
  427               throw ex;
  428            }
  429            Map<String,Object> tmp = new HashMap<String,Object>();
  430            tmp.putAll(options);
  431            tmp.put("javax.security.auth.login.name", username);
  432            tmp.put("javax.security.auth.login.password", password);
  433   
  434            callback.init(tmp);
  435            // Check for a callbacks
  436            Callback[] callbacks = (Callback[]) tmp.get("callbacks");
  437            if( callbacks != null )
  438            {
  439               try
  440               {
  441                  callbackHandler.handle(callbacks);
  442               }
  443               catch(IOException e)
  444               {
  445                  LoginException le = new LoginException(digestOption+" callback failed");
  446                  le.initCause(e);
  447                  throw le;
  448               }
  449               catch(UnsupportedCallbackException e)
  450               {
  451                  LoginException le = new LoginException(digestOption+" callback failed");
  452                  le.initCause(e);
  453                  throw le;
  454               }
  455            }
  456         }
  457         String passwordHash = Util.createPasswordHash(hashAlgorithm, hashEncoding,
  458            hashCharset, username, password, callback);
  459         return passwordHash;
  460      }
  461   
  462      /**
  463       * Get the error associated with the validatePassword failure
  464       * @return the Throwable seen during validatePassword, null if no
  465       * error occurred.
  466       */
  467      protected Throwable getValidateError()
  468      {
  469         return validateError;
  470      }
  471   
  472      /**
  473       * Set the error associated with the validatePassword failure
  474       * @param validateError
  475       */
  476      protected void setValidateError(Throwable validateError)
  477      {
  478         this.validateError = validateError;
  479      }
  480   
  481      /** A hook that allows subclasses to change the validation of the input
  482       password against the expected password. This version checks that
  483       neither inputPassword or expectedPassword are null that that
  484       inputPassword.equals(expectedPassword) is true;
  485       @return true if the inputPassword is valid, false otherwise.
  486       */
  487      protected boolean validatePassword(String inputPassword, String expectedPassword)
  488      {
  489         if( inputPassword == null || expectedPassword == null )
  490            return false;
  491         boolean valid = false;
  492         if( ignorePasswordCase == true )
  493            valid = inputPassword.equalsIgnoreCase(expectedPassword);
  494         else
  495            valid = inputPassword.equals(expectedPassword);
  496         return valid;
  497      }
  498   
  499   
  500      /** Get the expected password for the current username available via
  501       the getUsername() method. This is called from within the login()
  502       method after the CallbackHandler has returned the username and
  503       candidate password.
  504       @return the valid password String
  505       */
  506      abstract protected String getUsersPassword() throws LoginException;
  507      
  508   }

Save This Page
Home » JBoss-5.1.0 » org » jboss » security » auth » spi » [javadoc | source]