Section 7.4. Securing Application Servlets

team bbl


7.4. Securing Application Servlets

Declarative transactions are the most popular declarative services in Spring, but not the only ones. Spring also allows declarative remoting and declarative security. In this example, you'll use a declarative security service called ACEGI.

7.4.1. Why do I care?

Since ACEGI is a declarative service, you can use it to secure any method on any bean in the context, without writing extra supportive code. ACEGI also has advanced features that are well beyond most EJB implementations, including the following:

  • A robust sign on implementation, based on Yale University's open source Central Authentication Services (CAS)

  • An instance based security model

  • Pluggable authentication implementations

  • HTTP authentication

You can access many of ACEGI's features declaratively. Your POJOs will not be overwhelmed with security code.

7.4.2. How do I do that?

First, you'll use the servlet-based implementation of ACEGI. This approach uses servlet filters. You'll configure it through a combination of additions to the Spring and web.xml configurations. To get going with ACEGI, download the latest version from their web site (http://acegisecurity.sourceforge.net/downloads.html). We used Version 0.6.1. From the /dist folder, copy acegi-security-catalina-server.jar into the /server/lib folder of your Tomcat install. You will also need available to your web application aopalliance.jar, spring.jar, and acegi-security-catalina-common.jar, and acegi-security.jar.

Your first implementation will used forms-based authentication to verify users. You have to create a login form that users will be redirected to whenever they attempt to access any of our restricted site pages. This file will be called acegilogin.jsp. Its only requirements are that it be a non-restricted page and that it contain a username field and a password field with well-known identifiers. Example 7-14 is acegilogin.jsp.

Example 7-14. acegilogin.jsp
<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core' %> <%@ page import="net.sf.acegisecurity.ui.AbstractProcessingFilter,                  net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter" %> <%@ page import="net.sf.acegisecurity.AuthenticationException" %> <h1>Login</h1> <form action="j_acegi_security_check" method="POST"> Username: <input type="text" name="j_username"><br/> Password: <input type="password" name="j_password"><br/> <input type="submit" value="Login"> </form> <c:choose>    <c:when test="${not empty param.error}">       <font color="red">       Your login attempt was not successful, try again.<BR><BR>       Reason: <%= ((AuthenticationException) session.getAttribute(          AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY)).          getMessage( ) %>       </font>    </c:when> </c:choose>

You should add a special section at the end to display login errors if the user enters invalid credentials. Note that the username field must be called j_username and the password field must be j_password. The ACEGI standard action for the form is j_acegi_security_check, though you could rename it to whatever you like.

Next you have to configure the ACEGI security beans in your Spring configuration (Example 7-15). Although ACEGI provides several ways to authenticate user credentials (via LDAP, a database, external provider, and so on), the easiest way is through a special provider called the InMemoryDaoImpl. This allows you to configure the user credentials in your configuration file.

Example 7-15. RentABikeApp-Servlet.xml
<bean        >        <property name="userMap">            <value>            justin=gehtland,ROLE_USER,ROLE_ADMIN            bruce=tate,ROLE_USER            </value>     </property> </bean>

Configure users with username to the left of the equals and a comma-separated list of values to the right. Put the password first. Remaining values are roles for the user. You can choose to use an ACEGI-provided password encoding scheme here that employs MD5 hashing.

To finish the authentication configuration, you have to wrap the InMemoryDaoImpl in a DaoAuthenticationProvider, then feed that provider to a ProviderManager, as in Example 7-16.

Example 7-16. RentABikeApp-Servlet.xml
<bean      >    <property name="authenticationDao">       <ref local="inMemoryDaoImpl"/>    </property> </bean> <bean      >    <property name="providers">       <list>          <ref local="daoAuthenticationProvider"/>       </list>    </property> </bean>

The DaoAuthenticationProvider ACEGI class implements ACEGI's AuthenticationProvider interface. It exposes authentication methods like authenticate and isPasswordCorrect. The ProviderManager simply iterates through a list of AuthenticationProviders to authenticate a given request. In rentaBike, you only have DaoAuthenticationProvider.

Next, you need to tell ACEGI how to determine if a user is authorized to make a specific call (Example 7-17). ACEGI has the notion of DecisionManagers and Voters. Voters are beans that examine credentials and cast votes about access; DecisionManagers collect votes from Voters and determine outcomes. Although you can author your own Voters and DecisionManagers, ACEGI provides a straightforward approach through pre-built classes, the RoleVoter and the AffirmativeBasedDecisionManager.

Example 7-17. RentABikeApp-Servlet.xml
<bean  /> <bean      >    <property name="allowIfAllAbstainDecisions"><value>false</value>    </property>    <property name="decisionVoters">       <list>          <ref local="roleVoter"/>       </list>    </property> </bean>

The RoleVoter requires that the roles be configured using some role beginning with ROLE_, like our ROLE_USER role above. The AffirmativeBased DecisionManager asks each configured Voter for its vote, and then permits access if any vote "yes".

Next, you need to configure a FilterSecurityInterceptor that protects access to resources. You configure it to point to your ProviderManager and DecisionManager, then feed it a list of protected resources (Example 7-18).

Example 7-18. RentABikeApp-Servlet.xml
<bean      >    <property name="authenticationManager">       <ref local="authenticationManager"/>    </property>    <property name="accessDecisionManager">       <ref local="httpRequestAccessDecisionManager"/>    </property>    <property name="objectDefinitionSource">       <value>          CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON          \A/.*/*.bikes\Z=ROLE_USER       </value>    </property> </bean>

Note the CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON directive at the top of our protected resources list. There are other available directives, the most common is PATTERN_TYPE_APACHE_ANT, for Ant-style pattern-matching. The default is regular expression-matching. Your protection list says that any URL anywhere in the application that ends in .bikes (our standard redirect) requires the user to belong to ROLE_USER. You could then add further expressions to the list to require other roles. For example, to lock down a subfolder called admin to users belonging to ROLE_ADMIN, see Example 7-19.

Example 7-19. RentABikeApp-Servlet.xml
<property name="objectDefinitionSource">    <value>       CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON       \A/.*/admin/*.htm\Z=ROLE_ADMIN       \A/.*/*.bikes\Z=ROLE_USER    </value> </property>

In the list of protected resources, order is important. ACEGI matches the first role in the list, so if you listed the admin folder last, any user of ROLE_USER could access the admin folder, which would be bad.

Finally, configure the servlet filters themselves (Example 7-20). You will need three: a SecurityEnforcementFilter, an AutoIntegrationFilter, and an AuthenticationProcessingFilter. The enforcement filter uses FilterSecurityInterceptor to determine if access to a resource has been granted, and the AuthenticationProcessingFilter redirects unauthenticated users to the login page. The SecurityEnforcementFilter also needs access to a configured AuthenticationProcessingFilterEntryPoint, which simply stores the URL for the login page and specifies whether it requires HTTPS.

Example 7-20. RentABikeApp-Servlet.xml
<bean      >    <property name="filterSecurityInterceptor">       <ref local="filterInvocationInterceptor"/>    </property>    <property name="authenticationEntryPoint">       <ref local="authenticationProcessingFilterEntryPoint"/>    </property> </bean> <bean   >    <property name="loginFormUrl"><value>/acegilogin.jsp</value></property>    <property name="forceHttps"><value>false</value></property> </bean> <bean      >    <property name="authenticationManager">       <ref local="authenticationManager"/>    </property>    <property name="authenticationFailureUrl">       <value>/acegilogin.jsp?login_error=1</value>    </property>    <property name="defaultTargetUrl"><value>/</value></property>    <property name="filterProcessesUrl">       <value>/j_acegi_security_check</value>    </property> </bean> <bean      ></bean>

The AuthenticationProcessingFilter requires four properties:


authenticationManager

Provides the AuthenticationProvider that will authenticate the user.


authenticationFailureUrl

Redirects when the user enters invalid credentials.


defaultTargetUrl

Redirects if a user accesses the login page directly instead of being redirected from a protected URL.


filterProcessingUrl

Specifies the target of the login page's form.

Last but not least, you have to modify the web.xml file to enable the required filters (Example 7-21).

Example 7-21. web.xml
<filter>    <filter-name>Acegi Authentication Processing Filter</filter-name>    <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>    <init-param>       <param-name>targetClass</param-name>       <param-value>          net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter       </param-value>    </init-param> </filter> <filter>    <filter-name>       Acegi Security System for Spring Auto Integration Filter    </filter-name>    <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>    <init-param>       <param-name>targetClass</param-name>       <param-value>          net.sf.acegisecurity.ui.AutoIntegrationFilter       </param-value>    </init-param> </filter> <filter>    <filter-name>Acegi HTTP Request Security Filter</filter-name>    <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>    <init-param>       <param-name>targetClass</param-name>       <param-value>          net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter       </param-value>    </init-param> </filter> <filter-mapping>    <filter-name>Acegi Authentication Processing Filter</filter-name>    <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping>    <filter-name>       Acegi Security System for Spring Auto Integration Filter    </filter-name>    <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping>    <filter-name>Acegi HTTP Request Security Filter</filter-name>    <url-pattern>/*</url-pattern> </filter-mapping>

Now, when you run it, ACEGI will force you to authenticate. Example 7-22 is the log output from attempting to access the root of the site, /bikes.htm.

Example 7-22. rentabike.log
2004-11-28 14:05:13,532 DEBUG [net.sf.acegisecurity.ui.AbstractIntegrationFilter]  - Authentication not added to ContextHolder (could not extract an authentication object  from the container which is an instance of Authentication) 2004-11-28 14:05:13,532 DEBUG [net.sf.acegisecurity.intercept.web. RegExpBasedFilterInvocationDefinitionMap] - Converted URL to lowercase, from:  'org.apache.coyote.tomcat5.CoyoteRequestFacade@3290aa'; to: '/bikes.htm' 2004-11-28 14:05:13,532 DEBUG [net.sf.acegisecurity.intercept.web. RegExpBasedFilterInvocationDefinitionMap] - Candidate is: '/bikes.htm'; pattern is  \A/.*/*.htm\Z; matched=true 2004-11-28 14:05:13,532 DEBUG [net.sf.acegisecurity.intercept.AbstractSecurityInterceptor]  - Secure object: FilterInvocation: URL: /bikes.htm; ConfigAttributes: [ROLE_USER] 2004-11-28 14:05:13,533 DEBUG [net.sf.acegisecurity.intercept.web. SecurityEnforcementFilter] - Authentication failed - adding target URL to Session:  http://localhost:8080/rentabike/bikes.htm net.sf.acegisecurity.AuthenticationCredentialsNotFoundException: A valid SecureContext  was not provided in the RequestContext     at net.sf.acegisecurity.intercept.AbstractSecurityInterceptor.interceptor (AbstractSecurityInterceptor.java:280)     ...etc. 2004-11-28 14:05:13,534 DEBUG [net.sf.acegisecurity.ui.webapp. AuthenticationProcessingFilterEntryPoint] - Redirecting to: http://localhost:8080/ rentabike/acegilogin.jsp 2004-11-28 14:05:13,534 DEBUG [net.sf.acegisecurity.ui.AbstractIntegrationFilter]  - ContextHolder does not contain any authentication information ...[other log entries]  net.sf.acegisecurity.providers.dao.event. AuthenticationSuccessEvent[source=net.sf.acegisecurity.providers. UsernamePasswordAuthenticationToken@514faa: Username: bruce; Password: [PROTECTED];  Authenticated: false; Details: 127.0.0.1; Not granted any authorities] 2004-11-28 14:05:18,495 INFO [net.sf.acegisecurity.providers.dao.event.LoggerListener]  - Authentication success for user: bruce; details: 127.0.0.1 2004-11-28 14:05:18,495 DEBUG [net.sf.acegisecurity.ui.AbstractProcessingFilter]  - Authentication success: net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken@453924: Username: bruce; Password: [PROTECTED]; Authenticated: false; Details: 127.0.0.1; Granted Authorities: ROLE_USER 2004-11-28 14:05:18,495 DEBUG [net.sf.acegisecurity.ui.AbstractProcessingFilter]  - Redirecting to target URL from HTTP Session (or default):http://localhost:8080/rentabike/bikes.htm

As you can see from the log, the request for /bikes.htm is matched against our rule and ACEGI knows that only users belonging to ROLE_USER can access it. ACEGI checks the Context for an Authentication object, finds none, so redirects the user to /acegilogin.jsp, but first captures the original target URL in the HttpSession. After the user authenticates successfully, ACEGI automatically redirects the user to the original target. This time, when ACEGI attempts to verify the user, the Authentication object is already in the session, permission is granted, and normal Spring dispatching takes over.

7.4.3. What just happened?

You just saw one of three ways that ACEGI can secure an application. This is the most straightforward method, with the least amount of control. The servlet filters-based approach uses J2EE servlet filters to intercept control before the initial Spring dispatcher is called. Then, ACEGI allows access if the user has appropriate credentials.

    team bbl



    Spring. A developer's Notebook
    Spring: A Developers Notebook
    ISBN: 0596009100
    EAN: 2147483647
    Year: 2005
    Pages: 90

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net