Wednesday, July 9, 2014

ADF: Programatic user authentication instead of j_security_check (Password retrieval )

When we create ADF project and enable security, system creates a login page for us. Which has following code.


<form method="POST" action="j_security_check">
      <table cellspacing="2" cellpadding="3" border="1">
        <tr>          <th>Username:</th>
          <td>            <input type="text" name="j_username"/>          </td>
        </tr>
        
        <tr>          <th>Password:</th>
          <td>            <input type="password" name="j_password"/>          </td>
        </tr>
      </table>
      <p>
        <input type="submit" name="submit" value="Submit"/>
      </p>
    </form>

When user clicks on submit button, login form get submitted. It calls j_security_check, which is provided by framework. It validates username/password against ldap configured in weblogic.

We do not get any point to know what was username/password. At times we may want to derive  username/password combination. A usecase could be integration with third party. Imagine you want to integrate with third party system which takes username/password. For example sharepoint webservices. I tried checking OPSS apis but seems like there is no way to retrieve password using them. OPSS apis not retrieving password makes sense also.

To solve our problem I can think of a solution to capture user password while he is trying to login and if authentication is fine store user password in sessionscope. Use it later when calling third party system.

I personally think there might be better ways to do it but I could not find any solution. In this blog I am just describing a way to authenticate user programmatically instead of j_security_check.

Here are the steps:

1. Change login.html to login.jspx: Instead of using normal login.html page lets create a jspx page for login.
Put following form entry in it.

<af:panelFormLayout id="pfl2" styleClass="form_layout">
                        <f:facet name="footer"/>
                        <af:inputText label="#{portalBundle.USER_NAME}:" id="it1"
                                      value="#{viewScope.username}"
                                      required="true" maximumLength="50"
                                      partialTriggers="cb1"/>
                        <af:inputText label="#{portalBundle.PASSWORD}:" id="it2"
                                      value="#{viewScope.password}"
                                      secret="true" required="true"
                                      maximumLength="50" partialTriggers="cb1"
                                      shortDesc="Enter Password"/>
                        <af:commandButton id="cb1"
                                          action="#{backingBeanScope.LoginPageBean.doLogin}"
                                          text="#{portalBundle.LOGIN}"
                                          styleClass="login"
                                          partialSubmit="true"/>
                      </af:panelFormLayout>


In above code commandButton-cb1 does a partial submit and calls doLogin method of LoginPageBean. Username and password are stored in viewscope.

LoginPageBean code should look like



import com.kbace.hrc.common.util.diagnostic.DiagnosticUtil;


import java.io.IOException;

import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;

import javax.security.auth.Subject;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import oracle.adf.share.ADFContext;
import oracle.adf.share.logging.ADFLogger;
import oracle.adf.share.security.SecurityContext;
import oracle.adf.view.rich.component.rich.RichPopup;
import oracle.adf.view.rich.component.rich.output.RichOutputText;

import weblogic.security.URLCallbackHandler;
import weblogic.security.services.Authentication;

import weblogic.servlet.security.ServletAuthentication;


public class LoginPageBean {
    private static ADFLogger _logger =
        ADFLogger.createADFLogger(LoginPageBean.class);
    private RichOutputText pageInit;
    private RichPopup loginErrorPopup;
    private RichPopup userRoleErrorPopup;

    public LoginPageBean() {
    }
    private String _username, _password;


       public String doLogin() {
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
        _username = (String)ADFUtil.evaluateEL("#{viewScope.username}");
        _password = (String)ADFUtil.evaluateEL("#{viewScope.password}");
        String un = _username;
        byte[] pw = _password.getBytes();
        FacesContext ctx = FacesContext.getCurrentInstance();
        HttpServletRequest request = (HttpServletRequest)ctx.getExternalContext().getRequest();
        String loginStatus = "login_failed";
        try {
            Subject subject =   Authentication.login(new URLCallbackHandler(un, pw));
            weblogic.servlet.security.ServletAuthentication.runAs(subject,     request);
            ServletAuthentication.generateNewSessionID(request);

            loginStatus = "success";
             String redirecturl = "/adfAuthentication?success_url=/index.html";
            Map session = (Map)ADFUtil.evaluateEL("#{sessionScope}");
            session.put("userPassword", _password);
            session.put("username", _username);

                try {
                   FacesContext.getCurrentInstance().getExternalContext().redirect(FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath() + redirecturl);
               
                } catch (Exception nm) {
                    nm.printStackTrace();
                }
          
        } catch (FailedLoginException fle) {
            ADFUtil.getRequestFlowScope().put("pShowAuthError", "Y");
            RichPopup.PopupHints pph=new RichPopup.PopupHints();
            this.getLoginErrorPopup().show(pph);
            ADFUtil.setEL("#{viewScope.password}", null);

        } catch (LoginException le) {
            FacesMessage msg =
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error Logging In",
                                 "Error Logging In");
            ctx.addMessage(null, msg);

        }
        return null;
    }



}




Above method doLogin tries following things
1. Get username/password from viewscope
2. Authenticate username/password using weblogic security apis. 
3. If valid username/password then start a new session.
4. Store username/password in sessionscope
5. Redirect to index.html if successful login.
6. Show error popup if authentication fails.
   

Also we need to add following libraries in our project


a.       com.bea.core.weblogic.security.auth_1.1.0.0.jar: $MW_HOME\modules
b.      com.bea.core.weblogic.security.identity_1.1.2.1.jar: $MW_HOME\modules
c.       ws.api.jar: $MW_HOME\wlserver_10.3\server\lib



I am not very sure if its best way of doing it. I would definitely love to know better ways of doing this but for time being I just know this way.


Disclaimer: Any views or opinions presented in this blog are solely those of the author and do not necessarily represent those of the company.

No comments: