Yaacfi -- Yet another access filter








( Introduction )

There are several ways to provide info to the filter about resources that require authorization:

  1. Protected URLs may be described in access restriction file, specified in the web.xml filter configuration directive.
           <filter>
             <filter-name>Yaacfi</filter-name>
             <filter-class>com.zeevbelkin.web.filter.access.Yaaf</filter-class>
             
             <init-param>
                 <param-name>access-restrictions</param-name>
                 <param-value>access-restrictions.xml</param-value>
             </init-param>
             
             <init-param>
                 <param-name>login-handler</param-name>
                 <param-value>/Login.do</param-value>
             </init-param>
         </filter>
         
     
    The access restriction file contains number of constraint directives, each from them contains the role list and number of "on" directives those describe the regular expression patterns those matches to the protected URL's. Unlike web.xml url-pattern configuration directive, on directive uses real regular expressions described in Pattern . This is an example of the access restriction file.
      <security-constraints>
         <constraint  allow-to-roles="role1,role2">
             <on url-pattern="/protected/.*"/>
         </constraint>
         <constraint allow-to-roles="admin">
             <on url-pattern="/adm/.*"/>
         </constraint>
         <!-- nobody should have direct access to JSP's those implement struts actions -->
         <constraint allow-to-roles="nobody">
             <on url-pattern="/actions/.*"/>
         </constraint>
      </security-constraints>
     
    Note: the filter permits access to a user in a role on an URL only and only if all on directives where the patterns match the URL are enclosed into constraint directives where the role is listed.

  2. User roles, required to execute struts actions may be specified in the struts configuration file.
  3. Also, a servlet can set HTTP response code in 401-403 range to make the filter to begin the login on demand interaction, if a user has not logged in yet in the current session.

The filter keeps info about the user identity, and additional info required to organize the login process, in a session bean SessionSecurityInfo (this is the class name and also the bean name) that implements Principal interface. This bean may be obtained by the name or by the getUserPrincipal request method call.

