cancel
Showing results for 
Search instead for 
Did you mean: 

Custom login module for EP7.4 with Captcha

Former Member
0 Kudos

Hi

I am trying to create a custom login module which validates the captcha shown at the login screen using SAP help link:

http://help.sap.com/saphelp_nw73/helpdata/en/48/ff4faf222b3697e10000000a42189b/content.htm?frameset=...

The session is being set in the Captcha servlet which is used to render the image on the login page.

However when I am trying to compare it with input or print the session value, its throwing an exception.

I checked in the NWA logs and it just shows the following error message:

6. com.temp.loginModule.MyLoginModuleClass OPTIONAL ok exception true Authentication did not succeed.

Please help me analyse the error stack. Can someone point where do i check the detailed logs to trace the issue?

Please find below source of my login module.


package com.temp.loginModule;

import java.io.IOException;

import java.util.Map;

import javax.security.auth.login.LoginException;

import javax.security.auth.Subject;

import javax.security.auth.callback.CallbackHandler;

import javax.security.auth.callback.Callback;

import javax.security.auth.callback.NameCallback;

import javax.security.auth.callback.UnsupportedCallbackException;

import nl.captcha.Captcha;

import com.sap.engine.interfaces.security.auth.AbstractLoginModule;

import com.sap.engine.lib.security.http.HttpGetterCallback;

import com.sap.engine.lib.security.http.HttpCallback;

import com.sap.engine.lib.security.LoginExceptionDetails;

import com.sap.engine.lib.security.Principal;

public class MyLoginModuleClass extends AbstractLoginModule{

  private CallbackHandler callbackHandler = null;

  private Subject subject = null;

  private Map sharedState = null;

  private Map options = null;

  // This is the name of the user you have created on

  // the AS Java so you can test the login module

  private String userName = null;

  private boolean successful;

  private boolean nameSet;

  public void initialize(Subject subject, CallbackHandler callbackHandler,

  Map sharedState, Map options) {

  // This is the only required step for the method

  super.initialize(subject, callbackHandler, sharedState, options);

  // Initializing the values of the variables

  this.callbackHandler = callbackHandler;

  this.subject = subject;

  this.sharedState = sharedState;

  this.options = options;

  this.successful = false;

  this.nameSet = false;

  }

  /**

  * Retrieves the user credentials and checks them. This is

  * the first part of the authentication process.

  */

  public boolean login() throws LoginException {

// HttpGetterCallback httpGetterCallback = new HttpGetterCallback(); 

//       httpGetterCallback.setType(HttpCallback.REQUEST_PARAMETER); 

//       httpGetterCallback.setName("captchaInput"); 

       String value = null; 

//

//       try { 

//       callbackHandler.handle(new Callback[] { httpGetterCallback }); 

//           String[] arrayRequestparam = (String[]) httpGetterCallback.getValue(); 

//           if(arrayRequestparam!=null && arrayRequestparam.length>0)

//           value = arrayRequestparam[0]; 

//

//       } catch (UnsupportedCallbackException e) { 

//       throwNewLoginException("An error occurred while trying to validate credentials."); 

//       } catch (IOException e) { 

//            throwUserLoginException(e, LoginExceptionDetails.IO_EXCEPTION); 

//       } 

  value = getRequestValue("captchaInput");

  userName = getRequestValue("j_username");

//

  HttpGetterCallback httpGetterCallbackSessionCaptcha = new HttpGetterCallback(); 

  httpGetterCallbackSessionCaptcha.setType(HttpCallback.SESSION_ATTRIBUTE); 

  httpGetterCallbackSessionCaptcha.setName("myCaptchaLogin"); 

  try { 

  callbackHandler.handle(new Callback[] { httpGetterCallbackSessionCaptcha }); 

  Captcha arraySessionParam = (Captcha) httpGetterCallbackSessionCaptcha.getValue();

// System.out.println("****************************************************httpGetterCallbackSessionCaptcha" + (arraySessionParam==null?"null session":arraySessionParam.getAnswer())+

// "\n captchaInput" + value+"*********************");

  if(arraySessionParam==null || !arraySessionParam.isCorrect(value)){

  throwNewLoginException("Entered code does not match with the image code.Session:"+(arraySessionParam==null?"null":arraySessionParam.getAnswer())+" Param:"+ value);

// throwUserLoginException(new Exception("Entered code does not match with the image code."));

  }

  httpGetterCallbackSessionCaptcha.setValue(null);

  } catch (UnsupportedCallbackException e) { 

  throwNewLoginException("An error occurred while trying to validate credentials."); 

  } catch (IOException e) { 

  throwUserLoginException(e, LoginExceptionDetails.IO_EXCEPTION); 

  }

  // Retrieve the user credentials via the callback

  // handler.

  // In this case we get the user name from the HTTP

  // NameCallback.

// NameCallback nameCallback = new NameCallback("User name: ");

  /* The type and the name specify which part of the HTTP request

  * should be retrieved. For Web container authentication, the

  * supported types are defined in the interface

  * com.sap.engine.lib.security.http.HttpCallback.

  * For programmatical authentication with custom callback

  * handler the supported types depend on the used callback handler.

  */

// try {

// callbackHandler.handle(new Callback[] {nameCallback});

// }

// catch (UnsupportedCallbackException e) {

// return false;

// }

// catch (IOException e) {

// throwUserLoginException(e, LoginExceptionDetails.IO_EXCEPTION);

// }

// userName = nameCallback.getName();

// if( userName == null || userName.length() == 0 ) {

// return false;  

// }

  /* When you know the user name, update the user information

  * using data from the persistence. The operation must

  * be done before the user credentials checks. This method also

  * checks the user name so that if a user with that name does not

  * exist in the active user store, a

  * java.lang.SecurityException is thrown.

  */

// try {

// refreshUserInfo(userName);

// } catch (SecurityException e) {

// throwUserLoginException(e);

// }

  /* Checks if the given user name starts with the specified

  * prefix in the login module options. If no prefix is specified,

  * then all users are trusted.

  */

// String prefix = (String) options.get("user_name_prefix");

// if ((prefix != null) && !userName.startsWith(prefix)) {

// throwNewLoginException("The user is not trusted.");

// }

  /* This is done if the authentication of the login module is    

  * successful.

  * Only one and exactly one login module from the stack must put

  * the user name in the shared state. This user name represents

  * the authenticated user.

  * For example if the login attempt is successful, method

  * getRemoteUser() of

  * the HTTP request will retrieve exactly this name.

  */

  if (sharedState.get(AbstractLoginModule.NAME) == null) {

  sharedState.put(AbstractLoginModule.NAME, userName);

  nameSet = true;

  }

  successful = true;

  return true;

  }

