Yaacfi -- Yet another access filter |
|
This example shows how to write a Struts application that implements cookie login, explicit login and on demand login. Main part of this example is implemented as a Struts action servlet, the parts that implement cookie login action handler, logout action handler and the explicit login handler are implement as JSP's. Of cause, these action JSP's do not contain any elements of the look (and, of case, it is possible to impelements such action handles as Struts action servlet too, if you prefer this way). To avoid ability to access the action JSP's directly, without Struts mapping, them reside in a particular "/actions" folder that requires nobody user role to access, this trick allows to assign user roles to the JSP-actions with Struts configuration file "roles" option. The JSP's that implement the design reside in other folder "/pages". This is the access restriction file for the project. Some resources will be protected with this file there, and other ones will be protected with Struts roles option in the Struts configuration 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> The application web.ini file contains the next filter and filter-mapping directives. <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>/LoginScreen.do</param-value> </init-param> <init-param> <param-name>userRoleResolver</param-name> <param-value>com.myapp.Resolver</param-value> </init-param> <init-param> <param-name>maxPostSize</param-name> <param-value>256</param-value> </init-param> <init-param> <param-name>savedLoginHandler</param-name> <param-value>com.myapp.TheSavedLoginHandler</param-value> </init-param> </filter> <filter-mapping> <filter-name>Yaacfi</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> LoginScreen Struts action, that shows the login screen, is the entry point of the login script. This demo uses a centralized role resolver that resolves roles for all users of the application (real life resolver ordinary obtains the user roles from a database). package com.myapp; import com.zeevbelkin.web.filter.access.*; import javax.servlet.*; public class Resolver implements Yaaf.UserRoleResolver { public boolean isUserInRole(String user_id,String role) { // all the authenticated users have role "role1" here return "role1".equals(role); } /** Creates a new instance of Resolver */ public Resolver(ServletContext ctx) { ctx.log("**** Resolver has been created"); } } The application derives the class TheSavedLoginHandler, from the SavedLoginHandler , to implement saved in cookie login. This derived class is listed in the win.ini file Yaacfi filter configuration as the savedLoginHandler parameter value. public class TheSavedLoginHandler extends SavedLoginHandler { public String createSavedLogin(String user_id) { if ("pupkin".equals(user_id)) return "right_secret"; return super.createSavedLogin(user_id); } public void assignCredentials(String saved_login,Yaaf.SessionSecurityInfo ssi) { if ("right_secret".equals(saved_login)) ssi.setName("pupkin"); } public void store(String user_id,String savedLogin) {} public void drop(String user_id) {} /** Creates a new instance of TheSavedLoginHandler */ public TheSavedLoginHandler(ServletContext ctx) { ctx.log("**** SavedLoginHandler has been created"); } } In a real application, saved login handler ordinady uses a database. This is a Struts configuration for the application. <form-beans> <form-bean name="logonForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="login" type="java.lang.String"/> <form-property name="password" type="java.lang.String"/> </form-bean> <form-bean name="testForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="aValue" type="java.lang.String"/> </form-bean> </form-beans> <global-exceptions> </global-exceptions> <global-forwards> <forward name="loginScreen" path="/pages/login.jsp"/> <forward name="logoutScreen" path="/pages/logoutScreen.jsp"/> <forward name="homePage" path="/"/> <forward name="afterExplicitLogin" path="/pages/afterExplicitLogin.jsp"/> <forward name="outdatedLoginSequence" path="/pages/outdatedLoginSequence.jsp"/> <forward name="alreadyLoggedIn" path="/pages/alreadyLoggedIn.jsp"/> </global-forwards> <action-mappings> <action path="/Welcome" roles="role1" forward="/welcomeStruts.jsp"/> <action path="/CookieLogin" forward="/actions/cookieLoginAction.jsp"/> <action path="/Logout" forward="/actions/logoutAction.jsp"/> <action path="/ExplicitLogin" forward="/actions/explicitLoginAction.jsp"/> <action path="/Whois" forward="/pages/afterExplicitLogin.jsp"/> <action path="/LoginScreen" forward="/pages/login.jsp"/> <action name="testForm" scope="request" path="/Test" roles="role1" forward="/actions/protectedFormHandler.jsp" /> <action input="/pages/login.jsp" name="logonForm" scope="request" path="/Login" type="com.myapp.struts.LoginAction" /> </action-mappings> This is the login screen JSP "/pages/login.jsp" fragment. Bean failure presents in the request if a user will enter a wrong login/password. Sometimes user begins 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. 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 passes this parameter back to the filter using a hidden form variable with the same name. <html:html locale="true"> <head> <title></title> <html:base/> </head> <body bgcolor="white"> <logic:present name="failure" scope="request"> <div style="color:#ff0000;font-size:8;margin-left:50;"> You entered wrong login/password<br> Please, try again.<br><br> </div> </logic:present> Enter your login/password<br><br> <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> </body> </html:html> LoginAction servlet is the main part of the login script, it works in both explicit and on demand login cases. If a user is authenticated successfully and the login is explicit one, the servlet returns null to Struts, otherwise the servlet returns a forward object that points to a post login screen in case of success, or, in case of failure, to the login screen again. In the last case the servlet creates a request bean "failure" to inform the login screen JSP. public class LoginAction extends Action { 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"); } } } An explicit login action handler "/actions/explicitLoginAction.jsp" marks the login process as an explicit one and then forwards the control to the login screen. <bean:define id="ssi" name="SessionSecurityInfo" scope="session"/> <jsp:setProperty name="ssi" property="explicitLogin" value="true"/> <logic:forward name="loginScreen"/> Other parts of this demo application are not essential. You can download sources of this example to look there and to run. |
Zeev Belkin
-- GPG Public Key
-- E-mail: koyaanisqatsi@narod.ru |