Yaafci key feature is that the filter delegates the user identity check to an external, customer written servlet, that, in case of successful authentication, sets the session remote user name by SessionSecurityInfo.setName method call, and, optionally, sets the session user role resolver by setRoleResolver call (a global user role resolver may be used to avoid need in this call). Then, in case of login on demand, the login servlet calls completeLogin method to resume the operation, that was interrupted to authenticate the user (in case of the explicit login, completeLogin does nothing). If you need the explicit login procedure, you should inform the filter about the location that will be used, by the filter, to start the login process. To provide this information it is necessary to set login-handler parameter in the filter configuration directive.
      <filter>
         <filter-name>Yaacfi</filter-name>
         <filter-class>com.zeevbelkin.web.filter.access.Yaaf</filter-class>
         <init-param>
             <param-name>access-restrictions</param-name>
             <param-value>access-restrictions.xml</param-value>
         </init-param>
         <init-param>
             <param-name>login-handler</param-name>
             <param-value>/Login.do</param-value>
         </init-param>
     </filter>
     <filter-mapping>
         <filter-name>Yaacfi</filter-name>
         <url-pattern>/*</url-pattern>
     </filter-mapping>
    
The global resolver class can be specified in the web.ini configuration file in the <filter> directive.
      <filter>
         <filter-name>Yaacfi</filter-name>
         <filter-class>com.zeevbelkin.web.filter.access.Yaaf</filter-class>
         <init-param>
         
             <param-name>userRoleResolver</param-name>
             <param-value>com.somepackage.UserRoleResolver</param-value>
        
         </init-param>
           ....
     </filter>
  
    
The global resolver class must implement Yaaf.UserRoleResolver interface. If the resolver class has a constructor that accepts ServletContext parameter, this parameter will be passed while the global resolver instantiation. When a none authenticated user posts data to an URL that requires authorization and the login on demand process begins, the filter should preserve the POST request body to restore it after the user authentication completion. The initial version of the filter preserves this data in the memory, and the maximum allowed POST request size if 256K. It is possible to change this value by setting the maxPostSize parameter.
      <filter>
         <filter-name>Yaacfi</filter-name>
         <filter-class>com.zeevbelkin.web.filter.access.Yaaf</filter-class>
            ...
         <init-param>
              <param-name>maxPostSize</param-name>
 	     <!-- the size if kilobytes -->
              <param-value>256</param-value>
         </init-param>
 	   ...
     </filter>
    
If a servlet returns HTTP 401-403 codes, the filter begins login on demand interaction. It is possible to suppress this behavior by setting handle403as401 parameter to false.
       <filter>
          <filter-name>Yaacfi</filter-name>
          <filter-class>com.zeevbelkin.web.filter.access.Yaaf</filter-class>
             ...
          <init-param>
               <param-name>handle403as401</param-name>
               <param-value>false</param-value>
          </init-param>
      ...
      </filter>
  
    
Yaacfi provides special support for the saved in cookie login implementation. It is necessary to derive a class from SavedLoginHandle class to use this ability. Then this derived class should be used to set savedLoginHandler parameter value in the filter directive in the web.xml file.
      <filter>
         <filter-name>Yaacfi</filter-name>
         <filter-class>com.zeevbelkin.web.filter.access.Yaaf</filter-class>
         <init-param>
             <param-name>access-restrictions</param-name>
             <param-value>access-restrictions.xml</param-value>
         </init-param>
         <init-param>
             <param-name>login-handler</param-name>
             <param-value>/pages/login.jsp</param-value>
         </init-param>
         <init-param>
             <param-name>savedLoginHandler</param-name>
             <param-value>com.myapp.TheSavedLoginHandler</param-value>
         </init-param>
     </filter>
    
Also, Yaacfi provides a special helper class AjaxLoginPeer to implement ajax login with DWR.

Sometimes a user begins a login interaction in one from the browser windows then leaves the window and begins a new interaction in another window. If these login interactions are "on demand", the filter keeps only info on the last resource, access to which required the authorization. Therefore, when a user returns to an old window, to continue a previously left login interaction process, in case of successful authentication, the browser will be redirected to a resource other then the resource, access to which raised the interaction in the current window. Of cause, such behavior is unwanted. To prevent this unwanted behavior the filter assigns a special ID to each "on demand" interaction process. The ID is passed as the yaacfiRqId request bean value to the application. The application should pass this parameter back to the filter using a hidden form variable with the same name (the filter can extract, also, the ID value from the HTTP request referer). Here is an example written with struts.

         <html:form action="/Login" method="post">
             <table> 
                 <tr>
                     <td>Login:</td><td>
                         <html:text property="login"/>
                     </td>
                 </tr>
                 <tr>
                     <td>Password:</td><td>
                         <html:password property="password"/>
                     </td>
                 </tr>
                 <tr>
                     <td> <html:submit/> </td>
                 </tr>
             </table>
             <input 
               name="yaacfiRqId" type="hidden" 
               value="<bean:write name="yaacfiRqId" scope="request"/>"
             />
         </html:form>
 
Then, the application can use isSavedRequestValid method to be sure that the handled request has been issued from the window from which the last login interaction was initiated.
    public ActionForward execute(
            ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response
            ) throws Exception {
        DynaActionForm dac=(DynaActionForm)form;
        Yaaf.SessionSecurityInfo ssi=
	    (Yaaf.SessionSecurityInfo)request.getUserPrincipal();
        
        synchronized (ssi.getLock()) {
            
            if (ssi.getName()!=null)
               return mapping.findForward("alreadyLoggedIn");

            if (!ssi.isSavedRequestValid(request))
               return mapping.findForward("outdatedLoginSequence");
            
            if ("pupkin".equals(dac.getString("login"))&&
	        "stam".equals(dac.getString("password"))) {
                // Yes, this guy is really Pupkin ...
                
                ssi.setName("pupkin");
                
                // Remember the user
                // to allow to him/her to log in
                // w/o any explicite action
                
                ssi.saveLogin(request,response,365);

                if (ssi.isExplicitLogin()) {
                    return mapping.findForward("afterExplicitLogin");
                } else
                    ssi.completeLogin(request,response);
                
                return null;
            }
            // failure
            request.setAttribute("failure","failure");
            return mapping.findForward("loginScreen");
        }
    }

click here to mail me Zeev Belkin -- GPG Public Key
-- E-mail: koyaanisqatsi@narod.ru