Глава 7 Spring in Action 2th edition |
Содержание
|
Securing Spring
This chapter covers
- Introducing Spring Security
- Securing web applications using servlet filters
- Authentication against databases and LDAP
- Transparently securing method invocations
Have you ever noticed that most people in television sitcoms don’t lock their doors? It happens all the time. On Seinfeld, Kramer frequently let himself into Jerry’s apartment to help himself to the goodies in Jerry’s refrigerator. On Friends, the various characters often entered one another’s apartments without warning or hesitation. Even once, while in London, Ross burst into Chandler’s hotel room, narrowly missing Chandler in a compromising situation with Ross’s sister.
In the days of Leave it to Beaver, it wasn’t so unusual for people to leave their doors unlocked. But it seems crazy that in a day when we’re concerned with privacy and security we see television characters enabling unhindered access to their apartments and homes.
It’s a sad reality that there are villainous individuals roaming around seeking to steal our money, riches, cars, and other valuables. And it should be no surprise that as information is probably the most valuable item we have, crooks are looking for ways to steal our data and identity by sneaking into unsecured applications.
As software developers, we must take steps to protect the information that resides in our applications. Whether it’s an email account protected with a username/password pair or a brokerage account protected with a trading PIN, security is a crucial aspect of most applications.
It is no accident that I chose to describe application security with the word “aspect.” Security is a concern that transcends an application’s functionality. For the most part, an application should play no part in securing itself. Although you could write security functionality directly into your application’s code (and that’s not uncommon), it is better to keep security concerns separate from application concerns.
If you’re thinking that it is starting to sound as if security is accomplished using aspect-oriented techniques, you’re right. In this chapter we’re going to explore ways to secure your applications with aspects. But we won’t have to develop those aspects ourselves—we’re going to look at Spring Security, a security framework based on Spring AOP and servlet filters.1
- I’m probably going to get a lot of emails about this, but I have to say it anyway: servlet filters are a primitive form of AOP, with URL patterns as a kind of pointcut expression language. There… I’ve said it… I feel better now.
Introducing Spring Security
Spring Security is a security framework that provides declarative security for your Spring-based applications. Spring Security provides a comprehensive security solution, handling authentication and authorization, at both the web request level and at the method invocation level. Based on the Spring Framework, Spring Security takes full advantage of dependency injection (DI) and aspectoriented techniques.
What’s in a name?
Historically, Spring Security is also known as Acegi Security (or simply Acegi). Acegi has long been a subproject of Spring. But as I write this, plans are afoot to bring Acegi even closer under the Spring umbrella of projects. Part of that move involves dropping the Acegi name in favor of “Spring Security.” This change is scheduled to take place in the 1.1.0 version of Acegi/Spring Security. Knowing that the change is imminent, I’ve decided to go ahead and start referring to it as Spring Security, although you’ll still see the Acegi name thrown about a bit in this chapter.
When securing web applications, Spring Security uses servlet filters that intercept servlet requests to perform authentication and enforce security. And, as you’ll find in section 7.4.1, Spring Security employs a unique mechanism for declaring servlet filters that enables you to inject them with their dependencies using Spring DI.
Spring Security can also enforce security at a lower level by securing method invocations. When securing methods, Spring Security uses Spring AOP to proxy objects, applying aspects that ensure that the user has proper authority to invoke the secured methods.
In any case, whether you only need security at the web request level or if you require lower-level method security, Spring Security employs five core components to enforce security, as shown in figure 7.1.
Before we get into the nitty-gritty of Spring Security, let’s take a high-level view of Spring Security and the part that each of these components plays in securing applications.
When you arrive home after a long day at work, you’ll need to unlock the door to your home. To open the door, you must insert a key into the lock that trips the tumblers properly and releases the latch. If the cut of the key is incorrect, the tumblers won’t be tripped and the latch won’t be released. But if you have the right key, all of the tumblers will accept the key and the latch will be released, allowing you to open the door.
In Spring Security, the security interceptor can be thought of as a latch that prevents you from accessing a secured resource in your application. To flip the latch and get past the security interceptor, you must enter your “key” (typically a username and password) into the system. The key will then try to trip the security interceptor’s “tumblers” in an attempt to grant you access to the secured resource.
The actual implementation of a security interceptor will depend on what resource is being secured. If you’re securing a URL in a web application, the security interceptor will be implemented as a servlet filter. But if you’re securing a method invocation, aspects will be used to enforce security. You’ll see both forms of security interceptor later in this chapter.
A security interceptor does little more than intercept access to resources to enforce security. It does not actually apply security rules. Instead, it delegates that responsibility to the various managers that are pictured at the bottom of figure 7.1. Let’s have a look at each of these managers, starting with the authentication manager.
Authentication managers
The first of the security interceptor’s tumblers to be tripped is the authentication manager. The authentication manager is responsible for determining who you are. It does this by considering your principal (typically a username) and your credentials (typically a password).
Your principal defines who you are and your credentials are evidence that corroborates your identity. If your credentials are good enough to convince the authentication manager that your principal identifies you then Spring Security will know whom it is dealing with.
As with the rest of Spring Security (and Spring itself), the authentication manager is a pluggable interface-based component. This makes it possible to use Spring Security with virtually any authentication mechanism you can imagine. As you’ll see later in this chapter, Spring Security comes with a handful of flexible authentication managers that cover the most common authentication strategies.
Access decisions managers
Once Spring Security has determined who you are, it must decide whether you are authorized to access the secured resource. An access decision manager is the second tumbler of the Spring Security lock to be tripped. The access decision manager performs authorization, deciding whether to let you in by considering your authentication information and the security attributes that have been associated with the secured resource.
For example, the security rules may dictate that only supervisors should be allowed access to a secured resource. If you have been granted supervisor privileges then the second and final tumbler, the access decision manager, will have been tripped and the security interceptor will move out of your way and let you gain access to the secured resource.
Just as with the authentication manager, the access decision manager is pluggable. Later in this chapter, we’ll take a closer look at the access decision managers that come with Spring Security.
Run-as managers
If you’ve gotten past the authentication manager and the access decision manager then the security interceptor will be unlocked and the door is ready to open. But before you twist the knob and go in, there’s one more thing that the security interceptor might do.
Even though you’ve passed authentication and been granted access to a resource, there may be more security restrictions behind the door. For example, you may be granted the rights to view a web page, but the objects that are used to create that page may have different security requirements than the web page. A run-as manager can be used to replace your authentication with an authentication that allows you access to the secured objects that are deeper in your application.
Note that not all applications have a need for identity substitution. Therefore, run-as managers are an optional security component and are not necessary in many applications secured by Spring Security.
After-invocation managers
Spring Security’s after-invocation manager is a little different from the other security manager components. Whereas the other security manager components perform some form of security enforcement before a secured resource is accessed, the after-invocation manager enforces security after the secured resource is accessed.
After-invocation managers are kind of like the person who waits to examine a receipt at the exit of some discount and home electronics stores. They check to ensure that you have proper authority to remove the valuable items from the store. Instead of making sure that you are allowed to remove a big-screen television from a store, however, after-invocation managers make sure that you’re allowed to view the data that is being returned from a secured resource.
If an after-invocation manager advises a service layer bean, it will be given the opportunity to review the value returned from the advised method. It can then make a decision as to whether the user is allowed to view the returned object. The after-invocation manager also has the option of altering the returned value to ensure that the user is only able to access certain properties of the returned object.
Like run-as managers, not all applications call for an after-invocation manager. You’ll only need an after-invocation manager if your application’s security scheme requires that access be restricted at the domain level on a per-instance basis.
Now that you’ve seen the big picture of Spring Security, we’re ready to configure Spring Security for the RoadRantz application. For our purposes, we won’t need a run-as manager or an after-invocation manager, so we’ll defer those as advanced Spring Security topics. Meanwhile, let’s get started by configuring an authentication manager.
Authenticating users
When applying security to an application, the first thing you need to do, before deciding whether to allow access, is figure out who the user is. In most applications, this means presenting a login screen to the user and asking them for the username and password.
How the user is prompted for their username and password will vary from application to application. For now, we’ll assume that the user’s login details have already been provided and we need Spring Security to authenticate the user. We’ll look at different ways to prompt the user for their username and password a little later in this chapter.
In Spring Security, the authentication manager assumes the job of establishing a user’s identity. An authentication manager is defined by the org.acegisecurity.AuthenticationManager interface:
public interface AuthenticationManager {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException;
Third time’s a charm
- A-ha! There’s the word “acegi” in the package name for AuthenticationManager. As mentioned earlier in this chapter, Spring Security has historically been known as Acegi Security. When Acegi is formally renamed to Spring Security, the packaging of its classes will also change. Actually, this will be the third base package name that Acegi/Spring Security has had. Acegi was originally packaged under net.sf.acegisecurity… then it was changed to org.acegisecurity. When version 1.1.0 is released, it will likely be repackaged under org.springframework.security. Nevertheless, as those changes haven’t happened yet, the examples in this chapter show the org.acegisecurity packaging.
The authenticate() method will attempt to authenticate the user using the org.acegisecurity.Authentication object (which carries the principal and credentials). If successful, the authenticate() method returns a complete Authentication object, including information about the user’s granted authorities (which will be considered by the authorization manager). If authentication fails, an AuthenticationException will be thrown.
As you can see, the AuthenticationManager interface is quite simple and you
could easily implement your own AuthenticationManager. But Spring Security comes with ProviderManager, an implementation of AuthenticationManager that is suitable for most situations. So instead of rolling our own authentication manager, let’s take a look at how to use ProviderManager.
Configuring a provider manager
ProviderManager is an authentication manager implementation that delegates responsibility for authentication to one or more authentication providers, as shown in figure 7.2.
The purpose of ProviderManager is to enable you to authenticate users against multiple identity management sources. Rather than relying on itself to perform authentication, ProviderManager steps one by one through a collection of authentication providers, until one of them successfully authenticates the user (or until it runs out of providers). This makes it possible for Spring Security to support multiple authentication mechanisms for a single application.
The following chunk of XML shows a typical configuration of ProviderManager in the Spring configuration file:
<bean id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref bean="daoAuthenticationProvider"/>
<ref bean="ldapAuthenticationProvider"/>
</list>
</property>
</bean>
ProviderManager is given its list of authentication providers through its providers property. Typically, you’ll only need one authentication provider, but in some cases, it may be useful to supply a list of several providers so that if authentication fails against one provider, another provider will be tried. Spring comes with several authentication providers, as listed in table 7.1.
Table 7.1 Spring Security comes with authentication providers for every occasion.
Authentication provider ('''org.acegisecurity.*''') | |
---|---|
adapters.AuthByAdapterProvider | Authentication using container adapters. This makes it possible to authenticate against users created within the web container (e.g., Tomcat, JBoss, Jetty, Resin, etc.). |
providers.anonymous. AnonymousAuthenticationProvider | Authenticates a user as an anonymous user. Useful when a user token is needed, even when the user hasn’t logged in yet. |
providers.cas.CasAuthenticationProvider | Authentication against the JA-SIG Central Authentication Service (CAS). Useful when you need single sign-on capabilities. |
providers.dao.DaoAuthenticationProvider | Retrieving user information, including username and password from a database. |
providers.dao. LdapAuthenticationProvider | Authentication against a Lightweight Directory Access Protocol (LDAP) server. |
providers.jaas. JaasAuthenticationProvider | Retrieving user information from a JAAS login configuration. |
providers.rememberme. RememberMeAuthenticationProvider | Authenticates a user that was previously authenticated and remembered. Makes it possible to automatically log in a user without prompting for username and password. |
providers.rcp. RemoteAuthenticationProvider | Authentication against a remote service. |
providers.TestingAuthenticationProvider | Unit testing. Automatically considers a TestingAuthenticationToken as valid. Not for production use. |
providers.x509. X509AuthenticationProvider | Authentication using an X.509 certificate. Useful for authenticating users that are, in fact, other applications (such as a web-service client). |
runas.RunAsImplAuthenticationProvider | Authenticating a user who has had their identity substituted by a run-as manager. |
As you can see in table 7.1, Spring Security provides an authentication provider to meet almost any need. But if you can’t find an authentication provider that suits your application’s security needs, you can always create your own authentication provider by implementing the org.acegisecurity.providers.AuthenticationProvider interface:
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class authentication);
}
You may have noticed that the AuthenticationProvider interface isn’t much different from the AuthenticationManager interface shown a few pages back. They both share an authenticate() method that handles the authentication. In fact, you can think of authentication providers as subordinate authentication managers.
Space constraints do not allow me to go into the details of all 11 of Spring Security’s authentication providers. However, I will focus on a couple of the most commonly used authentication providers, starting with DaoAuthenticationProvider, which supports simple database-oriented authentication.
Authenticating against a database
Many applications store user information, including the username and password, in a relational database. If that’s how your application keeps user information, Spring Security’s DaoAuthenticationProvider may be a good choice for your application.
A DaoAuthenticationProvider is a simple authentication provider that uses a Data Access Object (DAO) to retrieve user information (including the user’s password) from a relational database.
With the username and password in hand, DaoAuthenticationProvider performs authentication by comparing the username and password retrieved from the database with the principal and credentials passed in an Authentication object from the authentication manager (see figure 7.3). If the username and password match up with the principal and credentials, the user will be authenticated and a fully populated Authentication object will be returned to the authentication manager. Otherwise, an AuthenticationException will be thrown and authentication will have failed.
Configuring a DaoAuthenticationProvider couldn’t be simpler. The following XML excerpt shows how to declare a DaoAuthenticationProvider bean and wire it with a reference to its DAO:
<bean id="authenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService"
ref="userDetailsService"/>
</bean>
The userDetailsService property is used to identify the bean that will be used to retrieve user information from the database. This property expects an instance of org.acegisecurity.userdetails.UserDetailsService. The question that remains is how the userDetailsService bean is configured.
The UserDetailsService interface requires that only one method be implemented:
public interface UserDetailsService {
UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException;
}
This method is fairly self-explanatory and you may already be thinking of several ways that you can implement this interface. But before you start writing your own implementation of UserDetailsService, you may be interested to know that Spring Security comes with two ready-made implementations of AuthenticationDao to choose from: InMemoryDaoImpl and JdbcDaoImpl. Let’s see how these two classes work to look up user details, starting with InMemoryDaoImpl.
Using an in-memory DAO
Although it may seem natural to assume that an AuthenticationDao object will always query a relational database for user information, that doesn’t necessarily have to be the case. If your application’s authentication needs are trivial or for development-time convenience, it may be simpler to configure your user information directly in the Spring configuration file.
For that purpose, Spring Security comes with InMemoryDaoImpl, an implementation of UserDetailsService that draws its user information from its Spring configuration. Here’s an example of how you may configure an InMemoryDaoImpl in the Spring configuration file:
<bean id="authenticationDao"
class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
palmerd=4moreyears,disabled,ROLE_PRESIDENT
bauerj=ineedsleep,ROLE_FIELD_OPS
obrianc=nosmile,ROLE_SR_ANALYST,ROLE_OPS
myersn=traitor,disabled,ROLE_CENTRAL_OPS
</value>
</property>
</bean>
The userMap property is configured with an org.acegisecurity.userdetails.memory.UserMap object that defines a set of usernames, passwords, and privileges. Fortunately, you needn’t concern yourself with constructing a UserMap instance when wiring InMemoryDaoImpl because there’s a property editor that handles the conversion of a String to a UserMap object for you.
On each line of the userMap, String is a name-value pair where the name is the username and the value is a comma-separated list that starts with the user’s password and is followed by one or more names that are the authorities to be granted to the user. Figure 7.4 breaks down the format of an entry in the user map.
In the declaration of the authenticationDao bean earlier, four users are defined: parlmerd, bauerj, obrianc, and myersn. Respectively, their passwords are 4moreyears, ineedsleep, nosmile, and traitor. The authorities are granted as follows:
- ROLE_PRESIDENT authority has been given to the user whose username is palmerd.
- ROLE_FIELD_OPS has been given to bauerj.
- ROLE_CENTRAL_OPS has been given to myersn.
- The obrianc user has been granted two authorities: ROLE_SR_ANALYST and ROLE_OPS.
Take special note of the palmerd and myersn users. A special disabled flag immediately follows their passwords, indicating that they have been disabled (and thus can’t authenticate).
Although InMemoryDaoImpl is convenient and simple, it has some obvious limitations. Primarily, security administration requires that you edit the Spring configuration file and redeploy your application. While this is acceptable (and maybe even helpful) in a development environment, it is probably too cumbersome for production use. Therefore, I strongly advise against using InMemoryDaoImpl in a production setting. Instead, you should consider using JdbcDaoImpl, which we’ll look at next.
Declaring a JDBC DAO
JdbcDaoImpl is a simple, yet flexible, authentication DAO that retrieves user information from a relational database. In its simplest form, all it needs is a reference to a javax.sql.DataSource, and it can be declared in the Spring configuration file as follows:
<bean id="authenticationDao"
class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
As configured here, JdbcDaoImpl makes some basic assumptions about how user information is stored in the database. Specifically, it assumes a Users table and an Authorities table, as illustrated in figure 7.5.
When JdbcDaoImpl looks up user information, it will query with the following SQL:
SELECT username, password, enabled
FROM users
WHERE username = ?
Likewise, when looking up a user’s granted authorities, JdbcDaoImpl will use the following SQL:
SELECT username, authority
FROM authorities
WHERE username = ?
While the table structures assumed by JdbcDaoImpl are straightforward, they probably do not match the tables you have set up for your own application’s security. For instance, in the RoadRantz application, the Motorist table holds registered users’ usernames (in the email column) and password. Does this mean that we can’t use JdbcDaoImpl to authenticate motorists in the RoadRantz application?
Not at all. But if we are to use JdbcDaoImpl, we must help it out a bit by telling it how to find the user information by setting the usersByUsernameQuery property. The following adjustment to the authenticationDao bean sets it up to query users from RoadRantz’s Motorist table:
<bean id="authenticationDao"
class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref bean="dataSource" />
<property name="usersByUsernameQuery">
<value>
SELECT email as username, password, enabled
FROM Motorist
WHERE email=?
</value>
</property>
</bean>
Now JdbcDaoImpl knows to look in the Motorist table for authentication information. But we must also tell JdbcDaoImpl how to query the database for a user’s granted authorities. For that we’ll set the authoritiesByUsernameQuery property:
<bean id="authenticationDao"
class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource" />
...
<property name="authoritiesByUsernameQuery">
<value>
SELECT email as username, privilege as authority
FROM Motorist_Privileges mp, Motorist m
WHERE mp.motorist_id = m.id
AND m.email=?
</value>
</property>
</bean>
Here we’ve configured JdbcDaoImpl to retrieve the motorist’s granted authorities from the Motorist_Privileges table. The query joins in the Motorist table because the Motorist_Privileges table only knows about a Motorist through a foreign key and JdbcDaoImpl expects the query to retrieve the authorities by username.
Working with encrypted passwords
When DaoAuthenticationProvider compares the user-provided password (at authentication) with the one retrieved from the database, it assumes that the password has been stored unencrypted. To beef up security, you may want to encrypt the password before storing it in the database. But if the password is stored encrypted in the database, the user-provided password must also be encrypted before the two passwords can be compared.
To accommodate encrypted passwords, DaoAuthenticationProvider can be wired with a password encoder. Spring Security comes with several password encoders to choose from, as described in table 7.2.
Table 7.2 Spring Security’s password encoders.
Password encoder ('''org.acegisecurity.providers.*''') | |
---|---|
encoding.Md5PasswordEncoder | Performs Message Digest (MD5) encoding on the password |
encoding.PlaintextPasswordEncoder | Performs no encoding on the password, returning it unaltered |
encoding.ShaPasswordEncoder | Performs Secure Hash Algorithm (SHA) encoding on the password |
ldap.authenticator.LdapShaPasswordEncoder | Encodes the password using LDAP SHA and salted-SHA (SSHA) encodings |
By default DaoAuthenticationProvider uses the PlaintextPasswordEncoder, which means that the password is left unencoded. But we can specify a different encoding by wiring DaoAuthenticationProvider’s passwordEncoder property. For example, here’s how to wire DaoAuthenticationProvider to use MD5 encoding:
<bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="authenticationDao" />
<property name="passwordEncoder">
<bean class="org.acegisecurity.providers.encoding.Md5PasswordEncoder" />
</property>
</bean>
You’ll also need to set a salt source for the encoder. A salt source provides the salt, or encryption key, for the encoding. Spring Security provides two salt sources:
- SystemWideSaltSource—Provides the same salt for all users
- ReflectionSaltSource—Uses reflection on a specified property of the user’s User object to generate the salt
ReflectionSaltSource is the more secure of the two salt sources because each user’s password will likely be encoded using a different salt value. Even if a hacker were to figure out the salt used to encode one user’s password, it’s unlikely that they’ll be able to use the same salt to crack another user’s password. To use a ReflectionSaltSource, wire it into DaoAuthenticationProvider’s saltSource property like this:
<bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="authenticationDao" />
<property name="passwordEncoder">
<bean class="org.acegisecurity.providers.encoding.Md5PasswordEncoder" />
</property>
<property name="saltSource">
<bean class="org.acegisecurity.providers.dao.salt.ReflectionSaltSource">
<property name="userPropertyToUse" value="userName" />
</bean>
</property>
</bean>
Here the user’s userName property is used as the salt to encode the user’s password. It’s important that the salt be static and never change. Otherwise, it will be impossible to authenticate the user (unless the password is re-encoded after the change using the new salt).
Although ReflectionSaltSource is certainly more secure, SystemWideSaltSource is much simpler and is sufficient for most circumstances. SystemWideSaltSource uses a single salt value for encoding all users’ passwords. To use a SystemWideSaltSource, wire the saltSource property like this:
<bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="authenticationDao" />
<property name="passwordEncoder">
<bean class="org.acegisecurity.providers.encoding.Md5PasswordEncoder" />
</property>
<property name="saltSource">
<bean class="org.acegisecurity.providers.dao.salt.SystemWideSaltSource">
<property name="systemWideSalt" value="ABC123XYZ789" />
</bean>
</property>
</bean>
In this case, the same salt value, ABC123XYZ789, is used for encoding all passwords.
Caching user information
Every time that a request is made to a secured resource, the authentication manager is asked to retrieve the user’s security information. But if retrieving the user’s information involves performing a database query, querying for the same data every time may hinder application performance. Recognizing that a user’s information will not frequently change, it may be better to cache the user data upon the first query and retrieve it from cache with every subsequent request.
To enable caching of user information, we must provide DaoAuthenticationProviderwith an implementationof the org.acegisecurity.providers.dao.UserCache interface. This interface mandates the implementation of three methods:
public UserDetails getUserFromCache(String username);
public void putUserInCache(UserDetails user);
public void removeUserFromCache(String username);
The methods in the UserCache are self-explanatory, providing the ability to put, retrieve, or remove user details from the cache. It would be simple enough for you to write your own implementation of UserCache. However, Spring Security provides two convenient implementations that you should consider before developing your own:
- org.acegisecurity.providers.dao.cache.NullUserCache
- org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache
NullUserCache does not actually perform any caching at all. Instead, it always returns null from its getUserFromCache() method, forcing DaoAuthenticationProvider to query for the user information. This is the default UserCache used by DaoAuthenticationProvider.
EhCacheBasedUserCache is a more useful cache implementation. As its name implies, it is based on EHCache. Using EHCache with DaoAuthenticationProvider is simple. Simply wire an EhCacheBasedUserCache bean into DaoAuthenticationProvider’s userCache property:
<bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="authenticationDao" />
...
<property name="userCache">
<bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
<property name="cache" ref="ehcache" />
</bean>
</property>
</bean>
The cache property refers to an ehcache bean, which should be an EHCache Cache object. One way to get such a Cache object is to use the Spring Modules’ cache module. For example, the following XML uses Spring Modules to configure EHCache:
<bean id="ehcache"
class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager" ref="cacheManager" />
<property name="cacheName" value="userCache" />
</bean>
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml" />
</bean>
As you may recall from chapter 5, Spring Modules’ EhCacheFactoryBean is a Spring factory bean that produces an EHCache Cache object. The actual caching configuration is found in the ehcache.xml file, which will be retrieved from the classpath.
DaoAuthenticationProvider is great when your application’s security information is kept in a relational database. Often, however, an application’s security is architected to authenticate against an LDAP server. Let’s see how to use Spring Security’s LdapAuthenticationProvider, which is a more suitable choice when authentication must happen via LDAP.
Authenticating against an LDAP repository
Spring Security supports authentication against LDAP through LdapAuthenticationProvider, an authentication provider that knows how to check user credentials against an LDAP repository. The following <bean> illustrates a typical configuration for LdapAuthenticationProvider:
<bean id="ldapAuthProvider"
class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
<constructor-arg ref="authenticator" />
<constructor-arg ref="populator" />
</bean>
As you can see, there’s not much exciting about the LdapAuthenticationProvider. There are no details on how to find the LDAP server or about the repository’s initial context. Instead, LdapAuthenticationProvider is wired with an authenticator and a populator through constructor injection. What are those beans and what are they used for?
In fact, although LdapAuthenticationProvider claims to know how to talk to an LDAP repository, it actually relies on two strategy objects to do the real work:
- The authenticator strategy handles the actual authentication (e.g., verification of user credentials) against the LDAP repository. The authenticator strategy can be any object that implements org.acegisecurity.providers.ldap.LdapAuthenticator.
- The populator strategy is responsible for retrieving a user’s set of granted authorities from the LDAP repository. The populator strategy is any object that implements org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator.
Because the authentication and authorities responsibilities are defined as strategies, separate from LdapAuthenticationProvider, you are able to wire in the strategy implementations that best fit your application’s security needs.
So just how are the authenticator and populator beans defined? Let’s start by looking at the authenticator bean, which defines the authentication strategy for LdapAuthenticationProvider.
Authenticating with LDAP binding
When it comes to authenticating against LDAP, two approaches are commonly taken:
- Binding to the LDAP server using the username and password of an LDAP user
- Retrieving a user’s entry in LDAP and comparing the supplied password with a password attribute in the LDAP record
For bind authentication, Spring Security comes with an LdapAuthenticator implementation called BindAuthenticator. BindAuthenticator uses an LDAP bind operator to bind as a user to the LDAP server. This approach relies on the LDAP server to authenticate the user’s credentials.
The following <bean> declares a BindAuthenticator in Spring:
<bean id="authenticator"
class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
<constructor-arg ref="initialDirContextFactory" />
<property name="userDnPatterns">
<list>
<value>uid={0},ou=motorists</value>
</list>
</property>
</bean>
Here I’ve declared the BindAuthenticator to be injected through a constructor argument and through the userDnPatterns property. We’ll come back to the constructor argument in a moment. First, let’s consider the userDnPatterns property.
The userDnPatterns property is used to tell BindAuthenticator how to find a user in LDAP. It takes a list of one or more patterns that BindAuthenticator will use as the distinguished name (DN) to identify the user. In this case, we’re only using a single DN pattern, as described in figure 7.6.
The {0} in the DN pattern is a pattern argument that serves as a placeholder for the username. For example, if the username is cwagon, the DN used to bind to LDAP will be uid=cwagon,ou=motorists.
Now back to the constructor argument. The main thing that a BindAuthenticator needs to know to be able to do its job is how to access the LDAP repository. Thus, it is constructed with a constructor argument wired to initialDirContextFactory, which is declared as follows:
<bean id="initialDirContextFactory"
class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
<constructor-arg
value="ldap://ldap.roadrantz.com:389/dc=roadrantz,dc=com"/>
</bean>
DefaultInitialDirContextFactory captures all the information needed to connect to an LDAP server and produces a JNDI DirContext object. If you don’t know much about JNDI or DirContext, don’t worry about these details. Just keep in mind that BindAuthenticator uses DefaultInitialDirContextFactory to know how to get to the LDAP repository.
The constructor argument used to create DefaultInitialDirContextFactory is wired with the URL of the LDAP provider. In this case, I’ve wired it with a reference to the RoadRantz LDAP server2 and established the initial context at dc=roadrantz,dc=com. The DN used to look up the user information will be relative to this initial context.
Authenticating by comparing passwords
As an alternative to bind authentication, Spring Security also supports authentication by password comparison with PasswordComparisonAuthenticator. PasswordComparisonAuthenticator works by comparing the supplied password with a password attribute (userPassword, by default) in the user record. Here’s how it might be configured in Spring:
<bean id="authenticator"
class="org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator">
<constructor-arg ref="initialDirContextFactory" />
<property name="userDnPatterns">
<list>
<value>uid={0},ou=motorists</value>
</list>
</property>
</bean>
Notice that with the exception of the class name, this PasswordComparisonAuthenticator declaration is identical to the BindAuthenticator declaration. That’s because in their simplest forms, both are fundamentally the same. Both need an initial context factory to know how to get to the LDAP repository, and both need one or more DN patterns for locating user records.
But there are a few more properties you use to customize PasswordComparisonAuthenticator. For example, if the default userPassword attribute doesn’t suit your needs, you can override it by wiring in a new value to the passwordAttributeName property. For example, declare PasswordComparisonAuthenticator as follows to compare the password against an attribute named userCredentials:
<bean id="authenticator"
class="org.acegisecurity.providers.ldap. authenticator.PasswordComparisonAuthenticator">
<constructor-arg ref="initialDirContextFactory" />
<property name="userDnPatterns">
<list>
<value>uid={0},ou=motorists</value>
</list>
</property>
<property name="passwordAttributeName" value="userCredentials" />
</bean>
In case you’re already thinking about it, don’t try accessing the LDAP server at ldap.roadrantz.com. That address is just an example… there’s no LDAP provider there.
Another customization you may choose is how the password is encoded in LDAP. By default, PasswordComparisonAuthenticator uses Spring Security’s LdapShaPasswordEncoder to encode the password before comparison. LdapShaPasswordEncoder supports LDAP Secure Hash Algorithm (SHA) and SSHA (salted-SHA) encodings. But if these don’t suit your needs, any implementation of org.acegisecurity.providers.encoding.PasswordEncoder, including those in table 7.2, can be wired into the passwordEncoder property.
For example, should you store the password in LDAP in plain text (not advised, but possible), declare PasswordComparisonAuthenticator like this:
<bean id="authenticator"
class="org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator">
<constructor-arg ref="initialDirContextFactory" />
<property name="userDnPatterns">
<list>
<value>uid={0},ou=motorists</value>
</list>
</property>
<property name="passwordEncoder">
<bean class="org.acegisecurity.providers.encoding.? PlaintextPasswordEncoder" />
</property>
</bean>
Before we move on to the populator strategy bean, let’s make one last tweak to the initialDirContextFactory bean.
Unlike BindAuthenticator, PasswordComparisonAuthenticator doesn’t bind to LDAP using the user’s DN. Some LDAP providers allow anonymous binding, in which case the initialDirContextFactory will work as is. However, for security reasons, most LDAP providers do not allow anonymous binding, so we’ll need to provide a manager DN and password for DefaultInitialDirContextFactory to bind with:
<bean id="initialDirContextFactory"
class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
<constructor-arg
value="ldap://ldap.roadrantz.com:389/dc=roadrantz,dc=com"/>
<property name="managerDn"
value="cn=manager,dc=roadrantz,dc=com" />
<property name="managerPassword" value="letmein" />
</bean>
When DefaultInitialDirContextFactory accesses LDAP, it will bind as the manager and act on behalf of the user when comparing the user’s password.
Now let’s configure the populator strategy bean to complete the LDAP authentication picture.
Declaring the populator strategy bean
Authenticating the user is only the first step performed by LdapAuthenticationProvider. Once the user’s identity is confirmed, LdapAuthenticationProvider must retrieve a list of the user’s granted authorities to determine what rights the user has within the application.
As with authentication, LdapAuthenticatorProvider uses a strategy object to find a user’s granted authorities from LDAP. Spring Security comes with one implementation of the LdapAuthoritiesPopulator interface: DefaultLdapAuthoritiesPopulator. Here’s how DefaultLdapAuthoritiesPopulator is configured in Spring:
<bean id="populator"
class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg ref="initialDirContextFactory" />
<constructor-arg value="ou=groups" />
<property name="groupRoleAttribute" value="ou" />
</bean>
The first thing you’ll notice is that DefaultLdapAuthoritiesPopulator is constructed with two constructor arguments, the first of which is a reference to our old friend, initialDirContextFactory. Just like the authenticator strategy bean, the populator strategy bean needs to know how to get to the LDAP repository to retrieve the user’s granted authorities.
The second constructor argument helps DefaultLdapAuthoritiesPopulator find groups within the LDAP repository. Since an LDAP repository is hierarchical in nature, security groups could be found anywhere. This constructor argument specifies a base DN from which to search for groups. This base DN is relative to the initial context. Therefore, with the group base DN as ou=groups, we’ll be searching for groups in ou=groups,dc=roadrantz,dc=com.
Finally, the groupRoleAttribute property specifies the name of the attribute that will contain role information (which effectively translates to a user’s granted authorities). It defaults to cn, but for our example, we’ve set it to ou.
Configured this way, DefaultLdapAuthoritiesPopulator will retrieve all groups that the user is a member of—that is, all groups that have a member attribute with the user’s DN.
For example, suppose that you have an LDAP repository populated with the following LDIF:3
dn: ou=groups,dc=roadrantz,dc=com
objectClass: top
objectClass: organizationalUnit
ou: groups
dn: cn=motorists,ou=groups,dc=roadrantz,dc=com
objectClass: groupOfNames
objectClass: top
cn: motorists
description: Acegi Security Motorists
member: uid=craig,ou=people,dc=roadrantz,dc=com
member: uid=raymie,ou=people,dc=roadrantz,dc=com
ou: motorist
dn: cn=vips,ou=groups,dc=roadrantz,dc=com
objectClass: groupOfNames
objectClass: top
cn: vips
description: Acegi Security Motorists
member: uid=craig,ou=people,dc=roadrantz,dc=com
ou: vip
LDAP aficionados know LDIF to be the LDAP Data Interchange Format. It’s the standard way of representing LDAP directory content.
When the user named craig is authenticated, his granted authorities will include ROLE_MOTORIST and ROLE_VIP. But when raymie is authenticated, her granted authorities will only include ROLE_MOTORIST, because the vips group does not have her DN as a member attribute.
Note that the group name (which is in the ou attribute) is converted to uppercase and then prefixed with ROLE_. The case normalization is just a convenience that helps find a user’s authorities regardless of whether it’s lower or uppercase. You can turn off this behavior by setting the convertToUpperCase property to false.
The ROLE_ prefix is provided for the sake of RoleVoter, which we’ll talk about in section 7.3.2. If you would rather use a different role prefix, you can configure DefaultLdapAuthoritiesPopulator’s rolePrefix property however you’d like.
For example, to turn off uppercase normalization and change the role prefix to GROUP_, configure DefaultLdapAuthoritiesPopulator like this:
<bean id="populator"
class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg ref="initialDirContextFactory" />
<constructor-arg value="ou=groups" />
<property name="groupRoleAttribute" value="ou" />
<property name="convertToUpperCase" value="false" />
<property name="rolePrefix" value="GROUP_" />
</bean>
One more tweak that you may want to make to DefaultLdapAuthoritiesPopulator is to change how it looks for members. Normally, it looks for groups whose member attribute has the user’s DN. That’s fine if your LDAP is set up to use the member attribute that way. But let’s say that instead of member, your LDAP repository uses an associate attribute to track membership. In that case, you’ll want to set the groupSearchFilter property like this:
<bean id="populator"
class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg ref="initialDirContextFactory" />
<constructor-arg value="ou=groups" />
<property name="groupRoleAttribute" value="ou" />
<property name="convertToUpperCase" value="false" />
<property name="rolePrefix" value="GROUP_" />
<property name="groupSearchFilter" value="(associate={0})" />
</bean>
Notice that the groupSearchFilter property uses the {0} pattern argument to represent the user’s DN.
Now we’ve wired in Spring Security’s authentication processing beans to identify the user. Next let’s see how Spring Security determines whether an authenticated user has the proper authority to access the secured resource.
Controlling access
Authentication is only the first step in Spring Security. Once Spring Security has figured out who the user is, it must decide whether to grant access to the resources that it secures. We’ve configured the authentication manager from figure 7.1. Now it’s time to configure the access decision manager. An access decision manager is responsible for deciding whether the user has the proper privileges to access secured resources. Access decision managers are defined by the org.acegisecurity.AccessDecisionManager interface:
public interface AccessDecisionManager {
public void decide(Authentication authentication, Object object,
ConfigAttributeDefinition config)
throws AccessDeniedException,
InsufficientAuthenticationException;
public boolean supports(ConfigAttribute attribute);
public boolean supports(Class clazz);
}
The supports() methods consider the secured resource’s class type and its configuration attributes (the access requirements of the secured resource) to determine whether the access decision manager is capable of making access decisions for the resource. The decide() method is where the ultimate decision is made. If it returns without throwing an AccessDeniedException or InsufficientAuthenticationException, access to the secured resource is granted. Otherwise, access is denied.
Voting access decisions
Spring Security’s access decision managers are ultimately responsible for determining the access rights for an authenticated user. However, they do not arrive at their decision on their own. Instead, they poll one or more objects that vote on whether or not a user is granted access to a secured resource. Once all votes are in, the decision manager tallies the votes and arrives at its final decision.
Spring Security comes with three implementations of AccessDecisionManager, as listed in table 7.3. Each takes a different approach to tallying votes.
All of the access decision managers are configured the same in the Spring configuration file. For example, the following XML excerpt configures a UnanimousBased access decision manager:
Table 7.3 Spring Security’s access decision managers help decide whether a user is granted access by tallying votes on whether to let the user in.
Access decision manager | How it decides to grant/deny access |
---|---|
org.acegisecurity.vote.AffirmativeBased | Allows access if at least one voter votes to grant access |
org.acegisecurity.vote.ConsensusBased | Allows access if a consensus of voters vote to grant access |
org.acegisecurity.vote.UnanimousBased | Allows access if all voters vote to grant access |
<bean id="accessDecisionManager"
class="org.acegisecurity.vote.UnanimousBased">
<property name="decisionVoters">
<list>
<ref bean="roleVoter"/>
</list>
</property>
</bean>
The decisionVoters property is where you provide the access decision manager with its list of voters. In this case, there’s only one voter, which is a reference to a bean named roleVoter. Let’s see how the roleVoter is configured.
Casting an access decision vote
Although access decision voters don’t have the final say on whether access is granted to a secured resource (that job belongs to the access decision manager), they play an important part in the access decision process. An access decision voter’s job is to consider the user’s granted authorities alongside the authorities required by the configuration attributes of the secured resource. Based on this information, the access decision voter casts its vote for the access decision manager to use in making its decision.
An access decision voter is any object that implements the org.acegisecurity.vote.AccessDecisionVoter interface:
public interface AccessDecisionVoter {
public static final int ACCESS_GRANTED = 1;
public static final int ACCESS_ABSTAIN = 0;
public static final int ACCESS_DENIED = -1;
public boolean supports(ConfigAttribute attribute);
public boolean supports(Class clazz);
public int vote(Authentication authentication, Object object,
ConfigAttributeDefinition config);
}
As you can see, the AccessDecisionVoter interface is similar to that of AccessDecisionManager. The big difference is that instead of a decide() method that returns void, there is a vote() method that returns int. That’s because an access decision voter doesn’t decide whether to allow access—it only returns its vote as to whether or not to grant access.
When faced with the opportunity to place a vote, an access decision voter can vote one of three ways:
- ACCESS_GRANTED—The voter wishes to allow access to the secured resource.
- ACCESS_DENIED—The voter wishes to deny access to the secured resource.
- ACCESS_ABSTAIN—The voter is indifferent.
As with most Spring Security components, you are free to write your own implementation of AccessDecisionVoter. However, Spring Security comes with RoleVoter, a useful implementation that votes when the secured resources configuration attributes represent a role. More specifically, RoleVoter participates in a vote when the secured resource has a configuration attribute whose name starts with ROLE_.
The way that RoleVoter decides on its vote is by simply comparing all of the configuration attributes of the secured resource (that are prefixed with ROLE_) with all of the authorities granted to the authenticated user. If RoleVoter finds a match, it will cast an ACCESS_GRANTED vote. Otherwise, it will cast an ACCESS_ DENIED vote.
The RoleVoter will only abstain from voting when the authorities required for access are not prefixed with ROLE_. For example, if the secured resource only requires non-role authorities (such as CREATE_USER), the RoleVoter will abstain from voting.
You can configure a RoleVoter with the following XML in the Spring configuration file:
<bean id="roleVoter"
class="org.acegisecurity.vote.RoleVoter"/>
As stated earlier, RoleVoter only votes when the secured resource has configuration attributes that are prefixed with ROLE_. However, the ROLE_ prefix is only a default. You may choose to override the default prefix by setting the rolePrefix property:
<bean id="roleVoter"
class="org.acegisecurity.vote.RoleVoter">
<property name="rolePrefix" value="GROUP_" />
</bean>
Here, the default prefix has been overridden to be GROUP_. Thus the RoleVoter will now only cast authorization votes on privileges that begin with GROUP_.
Handling voter abstinence
Knowing that any voter can vote to grant or deny access or abstain from voting, you may be wondering what would happen if all voters abstained from voting. Will the user be granted or denied access?
By default, all the access decision managers deny access to a resource if all the voters abstain. However, you can override this default behavior by setting the allowIfAllAbstain property on the access decision manager to true:
<bean id="accessDecisionManager"
class="org.acegisecurity.vote.UnanimousBased">
<property name="decisionVoters">
<list>
<ref bean="roleVoter"/>
</list>
</property>
<property name="allowIfAllAbstain" value="true" />
</bean>
By setting allowIfAllAbstain to true, you are establishing a policy of “silence is consent.” In other words, if all voters abstain from voting, access is granted as if they had voted to grant access.
Now that you’ve seen how Spring Security’s authentication and access control managers work, let’s put them to work. In the next section you’ll learn how to use Spring Security’s collection of servlet filters to secure a web application. Later, in section 7.6, we’ll dig deep into an application and see how to use Spring AOP to apply security at the method-invocation level.
Securing web applications
Spring Security’s support for web security is heavily based on servlet filters. These filters intercept an incoming request and apply some security processing before the request is handled by your application. Spring Security comes with a handful of filters that intercept servlet requests and pass them on to the authentication and access decision managers to enforce security. Depending on your needs, you may use several of the filters listed in table 7.4 to secure your application.
Even though table 7.4 lists 17 filters provided by Spring Security, most applications will suffice with only a handful of them. Specifically, when a request is submitted to a Spring-secured web application, it will pass through at least the following four filters (as illustrated in figure 7.7):
Table 7.4 Spring Security controls access to web applications through several servlet filters.
Filter ('''org.acegisecurity.*''') | |
---|---|
adapters.HttpRequestIntegrationFilter | Populates the security context using information from the user principal provided by the web container. |
captcha. CaptchaValidationProcessingFilter | Helps to identify a user as a human (as opposed to an automated process) using Captcha techniques. Captcha is a technique used to distinguish human users from automated/computer-driven users by challenging the user to identify something (typically an image) that is easily identified by a human, but difficult for a computer to make out. |
concurrent.ConcurrentSessionFilter | Ensures that a user is not simultaneously logged in more than a set number of times. |
context. HttpSessionContextIntegrationFilter | Populates the security context using information obtained from the HttpSession. |
intercept.web. FilterSecurityInterceptor | Plays the role of security interceptor, deciding whether or not to allow access to a secured resource. |
providers.anonymous. AnonymousProcessingFilter | Used to identify an unauthenticated user as an anonymous user. |
securechannel. ChannelProcessingFilter | Ensures that a request is being sent over HTTP or HTTPS (as the need dictates). |
ui.basicauth.BasicProcessingFilter | Attempts to authenticate a user by processing an HTTP Basic authentication. |
ui.cas.CasProcessingFilter | Authenticates a user by processing a CAS (Central Authentication Service) ticket. |
ui.digestauth.DigestProcessingFilter | Attempts to authenticate a user by processing an HTTP Digest authentication. |
ui.ExceptionTranslationFilter | Handles any AccessDeniedException or AuthenticationException thrown by any of the other filters in the filter chain. |
ui.logout.LogoutFilter | Used to log a user out of the application. |
ui.rememberme. RememberMeProcessingFilter | Automatically authenticates a user who has asked to be “remembered” by the application. |
ui.switchuser. SwitchUserProcessingFilter | Used to switch out a user. Provides functionality similar to Unix’s su. |
ui.webapp. AuthenticationProcessingFilter | Accepts the user’s principal and credentials and attempts to authenticate the user. |
ui.webapp.SiteminderAuthenticationProcessingFilter | Authenticates a users by processing CA/ Netegrity SiteMinder headers. |
ui.x509.X509ProcessingFilter | Authenticates a user by processing an X.509 certificate submitted by a client web browser. |
wrapper.SecurityContextHolderAwareRequestFilter | Populates the servlet request with a request wrapper. |
- (1) Due to the stateless nature of HTTP, Spring Security needs a way to preserve a user’s authentication between web requests. An integration filter is responsible for retrieving a previously stored authentication (most likely stored in the HTTP session) at the beginning of a request so that it will be ready for Spring Security’s other filters to process.
- (2)' Next, one of the authentication-processing filters will determine if the request is an authentication request. If so, the pertinent user information (typically a username/ password pair) is retrieved from the request and passed on to the authentication manager to determine the user’s identity. If this is not an authentication request, the filter performs no processing and the request flows on down the filter chain.
- (3) The next filter in line is the exception translation filter. The exception translation filter’s sole purpose in life is to translate AccessDeniedExceptions and AuthenticationExceptions that may have been thrown into appropriate HTTP responses. If an AuthenticationException is detected, the request will be sent to an authentication entry point (e.g., login screen). If an AccessDeniedException is thrown, the default behavior will be to return an HTTP 403 error to the browser.
- (4) The last of the required filters is the filter security interceptor. This filter plays the part of the security interceptor (see section 7.1.1) for web applications. Its job is to examine the request and determine whether the user has the necessary privileges to access the secured resource. It doesn’t work alone, though. It leans heavily on the authentication manager and the access decision manager to help it grant or restrict access to the resource.
If the user makes it past the filter security interceptor, the user will be granted access to the secured web resource. Otherwise, an AccessDeniedException will be thrown and the exception translation filter will handle it appropriately.
We’ll explore each of these filters individually in more detail. But before you can start using them, you need to understand how Spring Security places a Springflavored twist on servlet filters.
Proxying Spring Security’s filters
If you’ve ever used servlet filters, you know that for them to take effect, you must configure them in the web application’s web.xml file, using the <filter> and <filter-mapping> elements. While this works, it doesn’t lend itself to configuration using dependency injection.
For example, suppose you have the following filter declared in your web.xml file:
<filter>
<filter-name>Foo</filter-name>
<filter-class>FooFilter</filter-class>
</filter>
Now suppose that FooFilter needs a reference to a Bar bean to do its job. How can you inject an instance of Bar into FooFilter?
The short answer is that you can’t. The web.xml file has no notion of dependency injection, nor is there a straightforward way of retrieving beans from the Spring application context and wiring them into a servlet filter. The only option you have is to use Spring’s WebApplicationContextUtils to retrieve the bar bean from the Spring context. For example, you might place the following in the filter’s code:
ApplicationContext ctx = WebApplicationContextUtils.
getWebApplicationContext(servletContext);
Bar bar = (Bar) ctx.getBean("bar");
The problem with this approach, however, is that you must code Spring-specific code into your servlet filter. Furthermore, you end up hard-coding a reference to the name of the bar bean.
Fortunately, Spring Security provides a better way through FilterToBeanProxy.
Proxying servlet filters
FilterToBeanProxy is a special servlet filter that, by itself, doesn’t do much. Instead, it delegates its work to a bean in the Spring application context, as illustrated in figure 7.8. The delegate bean implements the javax.servlet.Filter interface just like any other servlet filter, but is configured in the Spring configuration file instead of web.xml.
By using FilterToBeanProxy, you are able to configure the actual filter in Spring, taking full advantage of Spring’s support for dependency injection. The web.xml file only contains the <filter> declaration for FilterToBeanProxy. The actual FooFilter is configured in the Spring configuration file and uses setter injection to set the bar property with a reference to a Bar bean.
To use FilterToBeanProxy, you must set up a <filter> entry in the web application’s web.xml file. For example, if you are configuring a FooFilter using FilterToBeanProxy, you’d use the following code:
<filter>
<filter-name>Foo</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>
com.roadrantz.FooFilter
</param-value>
</init-param>
</filter>
Here the targetClass initialization parameter is set to the fully qualified class name of the delegate filter bean. When this FilterToBeanProxy is initialized, it will look for a bean in the Spring context whose type is FooFilter. FilterToBeanProxy will delegate its filtering to the FooFilter bean found in the Spring context:
<bean id="fooFilter" class="com.roadrantz.FooFilter">
<property name="bar" ref="bar" />
</bean>
If a FooFilter bean isn’t found, an exception will be thrown. If more than one matching bean is found, the first one found will be used.
Optionally, you can set the targetBean initialization parameter instead of targetClass to pick out a specific bean from the Spring context. For example, you might pick out the fooFilter bean by name by setting targetBean as follows:
<filter>
<filter-name>Foo</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetBean</param-name>
<param-value>fooFilter</param-value>
</init-param>
</filter>
The targetBean initialization parameter enables you to be more specific about which bean to delegate filtering to, but requires that you match the delegate’s name exactly between web.xml and the Spring configuration file. This creates extra work for you if you decide to rename the bean. For this reason, it’s probably better to use targetClass instead of targetBean.
NOTE
- It may be interesting to know that there’s nothing about FilterToBeanProxy that is specific to Spring Security or to securing web applications. You may find that FilterToBeanProxy is useful when configuring your own servlet filters. In fact, because it’s so useful, a similar filter named org.springframework.web.filter.DelegatingFilterProxy was added to Spring in version 1.2.
Finally, you’ll need to associate the filter to a URL pattern. The following <filter-mapping> ties the Foo instance of FilterToBeanProxy to a URL pattern of /* so that all requests are processed:
<filter-mapping>
<filter-name>Foo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Regardless of whether you choose targetClass or targetBean, FilterToBeanProxy must be able to access the Spring application context. This means that the Spring context has to be loaded using Spring’s ContextLoaderListener or ContextLoaderServlet (see chapter 13).
So now that you know how FilterToBeanProxy works, the burning question is: what does all of this have to do with Spring Security? I’m glad you asked.
As I mentioned earlier, Spring Security uses servlet filters in enforcing web security. Each of these filters must be injected with other beans from the Spring application context to do their job. For example, the FilterSecurityInterceptor needs to be injected with the AuthenticationManager and the AccessDecisionManager so that it can enforce security. Unfortunately, the servlet specification doesn’t make it easy to do dependency injection on servlet filters. FilterToBeanProxy solves this problem by being the “front-man” for the real filters that are configured as beans in the Spring application context.
Proxying multiple filters
Now you’re probably wondering, if FilterToBeanProxy handles requests by proxying to a Spring-configured bean, what is on the receiving end (on the Spring side)? That’s an excellent question.
Actually, the bean that FilterToBeanProxy proxies to can be any implementation of javax.servlet.Filter. This could be any of Spring Security’s filters, or it could be a filter of your own creation. But as I’ve already mentioned, Spring Security requires at least four and possibly a dozen or more filters to be configured. Does this mean that you have to configure a FilterToBeanProxy for each of Spring Security’s filters?
Absolutely not. While it’s certainly possible to add several FilterToBeanProxys to web.xml (one for each of Spring Security’s filters), that’d be way too much XML to write. To make life easier, Spring Security offers FilterToBeanProxy’s cohort, FilterChainProxy.
FilterChainProxy is an implementation of javax.servlet.Filter that can be configured to chain together several filters at once, as illustrated in figure 7.9.
FilterBeanProxy intercepts the request from the client and sends it to FilterChainProxy for handling. FilterChainProxy then passes the request through one or more filters that are configured in the Spring application context. FilterChainProxy is configured like this in the Spring application context:
<bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=filter1,filter2,filter3
</value>
</property>
</bean>
The filterInvocationDefinitionSource property takes a String that is parsed into a scheme that FilterChainProxy will use to chain filters together. In this example, the first line tells FilterChainProxy to normalize URL patterns to lowercase before comparing them. The next line says that Apache Ant–style paths are to be used when declaring URL patterns.
Finally, one or more URL-to-filter-chain mappings are provided. Here, the /** pattern (in Ant, this means all URLs will match) is mapped to three filters. The filter configured as the filter1 bean will be the outermost filter and will receive the request first. The filter2 bean is next. And the filter3 bean will be the innermost bean and will be the last filter to receive the request before the actual secured resource is processed. When a response is returned, it flows in reverse order, from filter3 to filter1.
Configuring proxies for Spring Security
Up to now, we’ve kept the configuration of the filter proxies mostly generic. But it’s time to configure them for use in Spring Security. First up, let’s configure a FilterToBeanProxy in web.xml:
<filter>
<filter-name>Spring Security Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
Here we’ve configured FilterToBeanProxy to proxy to any bean in the Spring context whose type is FilterChainProxy. This is perfect because, as you may have guessed, we’re going to configure a FilterChainProxy in the Spring context.
But before we leave the web.xml file, we need to configure a filter mapping for the FilterToBeanProxy:
<filter-mapping>
<filter-name>Spring Security Filter Chain Proxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
As with any servlet <filter-mapping>, the <filter-name> value must match the <filter-name> of the <filter> that is being mapped. As for the <url-pattern>, we recommend that you use /* so that all requests are piped through Spring Security and are potentially secured. Even if it isn’t necessary to secure the entire application, filtering all requests through /* will keep the web.xml configuration simple. Later, when we configure FilterSecurityInterceptor, we can choose which parts of the application should be secured and which should not.
And that’s all that’s needed in the web.xml file! Even though Spring Security uses several filters to secure a web application, we only have to configure the one FilterToBeanProxy filter in web.xml. From here on out, we’ll configure Spring Security in the Spring application context.
Speaking of the Spring application context, we’re going to need a FilterChainProxy bean to handle the requests delegated from FilterToBeanProxy. For the RoadRantz application, let’s start with the minimal Spring Security configuration by configuring a FilterChainProxy in the Spring application context (in roadrantz-security.xml) with the <bean> declaration shown in listing 7.1.
Listing 7.1 Configuring a FilterChainProxy for Spring Security
<bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionIntegrationFilter,
authenticationProcessingFilter,
exceptionTranslationFilter,
filterSecurityInterceptor
</value>
</property>
</bean>
Here, we’ve configured FilterChainProxy to chain together four of Spring Security’s filters. We’ll explore each of these filters in more detail in a moment. First, however, it’s important to point out that if it weren’t for FilterChainProxy, we’d have to configure four different <filter> entries and eight different <filtermapping> entries in web.xml. But with FilterChainProxy, the web.xml configuration is simplified to a single <filter> and <filter-mapping> pair.
As the request comes in from the client and makes its way toward the secured resource, it passes through each of the filters. You can think of Spring Security’s filters as different layers in the skin of an onion. Figure 7.10 shows how a request flows through each of Spring Security’s filters on its way to a secured resource.
Aside from looking a bit like the opening segment of a Looney Tunes cartoon, figure 7.10 illustrates something very important about Spring Security. Although not all of Spring Security’s filters are required for every application, it’s crucial that the ones that are used be configured in a specific order in the filterInvocationDefinitionSource property. That’s because some of the filters make assumptions about what security tasks have been performed before them. If the filters are configured out of order, they conflict with one another.
Each of the filters configured in the filterInvocationDefinitionSource property of FilterChainProxy refers to a <bean> configured in the Spring application context. Let’s follow the path of the request through each of the filters, starting with the integration filter.
Handling the security context
Have you seen the movie Finding Nemo? If so, you’ll most certainly remember that one of the main characters was a blue tang (that’s a fish, in case you didn’t know) named Dory. Many of the funniest moments in the movie were a result of Dory’s struggle with short-term memory loss. Throughout much of the movie, she would forget little things, such as where they were going or the name of Marlin, the clownfish who was looking for his son Nemo.
As it turns out, HTTP and Dory have a lot in common. You see, HTTP is a stateless protocol. That means that, like Dory, HTTP lives in the here-and-now and tends to forget things between requests. This poses a small problem for secure applications that are served over HTTP. Without something to help HTTP remember who you are, you’d have to log into an application with each request.
Fortunately, several solutions have been devised to help HTTP with its shortterm memory loss. With Java-based web applications, sessions can be used to store data between requests. With each request, stored user information can be retrieved from the session, used to process the request, and then placed back into the session so that it’s available for the next request.
The first Spring Security filter that a request must pass through is HttpSessionContextIntegrationFilter. This filter’s main job is to try to remember an authenticated user between requests. It is configured in the Spring application context like this:
<bean id="httpSessionIntegrationFilter"
class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
When a request first comes in, HttpSessionContextIntegrationFilter checks to see if it can find the user’s authentication information in the session (stored there from a previous request). If so then HttpSessionContextIntegrationFilter makes the user information available for Spring Security to use in the course of the current request. At the end of the request, HttpSessionContextIntegrationFilter will deposit the user’s authentication information back into the session so that it will be available for the next request.
If HttpSessionContextIntegrationFilter finds a user’s authentication information in the session, there’s no need for the user to log in again. But if the user’s authentication can’t be found, it probably means that they haven’t logged in yet. To handle user login, we’ll need to configure an authentication-processing filter, which is the next filter configured in FilterChainProxy and the next filter we’ll discuss.
Prompting the user to log in
When securing web applications with Spring Security, authentication is performed using a tag-team made up of an authentication entry point and an authentication-processing filter. As illustrated in figure 7.11, an authentication entry point prompts the user for login information, which is then processed by the authentication-processing filter.
An authentication entry point starts the login process by prompting the user with a chance to provide their credentials. After the user submits the requested information, an authentication-processing filter attempts to authenticate the user.
Spring Security comes with five matched pairs of authentication entry points and authentication-processing filters, as described in table 7.5.
Table 7.5 Spring Security’s authentication entry points prompt the user to log in. An authentication-processing filter processes the login request once the credentials are submitted.
Authentication entry point | Authentication-processing filter | |
---|---|---|
BasicProcessingFilterEntryPoint | BasicProcessingFilter | Prompts the user to log in via a browser dialog using HTTP Basic authentication |
AuthenticationProcessingFilterEntryPoint | AuthenticationProcessingFilter | Redirects the user to an HTML form-based login page |
CasProcessingFilterEntryPoint | CasProcessingFilter | Redirects the user to login page provided by JA-SIG’s CAS single sign-on solution |
DigestProcessingFilterEntryPoint | DigestProcessingFilter | Prompts the user to log in via a browser dialog using HTTP Digest authentication |
X509ProcessingFilterEntryPoint | X509ProcessingFilter | Processes authentication using X.509 certificates |
Let’s take a closer look at how the authentication entry point and authenticationprocessing filter work together to authenticate a user. We’ll examine a few of Spring Security’s authentication options, starting with Spring Security’s support for HTTP Basic authentication.
Basic authentication
The simplest form of web-based authentication is known as Basic authentication. Basic authentication works by sending an HTTP 401 (Unauthorized) response to the web browser. When the browser sees this response, it realizes that the server needs the user to log in. In response, the browser pops up a dialog box to prompt the user for a username and password (see figure 7.12).
When the user submits the login, the browser sends it back to the server to perform the authentication. If authentication is successful, the user is sent to the desired target URL. Otherwise, the server may send back another HTTP 401 response and the browser will prompt the user again to log in.
Using Basic authentication with Spring Security starts with configuring a BasicProcessingFilterEntryPoint bean:
<bean id="authenticationEntryPoint"
class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
<property name="realmName" value="RoadRantz" />
</bean>
BasicProcessingFilterEntryPoint has only one property to be configured. The realmName property specifies an arbitrary String that is displayed in the login dialog box to give users some indication of what it is that they’re being asked to log into. For example, the dialog box shown in figure 7.12 asks the user to enter a username and password for the RoadRantz realm.
After the user clicks the OK button in the login dialog box, the username and password are submitted via the HTTP header back to the server. At that point, BasicProcessingFilter picks it up and processes it:
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager"
ref="authenticationManager"/>
<property name="authenticationEntryPoint"
ref="authenticationEntryPoint"/>
</bean>
BasicProcessingFilter pulls the username and password from the HTTP header and sends them on to the authentication manager, which is wired in through the authenticationManager property. If authentication is successful, an Authentication object is placed into the session for future reference. Otherwise, if authentication fails, control is passed on to the authentication entry point (the BasicProcessingFilterEntryPoint wired in through the authenticationEntryPoint property) to give the user another chance.
Although Basic authentication is fine for simple applications, it has some limitations. Primarily, the login dialog box presented by the browser is neither userfriendly nor aesthetically appealing. Basic authentication doesn’t fit the bill when you want a more professional-looking login.
For the RoadRantz application, we want an eye-appealing web page that shares the same look and feel as the rest of the application. Therefore, Spring Security’s AuthenticationProcessingFilterEntryPoint is more appropriate for our needs. Let’s see how it works.
Form-based authentication
AuthenticationProcessingFilterEntryPoint is an authentication entry point that prompts the user with an HTML-based login form. For the RoadRantz application, we’ll configure it in roadrantz-security.xml as follows:
<bean id="authenticationEntryPoint"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/login.htm" />
<property name="forceHttps" value="true" />
</bean>
AuthenticationProcessingFilterEntryPoint is configured here with two properties. The loginFormUrl property is set to a URL (relative to the web application’s context) that will display the login page. The forceHttps property is set to true to force the login page to be displayed securely over HTTPS, even if the original request was made over HTTP.
Here we’ve set loginFormUrl to /login.htm. loginFormUrl can be configured with any URL that takes the user to an HTML form for login. In the case of the RoadRantz application, /login.htm is ultimately associated with a Spring MVC UrlFilenameViewController that displays the login page. Figure 7.13 shows what the RoadRantz login page might look like.
Regardless of how the login page is displayed, it’s important that it contain an HTML form that resembles the following:
<form method="POST" action="j_acegi_security_check">
<b>Username: </b><input type="text" name="j_username"><br>
<b>Password: </b><input type="password" name="j_password"><br>
<input type="submit" value="Login">
</form>
The login form must have two fields named j_username and j_password in which the user will enter the username and password, respectively. That’s because those are the field names expected by AuthenticationProcessingFilter. As for the form’s action attribute, it has been set to j_acegi_security_check, which will be intercepted by AuthenticationProcessingFilter.
AuthenticationProcessingFilter is a filter that processes authentication based on the username and password information given to it in the j_username and j_password parameters. It is configured in roadrantz-security.xml as follows:
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="filterProcessesUrl"
value="/j_acegi_security_check" />
<property name="authenticationFailureUrl"
value="/login.htm?login_error=1" />
<property name="defaultTargetUrl" value="/" />
<property name="authenticationManager"
ref="authenticationManager"/>
</bean>
The filterProcessesUrl property tells AuthenticationProcessingFilter which URL it should intercept. This is the same URL that is in the login form’s action attribute. It defaults to /j_acegi_security_check, but I’ve explicitly defined it here to illustrate that you can change it if you’d like.
The authenticationFailureUrl property indicates where the user will be sent should authentication fail. In this case, we’re sending them back to the login page, passing a parameter to indicate that authentication failed (so that an error message may be displayed).
Under normal circumstances, when authentication is successful, AuthenticationProcessingFilter will place an Authentication object in the session and redirect the user to their desired target page. The defaultTargetUrl property defines what will happen in the unusual circumstance where the target URL isn’t known. This could happen if the user navigates directly to the login screen without first attempting to access a secured resource.
Finally, the authenticationManager property is wired with a reference to an authenticationManager bean. Just like all other authentication-processing filters, the form-based AuthenticationProcessingFilter relies on an authentication manager to help establish the user’s identity.
Now we have an authentication processing filter and authentication entry point defined in the Spring configuration, ready for users to log in. But there’s one loose end left to tie up. The authentication-processing filter is wired into the FilterChainProxy, but you’re probably wondering what is supposed to be done with the authentication entry point. What part of Spring Security uses the authentication entry point to prompt the user for login?
I’ll answer that question for you soon. But first, we’ll need to look at the exception translation filter, the next filter in line to handle a secured request.
Handling security exceptions
In the course of providing security, any of Spring Security’s filters may throw some variation of AuthenticationException or AccessDeniedException. AuthenticationException, for example, will be thrown if, for any reason, the user cannot be authenticated. This could be because the user provided an invalid username/ password pair. Or it could even mean that the user hasn’t even attempted to log in yet. Even if the user is successfully authenticated, they may not be granted the authority necessary to visit certain secured pages. In that case, AccessDeniedException will be thrown.
Without anything to handle Spring Security’s AuthenticationException or AccessDeniedException, they’d flow up to the servlet container and be displayed in the browser as a really ugly stack trace. It goes without saying that this is less than ideal. We’d prefer to handle such exceptions in a more graceful manner.
That’s where ExceptionTranslationFilter comes in. ExceptionTranslationFilter is configured at a level just outside of FilterSecurityInterceptor so that it will have a chance to catch the exceptions that may be thrown by FilterSecurityInterceptor. ExceptionTranslationFilter is configured in Spring as follows:
<bean id="exceptionTranslationFilter"
class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint"
ref="authenticationEntryPoint" />
</bean>
ExceptionTranslationFilter catches the exceptions thrown from FilterSecurityInterceptor… but what does it do with them?
Notice that ExceptionTranslationFilter is injected with a reference to the authentication entry point. If ExceptionTranslationFilter catches an AuthenticationException, it means that the user hasn’t been successfully authenticated. In that case, the user is sent to the authentication entry point configured in the authenticationEntryPoint property to try to log in.
Handling authorization exceptions
An AccessDeniedException indicates that the user has been authenticated but has not been granted sufficient authority to access the resource that has been requested. In that case, an HTTP 403 error is returned to the browser. The HTTP
403 error means “forbidden” and indicates that the user isn’t allowed to access a requested resource.
By default, ExceptionTranslationFilter uses an AccessDeniedHandlerImpl to deal with AccessDeniedExceptions. Unless otherwise configured, AccessDeniedHandlerImpl only sends an HTTP 403 error to the browser. Unfortunately, an HTTP 403 error is usually displayed in a user-unfriendly way in the browser.
But we can configure our own AccessDeniedHandlerImpl that will forward the user to a nicer-looking error page when AccessDeniedException is caught. The following XML configures an AccessDeniedHandlerImpl that sends the user to an error page at the URL /error.htm:
<bean id="accessDeniedHandler"
class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/error.htm" />
</bean>
All that’s left to do is to wire this accessDeniedHandler into the ExceptionTranslationFilter:
<bean id="exceptionTranslationFilter"
class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint"
ref="authenticationEntryPoint" />
<property name="accessDeniedHandler"
ref="accessDeniedHandler" />
</bean>
We’ve now declared three out of the four security filters required by Spring Security. The three filters configured thus far are the tumblers in Spring Security’s lock. Now it’s time to configure FilterSecurityInterceptor, the latch that decides whether or not to allow access to a web resource.
Enforcing web security
Whenever a user requests a page within a web application, that page may or may not be a page that needs to be secure. In Spring Security, a filter security interceptor handles the interception of requests, determining whether a request is secure and giving the authentication and access decision managers a chance to verify the user’s identity and privileges. It is declared in the Spring configuration file as follows:
<bean id="filterSecurityInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"
ref="authenticationManager" />
<property name="accessDecisionManager"
ref="accessDecisionManager" />
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/editProfile.htm=ROLE_MOTORIST
</value>
</property>
</bean>
FilterSecurityInterceptor plays the part of the security interceptor (as described in section 7.1.1) for web applications. When a request comes in for a resource (likely a web page or Spring MVC controller), FilterSecurityInterceptor will perform several checks to see whether the user is allowed to access the resource:
- Has the user been authenticated? If not, FilterSecurityInterceptor will throw an AuthenticationException (which will be handled by the exception translation filter, which will be handled by the exception translation filter).
- Is the requested resource secured? The objectDefinitionSource property defines which resources are to be secured and what privileges are required to access them. If the request’s URL matches one of the URL patterns in objectDefinitionSource then the resource is secure.
- Has the user been granted privileges that are sufficient for accessing the resource? FilterSecurityInterceptor will compare the user’s granted privileges with those declared as being required for the resource. If the user’s privileges are sufficient, the request will be granted. If not, FilterSecurityInterceptor will throw an AccessDeniedException (that will be handled by the exception translation filter).
FilterSecurityInterceptor doesn’t work alone when making these decisions. That’s why it’s wired with a reference to an authentication manager and a reference to an access decision manager.
As for the objectDefinitionSource property, this is how we declare which resources are secured and what privileges are required to access them. The first line indicates that we want all URL patterns to be normalized to lowercase before comparison (otherwise, the URL patterns will be case sensitive). The next line indicates that we’ll be using Ant-style paths for declaring the URL patterns.
From the third line on, we can declare one or more URL patterns and what privilege is required to access each. In this case, we have one URL pattern that ensures that only authenticated users with the ROLE_MOTORIST role are allowed to visit the editProfile.htm page.
If the user has been authenticated and has appropriate privileges, FilterSecurityInterceptor will let the request continue. If, however, FilterSecurityInterceptor determines that the user doesn’t have adequate privileges, either an AuthenticationException or an AccessDeniedException will be thrown.
At this point, we’ve configured the basic filters required to secure the RoadRantz application with Spring Security. But there’s one more filter that, although not required, comes in handy for guaranteeing that secure information be transmitted securely in web requests. Next, I’ll show you how to ensure that secure requests are carried over HTTPS using Spring Security’s ChannelProcessingFilter.
Ensuring a secure channel
The letter “s” is the most important letter on the Internet. Anyone who has spent more than five minutes surfing the Web knows that most web pages are associated with URLs that start with “https://”. That’s because most web pages are requested and sent using the HTTP protocol.
HTTP is perfect for most pages, but is woefully insufficient when confidential information is passed around on the Internet. Information sent over HTTP can be easily intercepted and read by nefarious hackers who will use it for their illpurposed plans.
When information must be sent confidentially, the letter “s” goes to work. For those pages, you’ll find that the URL begins with “https://” instead of simply “https://”. With HTTPS, information is still sent using HTTP, but is sent on a different port and is encrypted so that if it is intercepted, it can’t be read by anyone for whom it isn’t meant.
Unfortunately, the problem with HTTPS is that the burden of ensuring that a page be transferred over HTTPS belongs to whoever writes the link to the secure page. In other words, for a page to be secured with encrypted HTTPS, it must be linked to with a URL that starts with “https://”. Without that one little “s” in there, the page will be sent unencrypted over HTTP.
Because it’s too easy to omit the all-important “s,” Spring Security offers a foolproof way to ensure that certain pages be transferred using HTTPS, regardless of which URL was used to link to them. As illustrated in figure 7.14, ChannelProcessingFilter is a Spring Security filter that intercepts a request, checks to see if it needs to be secure and, if so, calls “s” to work by redirecting the request to an HTTPS form of the original request URL.
We’ve configured a ChannelProcessingFilter for the RoadRantz application in roadrantz-security.xml as follows:
<bean id="channelProcessingFilter"
class="org.acegisecurity.securechannel.ChannelProcessingFilter">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/login.htm=REQUIRES_SECURE_CHANNEL
/j_acegi_security_check*=REQUIRES_SECURE_CHANNEL
/**=REQUIRES_INSECURE_CHANNEL
</value>
</property>
<property name="channelDecisionManager"
ref="channelDecisionManager" />
</bean>
The filterInvocationDefinitionSource property is configured to tell ChannelProcessingFilter which pages should be secured with HTTPS and which should not. It is configured with one or more URL patterns that are mapped to be either secure or not secure.
But before the URL patterns appear, we must set a few ground rules for how the URLs will be handled. The first line contains CONVERT_URL_TO_LOWERCASE_ BEFORE_COMPARISON to tell Spring Security to normalize all URLs before comparing them to the URL patterns that will follow. The second line contains PATTERN_TYPE_APACHE_ANT, which indicates that the URL patterns will be presented using Apache Ant–style paths.
Each line that follows maps a URL pattern to its security requirements. In the RoadRantz application, the login page must be secure (so that nobody can intercept a user’s password). Therefore,/login.htmismappedto REQUIRES_SECURE_CHANNEL, indicating that it should be sent over HTTPS. Likewise, information sent to the URL that processes logins must also be encrypted. As you’ll see soon, Spring Security’s AuthenticationProcessingFilter responds to /j_acegi_security_check, so this URL pattern is also set to REQUIRES_SECURE_CHANNEL.
None of the other pages in the RoadRantz application require encryption. So the /** URL pattern (which, in Ant path syntax indicates all URLs) is set to REQUIRES_INSECURE_CHANNEL, specifying that all other pages must be sent over plain, unsecured HTTP. Notice that these pages require an insecure channel. That means that if these pages are accessed over HTTPS, ChannelProcessingFilter will redirect them to be sent over HTTP.
Managing channel decisions
While ChannelProcessingFilter handles the task of redirecting the HTTP requests to HTTPS (and vice versa), it doesn’t necessarily need to redirect every request. Thus, it depends on a ChannelDecisionManagerImpl (wired into the channelDecisionManager property) to weigh the decision as to whether or not a request should be redirected. The ChannelDecisionManagerImpl is configured as follows:
<bean id="channelDecisionManager"
class="org.acegisecurity.securechannel.ChannelDecisionManagerImpl">
<property name="channelProcessors">
<list>
<bean class="org.acegisecurity.securechannel.SecureChannelProcessor"/>
<bean class="org.acegisecurity.securechannel.InsecureChannelProcessor"/>
</list>
</property>
</bean>
Here we’ve configured ChannelDecisionManagerImpl with two channel processors—one for secure channel (HTTPS) processing and one for insecure (HTTP) channel processing.
Before we move past Spring Security’s support for web-based security, let’s see how to use Spring Security’s tag library to enforce security rules within a page in the web application.
View-layer security
In most applications, there are certain elements that should only be displayed to a certain class of users. As you’ve already seen, Spring Security’s filters prevent certain pages from being presented to users who are not granted a specific set of authorities.
But filters provide a coarse-grained security, limiting access at the request level. In some cases, you may want more fine-grained control over what the user is allowed to see. Maybe all users of an application will be allowed to see a certain page, but only users who are granted special authority may see certain elements on that page.
To provide fine-grained security in web applications, Spring Security comes with a small, but powerful, JSP tag library. This tag library provides only three tags, as listed in table 7.6.
Table 7.6 Spring Security’s JSP tags for view-layer security.
| |
---|---|
<authz:acl> | Conditionally renders the tag body if the user has been granted one of a set of specific permissions to a domain object |
<authz:authentication> | Renders information about the user |
<authz:authorize> | Conditionally renders the tag body if the user has been (or has not been) granted certain authorities |
To use these tags in a JSP page, the tag library must be imported using the JSP
<%@ taglib prefix="authz" uri="https://acegisecurity.org/authz" %>
Let’s have a look at how to apply these tags, starting with <authz:authorize>.
Conditionally rendering content
The most useful of Spring Security’s JSP tags is the <authz:authorize> tag. This tag effectively performs an if statement, evaluating whether or not the current user has been granted proper authority to view certain content. If so, the body of the tag will be rendered. Otherwise, the tag’s content will be ignored.
To illustrate, let’s add a welcome message and a link to logoff from the RoadRantz application. It doesn’t make much sense to welcome a user who isn’t authenticated and even less sense to offer them a logoff link. Therefore, we want to be certain that the user has been granted certain privileges before they’re presented with that information. Using the ifAllGranted attribute of the <authz: authorize> tag, we might add the content to the view using this JSP snippet:
<authz:authorize ifAllGranted="ROLE_MOTORIST,ROLE_VIP">
Welcome Motorist!<br/>
<a href="j_acegi_logout">Logoff</a>
</authz:authorize>
Because the ifAllGranted attribute was used, the content contained in the body of the tag will only be rendered if the motorist has been granted both ROLE_MOTORIST ''and ''ROLE_VIP privileges. However, that is too restrictive, because while all users are granted ROLE_MOTORIST privileges, only a select few are granted ROLE_VIP privileges. So maybe the ifAnyGranted attribute would be more appropriate:
<authz:authorize ifAnyGranted="ROLE_MOTORIST,ROLE_VIP">
Welcome Motorist!<br/>
<a href="j_acegi_logout">Logoff</a>
</authz:authorize>
In this case, the user must be granted either ROLE_MOTORIST ''or ''ROLE_VIP privileges for the welcome message and logoff link to be displayed.
Although it may seem obvious, it is worth pointing out that if you are only checking for a single privilege, the choice between ifAllGranted and ifAnyGranted is moot. Either attribute will work equally well when only one privilege is listed in the attribute value.
The final attribute option you have is ifNotGranted, which only renders the tag’s content if the user has not been granted any of the authorities listed. For example, we’d use this to prevent content from being rendered to anonymous users:
<authz:authorize ifNotGranted="ROLE_ANONYMOUS">
<p>This is super-secret content that anonymous users aren't
allowed to see.</p>
</authz:authorize>
These three attributes cover a lot of ground by themselves. But some real security magic is conjured up when they’re used together. When used together, these attributes are evaluated by logically AND’ing them together. For instance, consider the following:
<authz:authorize ifAllGranted="ROLE_MOTORIST"
ifAnyGranted="ROLE_VIP,ROLE_FAST_LANE"
ifNotGranted="ROLE_ADMIN">
<p>Only special users see this content.</p>
</authz:authorize>
Used together this way, the tag’s content will only be rendered if the user has been granted ROLE_MOTORIST privileges and either ROLE_VIP or ROLE_FAST_LANE privileges, and is not granted ROLE_ADMIN privileges. Even though this is a contrived example, you can imagine how powerful the <authz:authorize> tag can be by combining its three attributes.
Controlling what the user can see is only one facet of Spring Security’s JSP tag library. Now let’s see how to use Spring Security tags to display information about an authenticated user.
Displaying user authentication information
In the previous section, we added a welcome message to the RoadRantz application for authorized users. For simplicity’s sake, the message was “Welcome Motorist!” That’s a good start, but we’d like to make the application more personal by displaying the user’s login name instead of “Motorist.”
Fortunately, the user’s login is typically carried around in the object that is returned from the user’s Authentication.getPrincipal() method. All we need is a convenient way to access the principal object in the JSP. That’s what the <authz:authentication> tag is for.
The <authz:authentication> tag renders properties of the object that is returned from Authentication.getPrincipal() to JSP output. Authentication.getPrincipal() typically returns an implementation of Spring Security’s org.acegisecurity.userdetails.UserDetails interface, which includes a getUsername() method. Therefore, all we need to do to display the username property of the UserDetails object is to add the following <authz:authentication> tag:
<authz:authorize ifAnyGranted="ROLE_MOTORIST,ROLE_VIP">
Welcome <authz:authentication operation="username"/>
</authz:authorize>
The operation attribute is a bit misleading, seeming to indicate that its purpose is to invoke a method. It’s true that it invokes a method, but more specifically, it invokes the getter method of the property whose name is specified in the operation attribute.
By default, the first letter of the operation value is capitalized and the result is
prepended with get to produce the name of the method that will be called. In this case, the getUsername() method is called and its return value is rendered to the JSP output.
Now you’ve seen how to secure web applications using Spring Security’s filters. Before we are done with Spring Security, however, let’s have a quick look at how to secure method invocations using Spring Security and AOP.
Securing method invocations
Whereas Spring Security used servlet filters to secure web requests, Spring Security takes advantage of Spring’s AOP support to provide declarative method-level security. This means that instead of setting up a SecurityEnforcementFilter to enforce security, you’ll set up a Spring AOP proxy that intercepts method invocations and passes control to a security interceptor.
Creating a security aspect
Probably the easiest way to set up an AOP proxy is to use Spring’s BeanNameAutoProxyCreator and simply list the beans that you’ll want secured.5 For instance, suppose that you’d like to secure the courseService and billingService beans:
<bean id="autoProxyCreator" class=
"org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>securityInterceptor</value>
</list>
</property>
<property name="beanNames">
<list>
<value>courseService</value>
<value>billingService</value>
</list>
</property>
</bean>
Here the autoproxy creator has been instructed to proxy its beans with a single interceptor, a bean named securityInterceptor. The securityInterceptor bean is configured as follows:
<bean id="securityInterceptor"
class="org.acegisecurity.intercept.method.MethodSecurityInterceptor">
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="accessDecisionManager">
<ref bean="accessDecisionManager"/>
</property>
<property name="objectDefinitionSource">
<value>
com.springinaction.springtraining.service.
CourseService.createCourse=ROLE_ADMIN
com.springinaction.springtraining.service.
CourseService.enroll*=ROLE_ADMIN,ROLE_REGISTRAR
</value>
</property>
</bean>
This is only a suggestion. If you prefer one of the other mechanisms for proxying beans (as discussed in chapter 4), such as ProxyFactoryBean or DefaultAdvisorAutoProxyCreator, you are welcome to use those here instead.
MethodSecurityInterceptor does for method invocations what FilterSecurityInterceptor does for servlet requests. That is, it intercepts the invocation and coordinates the efforts of the authentication manager and the access decision manager to ensure that method requirements are met.
Notice that the authenticationManager and accessDecisionManager properties are the same as for FilterSecurityInterceptor. In fact, you may wire the same beans into these properties as you did for FilterSecurityInterceptor.
MethodSecurityInterceptor also has an objectDefinitionSource property just as FilterSecurityInterceptor does. But, although it serves the same purpose here as with FilterSecurityInterceptor, it is configured slightly differently. Instead of associating URL patterns with privileges, this property associates method patterns with privileges that are required to invoke the method.
A method pattern (see figure 7.15) includes the fully qualified class name and the method name of the method(s) to be secured. Note that you may use wildcards at either the beginning or the end of a method pattern to match multiple methods.
When a secured method is called, MethodSecurityInterceptor will determine whether the user has been authenticated and has been granted the appropriate authorities to call the method. If so, the call will proceed to the target method. If not, an AcegiSecurityException will be thrown. More specifically, an AuthenticationException will be thrown if the user cannot be authenticated. Or, if the user hasn’t been granted authority to make the call, an AccessDeniedException will be thrown.
In keeping with Spring’s exception philosophy, AcegiSecurityException is an unchecked exception. The calling code can either catch or ignore the exception.
Writing method security attributes in the Spring configuration file is only one way to declare method-level security. Now let’s look at how to use Jakarta Commons Attributes to declare security attributes.
Securing methods using metadata
As with transactions and handler mappings, the first thing you must do is declare a metadata implementation to tell Spring how to load metadata. If you haven’t already added a CommonsAttributes bean to your application context, add one now:
<bean id="attributes"
class="org.springframework.metadata.commons.CommonsAttributes"/>
Next, you must declare an object definition source. In section 7.6.1, you defined an object definition source by setting the objectDefinitionSource property with a String that mapped security attributes to methods. But this time you’re going to declare security attributes directly in the secured object’s source code. Spring Security’s MethodDefinitionAttributes is an object definition source that retrieves its security attributes from the secured object’s metadata:
<bean id="objectDefinitionSource"
class="org.acegisecurity.intercept.method.MethodDefinitionAttributes">
<property name="attributes"><ref bean="attributes"/></property>
</bean>
The attributes property of MethodDefinitionAttributes is wired with a reference to the attributes bean so that it will know to pull security attributes using Jakarta Commons Attributes.6
Now that the objectDefinitionSource is configured, wire it into the objectDefinitionSource property of MethodSecurityInterceptor (replacing the String definition from section 7.6.1):
<bean id="securityInterceptor"
class="org.acegisecurity.intercept.method. MethodSecurityInterceptor">
…
<property name="objectDefinitionSource">
<ref bean="objectDefinitionSource"/>
</property>
</bean>
When Spring supports JSR-175 annotations, you will wire the attributes property with a different metadata implementation.
Now you’re ready to start tagging your code with security attributes. The only security attribute you need to know is SecurityConfig, which associates a privilege with a method. For example, the following snippet of code shows how to tag the enrollStudentInCourse() method from CourseService to require either ROLE_ADMIN or ROLE_REGISTRAR privileges:
/**
* @@org.acegisecurity.SecurityConfig("ROLE_ADMIN")
* @@org.acegisecurity.SecurityConfig("ROLE_REGISTRAR")
*/
public void enrollStudentInCourse(Course course,
Student student) throws CourseException;
Declaring these security attributes on enrollStudentInCourse() is equivalent to the declaration of the objectDefinitionSource as defined in section 7.6.1.
Summary
Security is a very important aspect of many applications. Spring Security provides a mechanism for securing your applications that is based on Spring’s philosophy of loose coupling, dependency injection, and aspect-oriented programming.
You may have noticed that this chapter presented very little Java code. We hope you weren’t disappointed. The lack of Java code illustrates a key strength of Spring Security—loose coupling between an application and its security. Security is an aspect that transcends an application’s core concerns. Using Spring Security, you are able to secure your applications without writing any security code directly into your application code.
Another thing you may have noticed is that much of the configuration required to secure an application with Spring Security is ignorant of the application that it is securing. The only Spring Security component that really needs to know any specifics about the secured application is the object definition source where you associate a secured resource with the authorities required to access the resource. Loose coupling runs both ways between Spring Security and its applications.
------------------------
ТРИО теплый пол отзыв
Заработок на сокращении ссылок
Earnings on reducing links
Код PHP на HTML сайты
Категория: Книги по Java
Комментарии |