Sunday, August 30, 2009

JAAS LDAP OC4J

We need a good LDAP tool; the best I found was LDAPSoft LDAP Admin Tool. It is limited trial version. It automatically connects to my network’s LDAP server if connected to LAN and gives a very good view active directory contents.
For website orion-web.xml
For ejb orion-ejb.xml
For application orion-application.xml
Orion-xxx.xml is OC4J propriety file and is not dictated by J2EE specs.
We need to create orion-application.xml using the information given by LDAPSoft.
<?xml version = '1.0' encoding = 'windows-1252'?>
<orion-application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:noNamespaceSchemaLocation="http://xmlns.oracle.com/oracleas/schema/orion-application-10_0.xsd">
 <!--jazn element specifies that I am using custom LDAP provider i.e. Active directory for JAAS. Other options for JAAS are db, file based, Oracle Internet Directory (oracle implementation of LDAP)-->
 <jazn provider="XML">
  <property name="custom.ldap.provider" value="true"></property>
 </jazn>
<!—Role that I will be using in the application. I gave it an app friendly name i.e. RAUser but in the end this app friendly role name must be mapped to a LDAP group whom authenticated users will be member. So, In order to define roles in the application I have to follow a three step process.
1- Define app friendly role name in web.xml under <security-role>
2- Specify authorization access of the role in web.xml under
<security-constraint>….<auth-constraint>. This step is optional.
3- Define mapping of app friendly roles names to backend (db, file based, LDAP) security provider. The first two steps are defined by J2EE specs but this step is specific is container. So, for OC4J I define it in orion-application.xml under <security-role-mapping>
   -->
 <security-role-mapping name="RAUser">
  <group name="VAS IT"></group>
 </security-role-mapping>
<!—- This section defines LDAP connectivity details. The content of this section are copied to system-jazn-data.xml after you deploy your application. All the settings under this section can be mentioned at deployment time using OracleAS Control but I took them to orion-application.xml so that I can reuse this file across projects. For this first time I deployed my application from OAS control but afterwards I copied the below section from system-jazn-data.xml-->
 <jazn-loginconfig>
  <application>
<!—application name that I will use when I deploy the application in OC4J.-->
   <name>RA-ldap</name>
   <login-modules>
    <login-module>
     <class>oracle.security.jazn.login.module.LDAPLoginModule</class>
     <control-flag>required</control-flag>
     <options>
      <option>
       <name>oracle.security.jaas.ldap.connect.pool.prefsize</name>
       <value>10</value>
      </option>
      <option>
       <name>oracle.security.jaas.ldap.connect.pool.initsize</name>
       <value>2</value>
      </option>
      <option>
       <name>oracle.security.jaas.ldap.connect.pool.timeout</name>
       <value>300000</value>
      </option>
      <option>
 <!--ObjectClass of user, for Active directory standard is user. I can verify this from LDAP tool by viewing any user properties-->      <name>oracle.security.jaas.ldap.user.object.class</name>
       <value>user</value>
      </option>
      <option>
       <name>oracle.security.jaas.ldap.provider.connect.pool</name>
       <value>true</value>
      </option>
      <option>
 <!--User credentials that I will use to connect to Active Directory server, for the time being I used my own. I can search and read Active Directory using my own credentials. But you should create credentials specifically for this app and set password expiry to never -->  <name>oracle.security.jaas.ldap.provider.credential</name>
       <value>{903}iezH/eKV6X1BkCbc/j+aKS+K</value>
      </option>
      <option>
       <name>oracle.security.jaas.ldap.provider.type</name>
       <value>Active Directory</value>
      </option>
      <option>
       <name>oracle.security.jaas.ldap.connect.pool.maxsize</name>
       <value>25</value>
      </option>
      <option>
<!--Active directory server info -->
       <name>oracle.security.jaas.ldap.provider.url</name>
       <value>ldap://172.18.70.33:389</value>
      </option>
      <option>
<!--Whether to search for role (IT) directly under oracle.security.jaas.ldap.role.searchbase or in the whole subtree that lies under it which is obviously expensive-->
       <name>oracle.security.jaas.ldap.role.searchscope</name>
       <value>onelevel</value>
      </option>
      <option>
<!--Whether to search for user directly under oracle.security.jaas.ldap.user.searchbase or search whole subtree-->
       <name>oracle.security.jaas.ldap.user.searchscope</name>
       <value>onelevel</value>
      </option>
      <option>
<!-- Search base for role (IT)-->
       <name>oracle.security.jaas.ldap.role.searchbase</name>
       <value>OU=Groups,OU=RASoft,DC=WT,DC=WI,DC=Pri</value>
      </option>
      <option>
<!-- Search base for user-->
       <name>oracle.security.jaas.ldap.user.searchbase</name>
       <value>OU=Executives,OU=IT,OU=Central,OU=RASoft,DC=WT,DC=WI,DC=Pri</value>
      </option>
      <option>
<!—Which LDAP attribute contains group name? It is cn by default for active diretcory-->       <name>oracle.security.jaas.ldap.role.name.attribute</name>
       <value>cn</value>
      </option>
      <option>
<!--ObjectClass of group/role which is group by default for Active Directory-->       <name>oracle.security.jaas.ldap.role.object.class</name>
       <value>group</value>
      </option>
      <option>
<!--Which LDAP attribute contains user name? It is sAMAccountName by default for active diretcory--> <name>oracle.security.jaas.ldap.user.name.attribute</name>
       <value>sAMAccountName</value>
      </option>
      <option>
