Raible's Wiki
Raible Designs AppFuseHomepage- Korean - Chinese - Italian - Japanese QuickStart Guide User Guide Tutorials Other ApplicationsStruts ResumeSecurity Example Struts Menu
Set your name in
UserPreferences
Referenced by
JSPWiki v2.2.33
Hide Menu |
This is version 6.
It is not the current version, and thus it cannot be edited. This document is a work in progress, don't count on this actually working yet.This How To will walk you through the steps of adding Acegi Security Acegi Security is a security framework that is build using the techniques of the Spring Framework Table of Contents
Prerequisites [#0]Before starting this how to, you must first have an AppFuse project that is wired with Spring. If your application is built on AppFuse 1.4 or newer, you meet this requirement already. If not, you will want to migrate your app to use the Spring Framework first.You will also have to replace container managed security with the Acegi FilterInvocation Security Interceptor. Add acegi .jar to your Eclipse classpath [#1]The .jar file for acegi already exists in your project but it probably is not in your Eclipse classpath. You can find it in:myproject/lib/spring-1.1.3/acegi-security-0.7-SNAPSHOT.jarOther versions should work too, but this is what I had to start with after Matt integrated Acegi authentication. Prepare your Actions [#2]There are two issues that will need to be dealt with in your actions. The first is we need to be able to catch a AccessDeniedException and redirect the user to a 403 error page. This is easy enough to accomplish just by adding the appropriate import and adding a try/catch block to the BaseAction.execute() method so it looks like this:import net.sf.acegisecurity.AccessDeniedException; ... /** * Override the execute method in LookupDispatchAction to parse * URLs and forward to methods without parameters. Also will * forward to unspecified method when no parameter is present. * <p/> * Will forward to a 403 server error in the case the user does * not have authorization to access a manager method being invoked. * <p/> * This is based on the following system: * <p/> * <ul> * <li>edit*.html -> edit method</li> * <li>save*.html -> save method</li> * <li>view*.html -> search method</li> * </ul> * * @param mapping The ActionMapping used to select this instance * @param request The HTTP request we are processing * @param response The HTTP response we are creating * @param form The optional ActionForm bean for this request (if any) * @return Describes where and how control should be forwarded. * @throws Exception if an error occurs */ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // Capture an AccessDeniedException thrown by Acegi Security // and redirect to the 403 server error page try { if (isCancelled(request)) { ActionForward af = cancelled(mapping, form, request, response); if (af != null) { return af; } } MessageResources resources = getResources(request); // Identify the localized message for the cancel button String edit = resources.getMessage(Locale.ENGLISH, "button.edit").toLowerCase(); String save = resources.getMessage(Locale.ENGLISH, "button.save").toLowerCase(); String search = resources.getMessage(Locale.ENGLISH, "button.search").toLowerCase(); String view = resources.getMessage(Locale.ENGLISH, "button.view").toLowerCase(); String[] rules = {edit, save, search, view}; // Identify the request parameter containing the method name String parameter = mapping.getParameter(); // don't set keyName unless it's defined on the action-mapping // no keyName -> unspecified will be called String keyName = null; if (parameter != null) { keyName = request.getParameter(parameter); } if ((keyName == null) || (keyName.length() == 0)) { for (int i = 0; i < rules.length; i++) { // apply the rules for automatically appending the method name if (request.getServletPath().indexOf(rules[i]) > -1) { return dispatchMethod(mapping, form, request, response, rules[i]); } } return this.unspecified(mapping, form, request, response); } // Identify the string to lookup String methodName = getMethodName(mapping, form, request, response, parameter); return dispatchMethod(mapping, form, request, response, methodName); } catch (AccessDeniedException ade) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return null; } } The second consideration for Actions is that public actions (any action that does not require the user to be logged on) can only access public methods on our manager beans and the resulting success page can only access public methods. When AppFuse comes out of the box the only action that needs to be modified is SignupAction. We will need to remove the code that automagically used to log in a user after signup. It would be possible to still do this, but it is fairly complicated and it's not really that important to me to make the user not have to log in right after creating his account. But the problem with leaving that code in is that is would cause one of the decorator pages to try and access UserManager.getUser() which is protected. This will cause an AccessDeniedException to be thrown since our user is not yet logged in. So remove this section of code from SignupAction: // Set cookies for auto-magical login ;-) String loginCookie = mgr.createLoginCookie(user.getUsername()); RequestUtil.setCookie(response, Constants.LOGIN_COOKIE, loginCookie, request.getContextPath()); Update web tests [#3]Because we are no longer able to go directly from our SignupAction to /mainMenu.html we will need to modify the Canoo web test to expect to see the login page instead. In test/web/web-tests.xml update the Signup target:<verifytitle description="view main menu" text=".*${mainMenu.title}.*" regex="true"/>to: <verifytitle description="view login page" text=".*${login.title}.*" regex="true"/> Modify Spring Configuration [#4]Here is the real meat of what we are tring to do. At this stage we will just use role based authorization, but we will explore more options after geting this to work. We will need to modify two spring files. First we will define the security interceptor in web/applicationContext-security.xml by adding the following bean definition.<bean id="userManagerSecurity" class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor"> <property name="authenticationManager"><ref bean="authenticationManager"/></property> <property name="accessDecisionManager"><ref local="accessDecisionManager"/></property> <property name="objectDefinitionSource"> <value> org.appfuse.service.UserManager.getUser=admin,tomcat org.appfuse.service.UserManager.getUsers=admin org.appfuse.service.UserManager.removeUser=admin </value> </property> </bean>Let's take a minute to look at what this is doing. We are reusing the authenticationManager and the accessDecisionManager from the filterInvocationInterceptor that controls access to specific URI's. So now we are stating which roles are needed to access specific methods of the UserManager. Because we have not defined any wildcards any method not listed is considered public and does not require the user to be logged in to access. Next we need to configure the userManager to utilize the userManagerSecurity bean we just defined. Edit the userManager bean in src/service/**/service/applicationContext-service.xml to look like this: <bean id="userManager" parent="txProxyTemplate"> <property name="target"> <bean class="org.appfuse.service.impl.UserManagerImpl"> <property name="userDAO"><ref bean="userDAO"/></property> </bean> </property> <!-- Override default transaction attributes b/c of LoginCookie methods --> <property name="transactionAttributes"> <props> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="remove*">PROPAGATION_REQUIRED</prop> <prop key="*LoginCookie">PROPAGATION_REQUIRED</prop> <prop key="*">PROPAGATION_REQUIRED,readOnly</prop> </props> </property> <!-- REMOVE DURING TEST: Start --> <property name="preInterceptors"> <list> <ref bean="userManagerSecurity"/> </list> </property> <!-- REMOVE DURING TEST: End --> </bean>You can see the comment tags surrounding the interceptor so we can remove the method security during the unit tests. Modify build.xml [#5]Now we need to modify build.xml to take advantage of those comment tags we just added to applicationContext-security.xml. In the test-module target we need to add a regular expression replace task to remove the XML between out comment tags. I changed the beginning of test-module target to look like this:<target name="test-module"> <!-- Inputs: module, test.classpath --> <echo level="info">Testing ${module}...</echo> <mkdir dir="${test.dir}/data"/> <propertycopy name="testcase" from="${module}-testcase" silent="true"/> <!-- Replace tokens in test properties files --> <copy todir="${test.dir}/${module}/classes"> <fileset dir="test/${module}" excludes="**/*.java"/> <filterset refid="variables.to.replace"/> </copy> <!-- Use test-specific XML files --> <copy todir="${webapp.target}/WEB-INF" overwrite="true"> <fileset dir="test" includes="*.xml"/> </copy> <!-- Remove configurations that should not be enabled during testing --> <replaceregexp file="${webapp.target}/WEB-INF/applicationContext-service.xml" flags="sg"> <regexp pattern="REMOVE DURING TEST: Start.*REMOVE DURING TEST: End" /> <substitution expression="REMOVED DURING TESTING" /> </replaceregexp> <property name="additional.src.dirs" value=""/> <junit ... Test that it all works [#6]At this point we should be able to successfully run an ant test-all. The unit tests will not use the security, but the web tests will. So if you want to make sure a user cannot do something they should not be allowed to you can add a test to web-tests.xml. But if you want to see that the security works, log in as user "tomcat" and try to access http://localhost:8080/appfuse/editProfile.html?method=search![]()
|