Chapter 7. Transactions and Security

In mountain biking and kayaking, the most thrilling moves are running ledges. It doesn’t start that way in either sport. With kayaks, you tend to do a whole lot of work to set yourself up for a few seconds of freefall. You line up against a landmark, stroke hard to build speed, push your body forward, and then simultaneously thrust with your hips and paddle off of the edge, which launches you. With bikes, you tend to pick a line through that won’t smash your chain ring, line your bike up, balance yourself behind the saddle so you don’t fly over the handlebars, roll forward slowly, compress your shocks, jump up to lighten the bike, and hope for the best while you’re falling.

Programmatic Transactions

Like most other services in Spring, you can use transactions programmatically or declaratively. When you’re using a new service, it often makes sense to understand that service programmatically within a method before you break it out as a declarative service.

Sometimes, you want to hide services, and Spring certainly makes it easy to do so. Other times, you want to be explicit, such as when transactional logic is the central job of your method. Spring makes it easy to do transaction logic programmatically.

How do I do that?

You’ll use the same mechanism for transactions as you do for JDBC: templates. For programmatic transactions, you’ll use a TransactionTemplate. You’ll put all of the code in the transaction body that you want to execute together.

In this case, imagine that a customer wants to cancel her current reservation and make a new one for later in the week. If the new reservation fails for any reason, she wants to keep her old one. You’ll add a method to RentABike called transferReservation. It will delete an existing reservation and create a new one (Example 7-1).

Example 7-1. HibRentABike.java
public void transferReservation(final Reservation oldRes, 
    final Reservation newRes) throws ReservationTransferException {
        
    TransactionTemplate template = 
        new TransactionTemplate(this.transactionManager);
       template.setPropagationBehavior(
        TransactionDefinition.PROPAGATION_REQUIRED);

       try {
     template.execute(new TransactionCallbackWithoutResult( ) {
                protected void doInTransactionWithoutResult(
             TransactionStatus transactionStatus) {
                        
              getHibernateTemplate( ).save(newRes);
              getHibernateTemplate( ).delete(oldRes);
            }
        });
    } catch (Exception ex) {
        throw new ReservationTransferException( );
    }
}

You’ll need to set up the transaction strategy for the template in the context, and create the accounts and the RentABike (Example 7-2).

Example 7-2. RentABike-servlet.xml
<beans>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
        <property name="dataSource"><ref local="dataSource"/></property>
              <property name="mappingResources">
               <list>
                   <value>com/springbook/Bike.hbm.xml</value>
                   <value>com/springbook/Customer.hbm.xml</value>
                   <value>com/springbook/Reservation.hbm.xml</value>
                   <value>com/springbook/LogEvent.hbm.xml</value>
               </list>
           </property>
           <property name="hibernateProperties">
               <props>
                     <prop key="hibernate.dialect">
                net.sf.hibernate.dialect.MySQLDialect
            </prop>
                     <prop key="hibernate.show_sql">false</prop>
               </props>
           </property>
       </bean>

    <bean id="transactionManager" class="org.springframework.orm.
hibernate.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref local="sessionFactory"/>
        </property>
    </bean>

    <bean id="rentaBike" class="com.springbook.HibRentABike">
            <property name="storeName">
            <value>Bruce's Bikes</value>
        </property>
            <property name="sessionFactory">    
            <ref local="sessionFactory"/>
        </property>
            <property name="transactionManager">
            <ref local="transactionManager"/>
        </property>
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName">
            <value>com.mysql.jdbc.Driver</value>
        </property>
            <property name="url">
            <value>jdbc:mysql://localhost/rentabike</value>
        </property>
            <property name="username"><value>bikestore</value></property>
    </bean>
</beans>

Take special care. Because you are using MySQL as the database for this example, you’ll need one final step to get it to work. In order to support transactions, you have to mark your tables in MySQL as InnoDB type, which lets MySQL be transactional, with full ACID semantics.