<!—User credentials for connecting to active directory server-->
       <name>oracle.security.jaas.ldap.provider.user</name>
       <value>wt\struser</value>
      </option>
      <option>
<!--Whether user are direct member of role/group (IT)-->       <name>oracle.security.jaas.ldap.membership.searchscope</name>
       <value>direct</value>
      </option>
      <option>
<!--Which group/role attribute specifies membership? For Active Directory it is by default member-->      
       <name>oracle.security.jaas.ldap.member.attribute</name>
       <value>member</value>
      </option>
      <option>
       <name>oracle.security.jaas.ldap.lm.cache_enabled</name>
       <value>true</value>
      </option>
     </options>
    </login-module>
   </login-modules>
  </application>
 </jazn-loginconfig>
</orion-application>
Now I need to focus on web.xml where I will set appropriate configs to trigger form based authentication. Below is the related excerpt from web.xml.
<!--
    ISSUE:
    If welcom-file-list is enabled partial requests
    enter into redirect loop. e.g.
    http://localhost:8888/RA-ldap/
    or
    http://localhost:8888/RA-ldap/faces
    results in redirect to
    http://localhost:8888/RA-ldap/
    and continues forever.
    -->
<!--Note no slash(/) in start as is the case with other paths specified in web.xml because this is not a path but file name instead-->
    <!--
    <welcome-file-list>         
        <welcome-file>faces/verifyNic.jsp</welcome-file>
    </welcome-file-list>-->
    <security-constraint>
        <display-name>SecurityConstraint</display-name>
        <web-resource-collection>
            <web-resource-name>SecurePages</web-resource-name>
            <description></description>
            <url-pattern>/faces/verifyNic.jsp</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
            <http-method>HEAD</http-method>
            <http-method>PUT</http-method>
            <http-method>OPTIONS</http-method>
            <http-method>TRACE</http-method>
            <http-method>DELETE</http-method>
        </web-resource-collection>
        <auth-constraint>
            <description></description>
            <role-name>RAUser</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>FORM</auth-method>
        <form-login-config>
            <form-login-page>/faces/login.jsp</form-login-page>
            <form-error-page>/faces/login.jsp?login=Incorrect user name / password</form-error-page>
        </form-login-config>
    </login-config>
    <security-role>
     <role-name>RAUser</role-name>
    </security-role>
FORM BASED AUTHENTICATION
In form based auth the login page as specified by <form-login-page> should post two params j_username, j_password to j_security_check servlet. This is what J2EE specs define. The sequence of steps is:
1-    Users requests a protected resource such as in this case verifyNic.jsp
2-    AS determines whether user is already logged in by checking if its session exists on server.
3-    If not logged in AS redirects to <form-login-page>
4-    Login page gets user name and password and post it to j_security_check which authenticates and authorize user and redirects user to url requested in step 1.
To make redirection in step 4 work in OC4J I need to start the server with following java option:
-Doc4j.redirect=true
For stand alone I do it by starting sever like this
>java -Doc4j.redirect=true -jar
$ORACLE_STANDALONE_HOME\j2ee\home\oc4j.jar
And for OAS I need to put this parameter in server properties which can be accessed from OAS control or opmn.xml.
In OAS control click on instance then Administration and then server properties and add it under java options section.
But what if user comes direct to login page or user logs out and tries to login again? To which page will AS redirect user? OC4J sends user to no where and keeps j_security_check in url which displays resource not found (404) error in browser. Some app servers allow another parameter j_url or j_uri to j_security_check so that server can redirect, after user is authenticated, to the given url but this is not the case with OC4J. I was unable to use welcome-file-list feature of web.xml as it enters into redirect loop (for details see above web.xml excerpt).
Another issue with JSF is that I cannot define the form’s action target because it follows a post-back model. And if I use a simple JSP page for login, I am deprived of all JSF input validations and then I have to do it all myself.
So the solution to above two problems is login proxy page. Below is JSF navigation diagram of the project.

The action of login.jsp is
        FacesContext context = FacesContext.getCurrentInstance();
        Map request = context.getExternalContext().getRequestMap();
        request.put("username", txtUser.getValue());
        request.put("password", txtPassword.getValue());
        return "proxy";
The jsp source of proxy.jsp is
<body onload="document.forms[0].submit();document.forms[1].submit();">
 
  <form action="verifyNic.jsp" method="POST"></form>
  <form action="j_security_check" method="POST">
      <input type="hidden" name="j_username" value="${requestScope.username}"/>
      <input type="hidden" name="j_password" value="${requestScope.password}"/>
  </form>
Explanation of proxy.jsp:
First html form tells OC4J that after authentication succeeds redirect me to verifyNic.jsp.
Second form submits user supplied credentials on previous page to j_security_check.
The order of form submits is important.
Logout
        FacesContext context = FacesContext.getCurrentInstance();
        HttpSession session = (HttpSession)context.getExternalContext().getSession(false);
        session.invalidate();
Get logged in user name in EJB
    @Resource
    private SessionContext ctx;
……
ctx.getCallerPrincipal().getName()
Login error
<h:outputLabel value="#{param.login}" …
Displays whatever is in query string parameter login. E.g. for login.jsp?login=incorrect it will display incorrect.
Value attribute uses JSF Unified Expression Language (EL) and implicit object param.



No comments: