6 Replies Latest reply: Feb 9, 2011 8:29 PM by handat RSS

    AMLoginModule Thread Safety

    807573
      Hello,

      Ler me start by saying, we have had a lot of success with a custom login module in Access Manager paired with our custom user management system. However, recently we have come across some issues.

      Within our custom login module (extends AMLoginModule) we implemented a page for resetting the user's password. This user's id and password are managed by the mentioned custom user management system. Recently we have a batch of users try to reset their passwords at the same time (30 at once), about half of them encountered errors ranging from not being able to reset their password to resetting successfully then not being able to log in with that password.

      My question boils down to, does Access Manager pair the currently logging in user with the same AMLoginModule every time (much like a stateful EJB) or are the AMLoginModules pooled, thus a user can be using a login module for a while then encounter a different one (randomly) throughout the login process?

      Here is the code I am dealing with:
        • 1. Re: AMLoginModule Thread Safety
          807573
          AMLogin.java:
          public class ARMLogin extends AMLoginModule  {
          
              private static final int SUCCESS = -1;
              private static final int DISCLAIMER = 1;
              private static final int LOGIN = 2;
              private static final int INVALIDLOGIN = 3;
              private static final int EMPTYLOGIN = 4;
              private static final int LOCKEDLOGIN = 5;
              private static final int PASSWORDRESET = 6;
              private static final int PASSWORDRESETSUCCESS = 7;
              private static final int PASSWORDRESETERROR = 8;
              private static final int PASSWORDRESETERRORSAMEOLDPW = 9;
              private static final int PASSWORDRESETERRORSAMERESTPW = 10;
          
              private static final String ARM_LOGIN_REMOTE_SERVICE_URL = "kcp-am-auth-armlogin-login-service-url";
               private static final String ARM_LOGIN_MODULE_LOCALE = "amAuthARMLogin";
               
               private Principal principal = null;
               
               private String wsdlUrl = null;
              private URL serviceUrl = null;
               private boolean acceptedDisclaimer = false;
               private LoginRemoteService loginServiceClient = null;
          
              /*]
                * (non-Javadoc)
                * @see com.sun.identity.authentication.spi.AMLoginModule#getPrincipal()
                */
               @Override
               public Principal getPrincipal() {
                    return principal;
               }
          
               /*
                * (non-Javadoc)
                * @see com.sun.identity.authentication.spi.AMLoginModule#init(javax.security.auth.Subject, java.util.Map, java.util.Map)
                */
               @SuppressWarnings("unchecked")
               @Override
               public void init(Subject subject, Map sharedState, Map options) {
                    
                    
                    //load the properties
                  String serviceUri = Misc.getMapAttr(options, ARM_LOGIN_REMOTE_SERVICE_URL);
          
                    if(serviceUri == null){
                         getDebug().error("LoginService URL not specified under " + ARM_LOGIN_REMOTE_SERVICE_URL + " setting to default");
                         serviceUri = ...
                    }
          
                  try {
                      serviceUrl = new URL(serviceUri);
                  } catch (MalformedURLException e) {
                       getDebug().error("URL Error in Service Client" , e);
                  }
          
                  setAuthLevel(1);
          
                  acceptedDisclaimer = false;
                    
               }
          
               /*
                * (non-Javadoc)
                * @see com.sun.identity.authentication.spi.AMLoginModule#process(javax.security.auth.callback.Callback[], int)
                */
               @Override
               public int process(Callback[] callbacks, int state) throws LoginException {
                    int processSuccess = 1;
          
                  //disclaimer sate
                  if (state == DISCLAIMER) {
                      if(callbacks.length > 1 && callbacks[1] instanceof ConfirmationCallback){
                          int index = ((ConfirmationCallback)callbacks[1]).getSelectedIndex();
                          if(index == 0){
                              acceptedDisclaimer = true;
                              processSuccess = LOGIN;
                          }
                      }
                  }
                  //username / password state
                    else if (acceptedDisclaimer && (state == LOGIN || state == INVALIDLOGIN || state == EMPTYLOGIN || state == LOCKEDLOGIN || state == PASSWORDRESETSUCCESS)) {
                         String username = ((NameCallback)callbacks[0]).getName();
                         char[] password = ((PasswordCallback)callbacks[1]).getPassword();
                         if (username == null || username.equals("") || password.length == 0){
                          //empty username / password
                          processSuccess = EMPTYLOGIN;
                         }
                      else{
                          LoginStatus status = null;
          
                          //authorize user via web service.
                          try{
                              status = getLoginRemoteService().login(username, new String(password));
                          }catch(RemoteException e){
                              getDebug().error("Error in Service Client" , e);
                          }
          
          
          
                          //if login failed restart state 2
                          if(status != null){
                              if(status.getType().equals(LoginStatusType.Login.name())){
                                   //success
                                  principal = new LoginPrincipal(username, status.getId());
                                  processSuccess = SUCCESS;
                              }
                              else if(status.getType().equals(LoginStatusType.LoginLockout.name())){
                                  //locked out
                                  processSuccess = LOCKEDLOGIN;
                              }
                              else if(status.getType().equals(LoginStatusType.PasswordReset.name())){
                                  //success for password reset
                                  principal = new LoginPrincipal(username, status.getId());
                                  processSuccess = PASSWORDRESET;
                                  setAuthLevel(11);
                              }
                              else{
                                  //incorrect login
                                  processSuccess = INVALIDLOGIN;
                              }
                          }
                      }
                         
                    }
                  else if(state == PASSWORDRESET || state == PASSWORDRESETERROR || state == PASSWORDRESETERRORSAMEOLDPW || state == PASSWORDRESETERRORSAMERESTPW){
                      char[] password = ((PasswordCallback)callbacks[0]).getPassword();
                      char[] passwordConfirm = ((PasswordCallback)callbacks[1]).getPassword();
          
                      LoginPrincipal principal = (LoginPrincipal)getPrincipal();
                      LoginStatus status = null;
                      try{
                          status = getLoginRemoteService().passwordReset(principal.getId(), new String(password), new String(passwordConfirm));
                      }catch(RemoteException e){
                          getDebug().error("Error in Service Client" , e);
                      }
          
                      //if login failed restart state 2
                      if(status != null){
                          if(status.getType().equals(LoginStatusType.PasswordResetSuccess.name())){
                               //success
                              processSuccess = PASSWORDRESETSUCCESS;
                          }
                          else if(status.getType().equals(LoginStatusType.PasswordResetErrorSameOldPW.name())){
                              //same previous password
                              processSuccess = PASSWORDRESETERRORSAMEOLDPW;
                          }
                          else if(status.getType().equals(LoginStatusType.PasswordResetErrorSameResetPW.name())){
                              //same previous password
                              processSuccess = PASSWORDRESETERRORSAMERESTPW;
                          }
                          else{
                              //incorrect login
                              processSuccess = PASSWORDRESETERROR;
                          }
                      }
                  }
                    
                    return processSuccess;
               }
               
               /**
                * Returns the appropriate debugging logger instance
                * 
                * @return debug logger
                */
               private Debug getDebug(){
                    return Debug.getInstance(ARM_LOGIN_MODULE_LOCALE);
               }
               
               /**
                * Lazily loaded LoginRemoteService
                * 
                * @return LoginRemoteService
                */
               private synchronized LoginRemoteService getLoginRemoteService() {
                    if(loginServiceClient == null){
                         if(serviceUrl == null){
                              getDebug().error("LoginService URL not specified");
                         }
          
                      try{
                          LoginRemoteLocator locator = new LoginRemoteLocator();
                          loginServiceClient = locator.getLoginRemoteServiceImplPort(serviceUrl);
                      }
                      catch (ServiceException e){
                          getDebug().error("Error in Service Client Creation" , e);
                      }
          
                    }
                    return loginServiceClient;
               }
          }
          • 2. Re: AMLoginModule Thread Safety
            807573
            AMLogin.xml:
            <ModuleProperties moduleName="ARMLogin" version="1.0" >
            
                <Callbacks template="Disclaimer.jsp" length="2" order="1" timeout="300" header="">
                    <NameCallback>
                        <Prompt>A</Prompt>
                    </NameCallback>
                    <ConfirmationCallback>
                        <OptionValues>
                                <OptionValue>
                                    <Value>I accept</Value>
                                </OptionValue>
                            </OptionValues>
                    </ConfirmationCallback>
                </Callbacks>
                <Callbacks template="Login.jsp" length="2" order="2" timeout="300" header="">
                    <NameCallback>
                        <Prompt>Username</Prompt>            
                    </NameCallback>
                    <PasswordCallback>            
                        <Prompt>Password</Prompt>
                    </PasswordCallback>
                </Callbacks>
                <Callbacks template="Login.jsp" length="2" order="3" timeout="300" header="Username and/or password incorrect">
                    <NameCallback>
                        <Prompt>Username</Prompt>
                    </NameCallback>
                    <PasswordCallback>
                        <Prompt>Password</Prompt>
                    </PasswordCallback>
                </Callbacks>
                <Callbacks template="Login.jsp" length="2" order="4" timeout="300" header="Please provide a Username and Password">
                    <NameCallback>
                        <Prompt>Username</Prompt>
                    </NameCallback>
                    <PasswordCallback>
                        <Prompt>Password</Prompt>
                    </PasswordCallback>
                </Callbacks>
                <Callbacks template="Login.jsp" length="2" order="5" timeout="300" header="Your account has been locked out due to excessive login attempts.  Please Try again in 15 minutes.">
                    <NameCallback>
                        <Prompt>Username</Prompt>
                    </NameCallback>
                    <PasswordCallback>
                        <Prompt>Password</Prompt>
                    </PasswordCallback>
                </Callbacks>
                <Callbacks template="PasswordReset.jsp" length="3" order="6" timeout="300" header="Change your password...">
                    <PasswordCallback>
                        <Prompt>New Password</Prompt>
                    </PasswordCallback>
                    <PasswordCallback>
                        <Prompt>Confirm Password</Prompt>
                    </PasswordCallback>
                    <ConfirmationCallback>
                        <OptionValues>
                                <OptionValue>
                                    <Value>Change Password</Value>
                                </OptionValue>
                            </OptionValues>
                    </ConfirmationCallback>
                </Callbacks>
                <Callbacks template="Login.jsp" length="3" order="7" timeout="300" header="Your Password was successfully updated.  Please login.">
                    <NameCallback>
                        <Prompt>Username</Prompt>
                    </NameCallback>
                    <PasswordCallback>
                        <Prompt>Password</Prompt>
                    </PasswordCallback>
                </Callbacks>
                <Callbacks template="PasswordReset.jsp" length="3" order="8" timeout="300" header="That Password or the confirmation was invalid.  Please try again.">
                    <PasswordCallback>
                        <Prompt>New Password</Prompt>
                    </PasswordCallback>
                    <PasswordCallback>
                        <Prompt>Confirm Password</Prompt>
                    </PasswordCallback>
                    <ConfirmationCallback>
                        <OptionValues>
                                <OptionValue>
                                    <Value>Change Password</Value>
                                </OptionValue>
                            </OptionValues>
                    </ConfirmationCallback>
                </Callbacks>
                <Callbacks template="PasswordReset.jsp" length="3" order="9" timeout="300" header="You must not reuse your previous password.  Please try again.">
                    <PasswordCallback>
                        <Prompt>New Password</Prompt>
                    </PasswordCallback>
                    <PasswordCallback>
                        <Prompt>Confirm Password</Prompt>
                    </PasswordCallback>
                    <ConfirmationCallback>
                        <OptionValues>
                                <OptionValue>
                                    <Value>Change Password</Value>
                                </OptionValue>
                            </OptionValues>
                    </ConfirmationCallback>
                </Callbacks>
                <Callbacks template="PasswordReset.jsp" length="3" order="10" timeout="300" header="You must not reuse the temporary reset password.  Please try again.">
                    <PasswordCallback>
                        <Prompt>New Password</Prompt>
                    </PasswordCallback>
                    <PasswordCallback>
                        <Prompt>Confirm Password</Prompt>
                    </PasswordCallback>
                    <ConfirmationCallback>
                        <OptionValues>
                                <OptionValue>
                                    <Value>Change Password</Value>
                                </OptionValue>
                            </OptionValues>
                    </ConfirmationCallback>
                </Callbacks>
            </ModuleProperties>
            • 3. Re: AMLoginModule Thread Safety
              807573
              I answered my own question on this one. the AMLoginModule instances are unique per login and are active until "completed." If you're interested see the javadocs:

              [http://docs.sun.com/source/816-6824-10/com/sun/identity/authentication/spi/AMLoginModule.html|http://docs.sun.com/source/816-6824-10/com/sun/identity/authentication/spi/AMLoginModule.html]
              • 4. Re: AMLoginModule Thread Safety
                807573
                I answered my own question by research
                • 5. Re: AMLoginModule Thread Safety
                  823418
                  Hi, I am trying to find the AMLoginModule in Opensso 8.0 and I cant find it. Can you please help me in this issue
                  • 6. Re: AMLoginModule Thread Safety
                    handat
                    in amserver.jar