Note

ACID stands for atomic, consistent, isolated and durable. All transactions need these properties.

What just happened?

A template is a simple, default transaction-handling method. All of the boilerplate code is in the template. You just have to implement the method that does all of the work. It will either all succeed or roll back.

You’re looking for two things: a pluggable architecture, and leverage. Of course, Hibernate transactions need only a command or two. Think about a native implementation. If you wanted to replace the Hibernate transactions with JTA transactions (to coordinate with changes in another database), you’d have to replace your implementation throughout your code, and you’d have to do much more work.

With your Spring implementation, you need only change configuration. The template gives you exactly what you want: a place to specify what needs to happen together successfully for the transaction to succeed.

Configuring Simple Transactions

Now, you’ll see the most useful type of transaction support: declarative transactions. It’s time to go back and explain the configuration of the transactions on your façade. In Chapter 4, you configured the transactions without explanation. It’s time to go back and fill in the holes.

Declarative transactions are perhaps the most important feature of EJB session beans. Many developers decide not to use EJB at all when they discover that you can in fact use declarative transactions without sacrificing your first-born child.

You will implement declarative transactions without ever touching the code. You only need to change the context. Just like the examples in the previous chapter, you will configure an interceptor. The interceptor defines the methods to be marked as transactional and defines their expected behavior. Some of the methods will use full transaction propagation, and some will use a lighter-weight, read-only propagation (Example 7-3).

Example 7-3. RentABike-servlet.xml
<bean name="transactionInterceptor" class="org.springframework.
transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager">
            <ref local="transactionManager"/>
        </property>
        <property name="transactionAttributeSource">
            <value>                
            com.springbook.RentABike.transferReservation=
            PROPAGATION_REQUIRED,-ReservationTransferException
            com.springbook.RentABike.save*=PROPAGATION_REQUIRED
            com.springbook.RentABike.*=PROPAGATION_REQUIRED,readOnly

            </value>
        </property>
    </bean>

In the definition of the behavior for the transferReservation method, notice that you pass the required transaction flag and the name of a checked exception. This tells your interceptor to roll back the transaction if an exception of that type is thrown; otherwise, not.

Next, you’ll build a proxy that specifies the target bean, like in Example 7-4.

Example 7-4. RentABike-servlet.xml
<bean id="rentaBike" class="org.springframework.aop.framework.ProxyFactoryBean">
       <property name="proxyInterfaces">
        <value>com.springbook.RentABike</value>
       </property>
       <property name="interceptorNames">
          <value>transactionInterceptor,rentaBikeTarget</value>
      </property>
</bean>

The execution remains unchanged.

Note

Think of this bean as the behavior in the original target, plus all of the aspects that you’ve added. In this case, the rentaBike is a transactional version of the target.

What just happened?

You just configured declarative transactions on a POJO. When any of the methods that we specified starts, the transaction advisor will tell the underlying transaction strategy to start the transaction. When the method successfully completes, the advisor will commit the transaction. If there’s an exception, the advisor will roll back. That’s exactly the behavior that you’re looking for.

What about...

Java Transaction API, or JTA? So far, you’ve used lightweight transactions. The nice thing about Spring is that you can change the transaction strategy without rewriting any code. Spring fully supports JTA, so you can use it to span multiple databases or perform distributed transactions (called XA transactions).

XA transactions may be more powerful than the alternatives, but you don’t always need them. In fact, there are at least two good reasons to use something else:

  • You may need individual features in a more direct transactional API, such as the isolation level support from JDBC transactions. In that case, XA transactions won’t work, because they don’t provide domain-specific abstractions.

  • You might need better performance. If XA transactions are overkill (such as applications that only access a single database), then XA transactions are likely to be much slower than an alternative, such as HibernateTransactions.

JTA tends to be the best fit when you’re using multiple resources. If your application needs to commit data from the same transaction across multiple databases, then JTA will likely be your best fit.