  /**

  * Commit the login. This is the second part of the authentication

  * process.

  * If a user name has been stored by the login() method,

  * the user name is added to the subject as a new principal.

  */

  public boolean commit() throws LoginException {

  if (successful) {

  /* The principals that are added to the subject should

  * implement java.security.Principal.You can use the class

  * com.sap.engine.lib.security.Principal for this purpose.

  */

  Principal principal = new Principal(userName);

  subject.getPrincipals().add(principal);

  /* If the login is successful, then the principal corresponding

  * to the <userName> (the same user name that has been added

  * to the subject) must be added in the shared state too.

  * This principal is considered to be the main principal

  * representing the user.

  * For example, this principal will be retrieved from method

  * getUserPrincipal() of the HTTP request.

  */

  if (nameSet) {

  sharedState.put(AbstractLoginModule.PRINCIPAL, principal);

  }

  } else {

  userName = null;

  }

  return true;

  }

  /**

  * Abort the authentication process.

  */

  public boolean abort() throws LoginException {

  if (successful) {

  userName = null;

  successful = false;

  }

  return true;

  }

  /**

  * Log out the user. Also removes the principals and

  * destroys or removes the credentials that were associated 

  * with the user during the commit phase.

  */

  public boolean logout() throws LoginException {

  // Remove principals and credentials from subject

  if (successful) {

  subject.getPrincipals(Principal.class).clear();

  successful = false;

  }

  return true;

  }

  private String getRequestValue(String parameterName) 

     throws LoginException { 

       HttpGetterCallback httpGetterCallback = new HttpGetterCallback(); 

       httpGetterCallback.setType(HttpCallback.REQUEST_PARAMETER); 

       httpGetterCallback.setName(parameterName); 

       String value = null; 

       try { 

      callbackHandler.handle(new Callback[] { httpGetterCallback }); 

           String[] arrayRequestparam = (String[]) httpGetterCallback.getValue(); 

           value = arrayRequestparam[0]; 

       } catch (UnsupportedCallbackException e) { 

            return null; 

       } catch (IOException e) { 

            throwUserLoginException(e, LoginExceptionDetails.IO_EXCEPTION); 

       } 

       return value; 

  } 

}

Regards

Ramanender Singh

Accepted Solutions (0)

Answers (1)

Answers (1)

Ivan-Mirisola
Product and Topic Expert
Product and Topic Expert
0 Kudos

Ramanender,


JAAS modules usually requires a restart whenever you need to change them. So be very careful with what you expect once you re-deploy your code.

Once the library is loaded it will never reload itself until you perform a restart of the VM. 

Connect to the debug port may help, but basic debugging will not take you too far either.


I would recommend you to use the log tracing facility on your code. Just enter the following class attribute:



import com.sap.tc.logging.Location;

.......

private static final Location trace = Location.getLocation(<your_classname_here>.class);

......

trace.warningT("Some Warning Text Here..." + variable here);

trace.debugT("Some Warning Text Here..." + variable here);

You may need to go NWA and set the Location Severity Level to Debug according to your needs.

Leave the trace code on your module for IT personnel to debug it if necessary. Don't forget to have the severity level of your code properly set.

Meaning: You don't want to have every trace message your module sills out with warningT() or infoT().

There is a excellent blog here on how this works


Then you will be able to inspect some variable contents while the callbackhandler is being executed.

Pay special attention with the timing - variables have a lifetime when dealing with login modules.

Use the entering(<method_name>) and exiting(<method_name> just ot make sure where in the code the variable should be populated and when.


BR,

Ivan

ramanender_singh
Explorer
0 Kudos

Thanks Ivan for the reply.

I have checked the issue and found that the session attribute being set on the login page is not communicated to my logon module. I have found a workaround for this using by passing the values from login page and now the authentication is happening successfully.

Now the issue is that I want to do this development using NWDI. The current method of implementation of logon module requires direct deployment of the archive.

Can this be implemented through DCs?

I have done the following while trying to achieve this:

1. Logon module code implemented in JAVA DC

2. Create Assembly Public parts of this class and package tree

3. Created a EAR DC which has dependency set as the Assembly part (as in point 2)

4. Create a Logon module in NWA with class (created in point 1)

However when i use the created logon module in any policy configuration, the log shows its not able to locate the class file.

I can see the EAR application in NWA/classloader and the required jar in its resources. Please let me know the class name to specified while creating Logon Module i.e. do we need to include the vendor name~application name etc?

Ivan-Mirisola
Product and Topic Expert
Product and Topic Expert
0 Kudos

You should either mark this as answered or continue comments on the original post until you feel comfortable with an answer.

Since you have another issue, post it under a new question and I will be happy to answer it to you.