Transactions on Multiple Databases

If you have to refactor a simple application to use multiple resources, Spring’s pluggable transaction strategies can save you a whole lot of effort. In this example, you’re going to use JTA transactions to span multiple databases. For this example to work, your application must be running in a JTA-aware J2EE container.

If you’re using some kind of agile programming method, you’ll likely run into this kind of scenario. Agile methods suggest that you take simple approaches to a problem, and implement more complex features only when they’re needed. Dependency injection makes it much easier to take slightly different versions of application resources and plug them into a given application without major upheaval.

How do I do that?

In this case, you’re going to maintain monetary transaction information in a separate database, rentaBikeAccounts. Whenever users make a reservation, they have to provide a down payment, and you’ll transactionally add this amount to the monetaryTransactions table in the new database. Example 7-5 is the script for setting up the new database and table.

Example 7-5. rentabike.sql
create database rentaBikeAccounts;
use rentabikeAccounts;

create table monetaryTransactions (
   txId int(11) not null auto_increment,
   resId int(11) not null default '0',
   amount double not null default '0',
   `type` varchar(50) not null,
   primary key (txId))
   type=InnoDB;

Don’t forget that you’ll need to set up a user account in MySQL with privileges on the new database:

GRANT ALL PRIVILEGES ON rentaBikeAccounts.* TO 'rentaBikeAccounts'@'localhost' 
WITH GRANT OPTION;

We just created a user named rentaBikeAccounts, with complete access to the new database.

Next, Example 7-6 creates a persistent domain class.

Example 7-6. MonetaryTransaction.java
public class MonetaryTransaction {
    private int txId;
    private int resId;
    private double amount;

    public int getTxId( ) {
        return txId;
    }

    public void setTxId(int txId) {
        this.txId = txId;
    }

    public int getResId( ) {
        return custId;
    }

    public void setResId(int resId) {
        this.resId = resId;
    }

    public double getAmount( ) {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public MonetaryTransaction(double amount, int resid) {
        this.resId = resid;
        this.amount = amount;
        this.txId = -1;
    }

    public MonetaryTransaction( ) {
    this(0.0, 0);
    }
}

Map the schema to the class, as in Example 7-7.

Example 7-7. MonetaryTransaction.hbm.xml
<hibernate-mapping>
   <class name="com.springbook.MonetaryTransaction" 
      table="monetaryTransactions">
      <id name="txId" column="txid" type="java.lang.Integer" 
         unsaved-value="-1">
         <generator class="native"></generator>
      </id>
      <property name="resId" column="resid" type="int"/>
      <property name="amount" column="amount" type="double"/>
   </class>
</hibernate-mapping>

To access this new database, you have to configure a second data source and SessionFactory in your application configuration. Session factories handle access for to a single database, so you need a new SessionFactory for the second one (Example 7-8).

Example 7-8. RentABikeApp-Servlet.xml
<bean id="dataSourceForAccounts" 
   class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName">
      <value>com.mysql.jdbc.Driver</value>
   </property>
   <property name="url">
      <value>jdbc:mysql://localhost/justbikes</value>
   </property>
   <property name="username"><value>rentaBikeAccounts</value></property>
</bean>

<bean id="sessionFactoryForAccounts" 
   class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
   <property name="dataSource"><ref local="dataSourceForBikes"/></property>
   <property name="mappingResources">
      <list>
         <value>com/springbook/MonetaryTransaction.hbm.xml</value>
      </list>
   </property>
   <property name="hibernateProperties">
      <props>
         <prop key="hibernate.dialect">
            net.sf.hibernate.dialect.MySQLDialect
         </prop>
         <prop key="hibernate.show_sql">false</prop>
      </props>
   </property>
</bean>

Unfortunately, you can’t use this new SessionFactory directly within the HibRentABike implementation. That’s because HibRentABike extends the Spring-provided HibernateDaoSupport class, which uses a single SessionFactory as the backing behind getHibernateTemplate( ). As such, you need a second façade class, this one to model access to this new database through the new SessionFactory (Examples Example 7-9 and Example 7-10).

Example 7-9. Accounts.java
public interface Accounts {
    void addTx(MonetaryTransaction tx);
    MonetaryTransaction getTx(int id);
    List getTxs( );
}
Example 7-10. RentABikeAccounts.java
public class RentABikeAccounts 
    extends HibernateDaoSupport 
    implements Accounts {

    public void addTx(MonetaryTransaction tx) {
        getHibernateTemplate( ).saveOrUpdate(tx);
    }

    public MonetaryTransaction getTx(int id) {
        return (MonetaryTransaction)getHibernateTemplate( ).
        load(MonetaryTransaction.class, new Integer(id));
    }

    public List getTxs( ) {
        return getHibernateTemplate( ).find("from MonetaryTransaction");
    }
}

You need to configure this façade in your application configuration (Example 7-11).

Example 7-11. RentABike-servlet.xml
<bean id="rentaBikeAccountsTarget" 
   class="com.springbook.RentABikeAccounts">
   <property name="sessionFactory">
      <ref local="sessionFactoryForAccounts"/>
   </property>
</bean>

<bean id="rentaBikeAccounts" 
   class="org.springframework.aop.framework.ProxyFactoryBean">
   <property name="proxyInterfaces">
      <value>com.springbook.Accounts</value>
   </property>
   <property name="interceptorNames">
      <value>transactionInterceptor,rentaBikeAccountsTarget</value>
   </property>
</bean>

Finally, use this new functionality within the RentABike façade, which means you’ll need to add a setter to RentABike and wire them together in the application context. Example 7-12 shows the additions to HibRentABike.

Example 7-12. HibRentABike.java
private Accounts accountsFacade;
    
public Accounts getAccountsFacade( ) {
    return accountsFacade;
}

public void setAccountsFacade(Accounts accountsFacade) {
    this.accountsFacade = accountsFacade;
}

public void addReservation(Reservation reservation, double amount) 
    throws AddReservationException {
       try {
        MonetaryTransaction tx = new MonetaryTransaction(amount, 
            reservation.getReservationId( ));
        getHibernateTemplate( ).saveOrUpdate(reservation);
        accountsFacade.addTx(tx);
      } catch (Exception ex) {
        throw new AddReservationException( );
       }
}

Example 7-13 shows the additions to the configuration file.

Example 7-13. App-Servlet.xml
<bean id="rentaBikeTarget" class="com.springbook.HibRentABike">
   <property name="storeName"><value>Bruce's Bikes</value></property>
   <property name="sessionFactory"><ref local="sessionFactory"/></property>
   <property name="accountsFacade">
      <ref local="rentaBikeAccounts"/>
   </property>
</bean>

<bean id="transactionManager" 
   class="org.springframework.transaction.jta.JtaTransactionManager">
</bean>

<bean name="transactionInterceptor" 
class="org.springframework.transaction.interceptor.TransactionInterceptor">
   <property name="transactionManager">
      <ref local="transactionManager"/>
   </property>
   <property name="transactionAttributeSource">
      <value>
com.springbook.RentABike.transferReservation=PROPAGATION_REQUIRED,
-ReservationTransferException
                com.springbook.RentABike.addReservation=PROPAGATION_REQUIRED,
-AddReservationException
      </value>
   </property>
</bean>

What just happened?

After adding a second data source, you were able to quickly add XA transactional functionality to the application. Now, whenever the HibRentABike.addReservation( ) method is called, the application transactionally adds a reservation to the original database and a MonetaryTransaction to the new database. You added a few new classes to the domain model, but mostly this was accomplished through configuration settings. Spring’s Hibernate and JTA support handle most of the heavy lifting, and now the application handles multiple physical data stores inside a single distributed transaction.

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.

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.

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 id="inMemoryDaoImpl"   
   class="net.sf.acegisecurity.providers.dao.memory.InMemoryDaoImpl">
       <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 id="daoAuthenticationProvider" 
   class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
   <property name="authenticationDao">
      <ref local="inMemoryDaoImpl"/>
   </property>
</bean>

<bean id="authenticationManager" 
   class="net.sf.acegisecurity.providers.ProviderManager">
   <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 id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>

<bean id="httpRequestAccessDecisionManager" 
   class="net.sf.acegisecurity.vote.AffirmativeBased">
   <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 id="filterInvocationInterceptor" 
   class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor">
   <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=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=ROLE_ADMIN
      A/.*/*.bikes=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 id="securityEnforcementFilter" 
   class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
   <property name="filterSecurityInterceptor">
      <ref local="filterInvocationInterceptor"/>
   </property>
   <property name="authenticationEntryPoint">
      <ref local="authenticationProcessingFilterEntryPoint"/>
   </property>
</bean>

<bean id="authenticationProcessingFilterEntryPoint" 
class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
   <property name="loginFormUrl"><value>/acegilogin.jsp</value></property>
   <property name="forceHttps"><value>false</value></property>
</bean>

<bean id="authenticationProcessingFilter" 
   class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
   <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 id="autoIntegrationFilter"
    class="net.sf.acegisecurity.ui.AutoIntegrationFilter"></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; 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.

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.

Securing Application Methods

In this lab, you’ll learn to use a different kind of security. You’ll secure a method on a bean instead of a servlet. This type of security is still declarative, but it’s a little more involved.

EJB security works by assigning users roles, giving permissions to those roles, and then assigning permissions to individual methods. In Spring, to use that model, you’ll want to secure a method on a bean in the context, instead. In most cases, you’ll want to secure the methods on your façade layer—in your case, the rentaBike bean.

How do I do that?

Method-based security relies on user roles, just like servlet-based security does (Example 7-23). You have already established two users with different roles in the previous lab.

Example 7-23. RentABikeApp-Servlet.xml
<bean id="inMemoryDaoImpl" 
   class="net.sf.acegisecurity.providers.dao.memory.InMemoryDaoImpl">
   <property name="userMap">
      <value>
         justin=gehtland,ROLE_USER,ROLE_ADMIN
         bruce=tate,ROLE_USER
      </value>
   </property>
</bean>

To establish access rules for methods on a bean, you have to create an instance of ACEGI’s MethodSecurityInterceptor (Example 7-24). For this application, you will want to secure methods on the façade layer that controls your data model. The interceptor needs references to a ProviderManager and a DecisionManager, just like the FilterSecurityInterceptor in the previous lab did. Similary, it will have a property called objectDefinitionSource that lists the methods on the beans that need to be secured, and what roles have access to them. For example, all members of ROLE_USER should be able to read data from the database, but only members of ROLE_ADMIN should be able to save, update, or delete.

Example 7-24. RentABikeApp-Servlet.xml
<bean id="bikeRentalSecurity" 
   class="net.sf.acegisecurity.intercept.method.MethodSecurityInterceptor">
   <property name="authenticationManager">
      <ref local="authenticationManager"/>
   </property>
   <property name="accessDecisionManager">
      <ref local="httpRequestAccessDecisionManager"/>
   </property>
   <property name="objectDefinitionSource">
      <value>
        com.springbook.RentABike.saveBike=ROLE_ADMIN
        com.springbook.RentABike.deleteBike=ROLE_ADMIN
        com.springbook.RentABike.saveCustomer=ROLE_ADMIN
        com.springbook.RentABike.deleteCustomer=ROLE_ADMIN
      </value>
   </property>
</bean>

To wire up the interceptor, you have to create a proxy around the rentaBike controller, just like we did in Chapter 6. First, make sure that your actual BikeStore is configured properly (Example 7-25).

Example 7-25. RentABikeApp-Servlet.xml
<bean id="rentaBikeTarget" class="com.springbook.HibRentABike">
   <property name="storeName"><value>Bruce's Bikes</value></
property>
   <property name="sessionFactory"><ref local="sessionFactory"/></
property>
   <property name="transactionManager">
      <ref local="transactionManager"/>
   </property>
</bean>

Then, wrap it in a Spring interceptor, using the MethodSecurityInterceptor as the filter (Example 7-26).

Example 7-26. RentABikeApp-Servlet.xml
<bean id="rentaBike" 
   class="org.springframework.aop.framework.ProxyFactoryBean">
   <property name="proxyInterfaces">
      <value>com.springbook.RentABike</value>
   </property>
   <property name="interceptorNames">
      <list>
         <idref local="bikeRentalSecurity"/>
         <idref local="rentaBikeTarget"/>
      </list>
   </property>
</bean>

Now, try to sign on as a user belonging to ROLE_ADMIN. Example 7-27 is the log output when you attempt to issue a saveBike command.

Example 7-27. rentabike.log
2004-11-29 10:41:39,906 DEBUG [net.sf.acegisecurity.intercept.AbstractSecurityInterceptor] 
- Secure object: Invocation: method=[public abstract void com.springbook.RentABike.
saveBike(com.springbook.Bike)] args=[Ljava.lang.Object;@68b9f] target is of class 
[com.springbook.HibRentABike]; ConfigAttributes: [ROLE_ADMIN]
2004-11-29 10:41:39,906 DEBUG [net.sf.acegisecurity.providers.ProviderManager] 
- Authentication attempt using net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider
2004-11-29 10:41:39,906 DEBUG [org.springframework.web.context.support.
XmlWebApplicationContext] - Publishing event in context [XmlWebApplicationContext for 
namespace 'rentaBikeApp-servlet']:
net.sf.acegisecurity.providers.dao.event.AuthenticationSuccessEvent[source=net.sf.
acegisecurity.providers.UsernamePasswordAuthenticationToken@d92c96: Username: justin; 
Password: [PROTECTED]; Authenticated: false; Details: 127.0.0.1; Granted Authorities: 
ROLE_USER, ROLE_ADMIN]
2004-11-29 10:41:39,906 INFO [net.sf.acegisecurity.providers.dao.event.LoggerListener] 
- Authentication success for user: justin; details: 127.0.0.1
...[call then proceeds as normal]

If you log out and log back in as a user NOT in the ROLE_ADMIN role, Example 7-28 is the log output.

Example 7-28. rentabike.log
2004-11-29 11:04:59,240 DEBUG [net.sf.acegisecurity.intercept.AbstractSecurityInterceptor] 
- Secure object: Invocation: method=[public abstract void com.springbook.RentABike.saveBike
(com.springbook.Bike)] args=[Ljava.lang.Object;@26bd42] target is of class 
[com.springbook.HibRentABike]; ConfigAttributes: [ROLE_ADMIN]
2004-11-29 11:04:59,240 DEBUG [net.sf.acegisecurity.providers.ProviderManager] 
- Authentication attempt using net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider
2004-11-29 11:04:59,240 DEBUG [org.springframework.web.context.support.
XmlWebApplicationContext] - Publishing event in context [XmlWebApplicationContext for 
namespace 'rentaBikeApp-servlet']: net.sf.acegisecurity.providers.dao.event.
AuthenticationSuccessEvent[source=net.sf.acegisecurity.providers.
UsernamePasswordAuthenticationToken@c91c22: Username: bruce; Password: [PROTECTED]; 
Authenticated: false; Details: 127.0.0.1; Granted Authorities: ROLE_USER]
2004-11-29 11:04:59,241 INFO [net.sf.acegisecurity.providers.dao.event.LoggerListener] 
- Authentication success for user: bruce; details: 127.0.0.1
2004-11-29 11:04:59,241 DEBUG [org.springframework.web.context.support.
XmlWebApplicationContext] - Publishing event in context [Root XmlWebApplicationContext]:
net.sf.acegisecurity.providers.dao.event.AuthenticationSuccessEvent[source=net.sf.
acegisecurity.providers.UsernamePasswordAuthenticationToken@c91c22: Username: bruce; 
Password: [PROTECTED]; Authenticated: false; Details: 127.0.0.1; Granted Authorities: 
ROLE_USER]

2004-11-29 11:04:59,241 INFO [net.sf.acegisecurity.providers.dao.event.LoggerListener] 
- Authentication success for user: bruce; details: 127.0.0.1
2004-11-29 11:04:59,241 DEBUG [net.sf.acegisecurity.intercept.AbstractSecurityInterceptor]
- Authenticated: net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken@537945:
Username: bruce; Password: [PROTECTED]; Authenticated: true; Details: 127.0.0.1; Granted 
Authorities: ROLE_USER
2004-11-29 11:04:59,241 ERROR [org.springframework.web.servlet.DispatcherServlet] - 
Could not complete request
net.sf.acegisecurity.AccessDeniedException: Access is denied.
    at net.sf.acegisecurity.vote.AffirmativeBased.decide(AffirmativeBased.java:86)
ETC.
2004-11-29 11:04:59,243 DEBUG [org.springframework.web.context.support.
XmlWebApplicationContext] - Publishing event in context [XmlWebApplicationContext for 
namespace 'rentaBikeApp-servlet']: RequestHandledEvent: url=[/rentabike/editBike.htm]; 
time=[5ms]; client=[127.0.0.1]; method=[POST]; servlet=[rentaBikeApp]; session=
[029AB8AD12ED146A4356817166C38083]; user=[null]; status=[failed: net.sf.acegisecurity.
AccessDeniedException: Access is denied.]
2004-11-29 11:04:59,243 DEBUG [org.springframework.web.context.support.
XmlWebApplicationContext] - Publishing event in context [Root XmlWebApplicationContext]: 
RequestHandledEvent: url=[/rentabike/editBike.htm]; time=[5ms]; client=[127.0.0.1]; 
method=[POST]; servlet=[rentaBikeApp]; session=[029AB8AD12ED146A4356817166C38083]; 
user=[null]; status=[failed: net.sf.acegisecurity.AccessDeniedException: Access is denied.]
2004-11-29 11:04:59,243 DEBUG [net.sf.acegisecurity.intercept.web.
SecurityEnforcementFilter] - Access is denied - sending back forbidden response
2004-11-29 11:04:59,243 DEBUG [net.sf.acegisecurity.ui.AbstractIntegrationFilter] - 
Updating container with new Authentication object, and then removing Authentication 
from ContextHolder

What just happened?

The ACEGI model uses interceptors. These interceptors map credentials (or roles) onto an individual user when the user authenticates. ACEGI stores this information in its authentication engine, and lets you, or an interceptor, query this information.

Next, ACEGI stores a map of permissions on each role. Then, when you proxy an interface, you specify what credentials are required to do a particular thing, like fire a method. Like the other services in this chapter, these work declaratively.

What about...

...instance-based security? EJB security often breaks down because guarding an individual method is not enough. You don’t care whether a manager wants to retrieve invoices. You care which invoices the manager wants to retrieve. He should only have access to the invoices for employees in his department.

ACEGI provides instance-based security. You can attach ACLs (access control lists) to individual instances, just as you attached permissions to roles. ACEGI provides a mapping mechanism to do this. You can assign AclEntries to domain instances, then use an AclManager to look up AclEntries from a given domain instance. AccessDecisionManagers then decide if the current user has appropriate permissions based on the AclEntries.

Building a Test-Friendly Interceptor

In this example, you’ll learn how to build interceptors that do nothing but improve your ability to test code. Usually, a transaction interceptor does three things:

  1. Begins a transaction when a method starts.

  2. Rolls back a transaction when an exception is thrown.

  3. Commits the transaction when a method completes.

Of course, that’s what you usually want to happen. Test cases are different animals, though. They’re responsible for putting conditions back to the state where they were before a test case was fired, and they need to execute quickly.

For a test case, often it makes sense to replace the third step. You may instead want the transaction to roll back after the method completes. That way, you remove one of the most expensive database steps (the commit), and you also restore the database state to what it was before you executed the transaction.

How do I do that?

In Spring, it’s easy to build this type of behavior into an interceptor, because you can build a custom interceptor. Example 7-29 shows the code for a transaction interceptor that rolls back.

Example 7-29. TestTxInterceptor.java
public class TestTxInterceptor implements MethodInterceptor {
    private PlatformTransactionManager platformTransactionManager;

    public PlatformTransactionManager getPlatformTransactionManager( ) {
        return platformTransactionManager;
    }

    public void setPlatformTransactionManager(PlatformTransactionManager 
      platformTransactionManager) {
        this.platformTransactionManager = platformTransactionManager;
    }

    public Object invoke(MethodInvocation methodInvocation) 
      throws Throwable {
        DefaultTransactionDefinition def = 
          new DefaultTransactionDefinition( );
        def.setPropagationBehavior(TransactionDefinition.
          PROPAGATION_REQUIRED);
        TransactionStatus status = 
          platformTransactionManager.getTransaction(def);
        
        Object results = null;
        try {
            results = methodInvocation.proceed( );
        } finally {
            platformTransactionManager.rollback(status);
        }
        return results;
    }
}

Next, Example 7-30 shows the configuration of the context.

Example 7-30. RentABikeApp-Servlet.xml
<bean id="transactionManager" 
   class="org.springframework.orm.hibernate.HibernateTransactionManager">
   <property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>

<bean id="testTxInterceptor" 
   class="com.springbook.interceptors.TestTxInterceptor">
   <property name="platformTransactionManager">
      <ref local="transactionManager"/>
   </property>
</bean>

<bean id="rentaBike" 
   class="org.springframework.aop.framework.ProxyFactoryBean">
   <property name="proxyInterfaces">
      <value>com.springbook.RentABike</value>
   </property>
   <property name="interceptorNames">
         <list>
            <idref local="bikeRentalSecurity"/>
            <idref local="testTxInterceptor"/>
            <idref local="rentaBikeTarget"/>
         </list>
   </property>
</bean>

Now, you can write any JUnit tests you want against the façade layer, testing reads and writes, testing for appropriate security filtering, and so on, without worrying about changing the database. When running the unit tests, use a version of the configuration file that assigns the interceptor, and leave out the interceptor in a deployment scenario.

What just happened?

The typical JUnit flow works like this:

  • setUp prepares the system for a test case. In our setUp method, we do all of the work that’s common across the databases.

  • A series of individual tests then follow. In our case, we slightly change the flow, in order to call all of the methods within a method that uses the Spring context.

tearDown then does all cleanup. For database tests, the tearDown can be particularly expensive, because it’s got to restore the test data to a usable state for other test cases. (An alternative approach is the reset the database to a known good state in every setUp method. If the set of test data is small enough, this is a safer approach. The larger the set of test data, the more likely it is that you will want to use tearDown to undo test changes instead of setUp to recreate the full database.) A test changes something in the database, verifies the results, and then repeats. Notice also that all of the database access is in a central method. That makes it easy to bracket the method with declarative transactions.

Now look at the context. This is a case where you’re actually proxying the test code to add some behavior that makes testing easier. That’s using the power of Spring to build a much better test. In the next chapter, you’ll look into a few more services, like remoting and messaging.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.55.18