Now that we have a high-level overview of the Spring Security architecture and its core
classes, let's take a closer look at one or two of the core interfaces and their
implementations, in particular the AuthenticationManager
,
UserDetailsService
and the
AccessDecisionManager
. These crop up regularly throughout
the remainder of this document so it's important you know how they are configured and how
they operate.
The AuthenticationManager
is just an interface, so the
implementation can be anything we choose, but how does it work in practice? What if we
need to check multiple authentication databases or a combination of different
authentication services such as a database and an LDAP server?
The default implementation in Spring Security is called
ProviderManager
and rather than handling the authentication
request itself, it delegates to a list of configured
AuthenticationProvider
s, each of which is queried in turn to
see if it can perform the authentication. Each provider will either throw an exception
or return a fully populated Authentication
object.
Remember our good friends, UserDetails
and
UserDetailsService
? If not, head back to the previous
chapter and refresh your memory. The most common approach to verifying an authentication
request is to load the corresponding UserDetails
and
check the loaded password against the one that has been entered by the user. This is the
approach used by the DaoAuthenticationProvider
(see below). The
loaded UserDetails
object - and particularly the
GrantedAuthority
s it contains - will be used when building the
fully populated Authentication
object which is returned
from a successful authentication and stored in the
SecurityContext
.
If you are using the namespace, an instance of
ProviderManager
is created and maintained internally, and
you add providers to it by using the namespace authentication provider elements
(see the namespace chapter). In this
case, you should not declare a ProviderManager
bean in your
application context. However, if you are not using the namespace then you would declare
it like so:
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager"> <property name="providers"> <list> <ref local="daoAuthenticationProvider"/> <ref local="anonymousAuthenticationProvider"/> <ref local="ldapAuthenticationProvider"/> </list> </property> </bean>
In the above example we have three providers. They are tried in the order shown (which
is implied by the use of a List
), with each provider able to attempt
authentication, or skip authentication by simply returning null
. If
all implementations return null, the ProviderManager
will throw a
ProviderNotFoundException
. If you're interested in
learning more about chaining providers, please refer to the
ProviderManager
JavaDocs.
Authentication mechanisms such as a web form-login processing filter are injected
with a reference to the ProviderManager
and will call it
to handle their authentication requests. The providers you require will sometimes be
interchangeable with the authentication mechanisms, while at other times they will
depend on a specific authentication mechanism. For example,
DaoAuthenticationProvider
and
LdapAuthenticationProvider
are compatible with any mechanism
which submits a simple username/password authentication request and so will work with
form-based logins or HTTP Basic authentication. On the other hand, some authentication
mechanisms create an authentication request object which can only be interpreted by a
single type of AuthenticationProvider
. An example of this would
be JA-SIG CAS, which uses the notion of a service ticket and so can therefore only be
authenticated by a CasAuthenticationProvider
. You needn't be too
concerned about this, because if you forget to register a suitable provider, you'll
simply receive a ProviderNotFoundException
when an attempt to
authenticate is made.
The simplest AuthenticationProvider
implemented by
Spring Security is DaoAuthenticationProvider
, which is also one
of the earliest supported by the framework. It leverages a
UserDetailsService
(as a DAO) in order to lookup
the username, password and GrantedAuthority
s. It
authenticates the user simply by comparing the password submitted in a
UsernamePasswordAuthenticationToken
against the one
loaded by the UserDetailsService
. Configuring the
provider is quite simple:
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="inMemoryDaoImpl"/> <property name="saltSource" ref="saltSource"/> <property name="passwordEncoder" ref="passwordEncoder"/> </bean>
The PasswordEncoder
and
SaltSource
are optional. A
PasswordEncoder
provides encoding and decoding of
passwords presented in the UserDetails
object that is
returned from the configured UserDetailsService
. A
SaltSource
enables the passwords to be populated
with a "salt", which enhances the security of the passwords in the authentication
repository. These will be discussed in more detail below.
From Spring Security 3.0.3, you can configure the ProviderManager
will attempt to clear any sensitive credentials information from the
Authentication
object which is returned by a successful
authentication request, to prevent information like passwords being retained longer
than necessary. This feature is controlled by the eraseCredentialsAfterAuthentication
property on ProviderManager
. It is off by default.
See the Javadoc for more information.
This may cause issues when you are using a cache of user objects, for example, to
improve performance in a stateless application. If the Authentication
contains a reference to an object in the cache (such as a UserDetails
instance) and this has its credentials removed, then it will no longer be possible to authenticate
against the cached value. You need to take this into account if you are using a cache. An obvious
solution is to make a copy of the object first, either in the cache implementation or in
the AuthenticationProvider
which creates the returned
Authentication
object.
As mentioned in the earlier in this reference guide, most authentication providers
take advantage of the UserDetails
and
UserDetailsService
interfaces. Recall that the
contract for UserDetailsService
is a single
method:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
The returned UserDetails
is an interface that provides
getters that guarantee non-null provision of authentication information such as the
username, password, granted authorities and whether the user account is enabled or
disabled. Most authentication providers will use a
UserDetailsService
, even if the username and password
are not actually used as part of the authentication decision. They may use the returned
UserDetails
object just for its
GrantedAuthority
information, because some other system (like
LDAP or X.509 or CAS etc) has undertaken the responsibility of actually validating the
credentials.
Given UserDetailsService
is so simple to implement, it
should be easy for users to retrieve authentication information using a persistence
strategy of their choice. Having said that, Spring Security does include a couple of
useful base implementations, which we'll look at below.
Is easy to use create a custom UserDetailsService
implementation that extracts information from a persistence engine of choice, but
many applications do not require such complexity. This is particularly true if
you're building a prototype application or just starting integrating Spring
Security, when you don't really want to spend time configuring databases or writing
UserDetailsService
implementations. For this sort
of situation, a simple option is to use the user-service
element
from the security namespace:
<user-service id="userDetailsService"> <user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="bob" password="bobspassword" authorities="ROLE_USER" /> </user-service>
This also supports the use of an external properties file:
<user-service id="userDetailsService" properties="users.properties"/>
The properties file should contain entries in the form
username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]
For example
jimi=jimispassword,ROLE_USER,ROLE_ADMIN,enabled bob=bobspassword,ROLE_USER,enabled
Spring Security also includes a UserDetailsService
that can obtain authentication information from a JDBC data source. Internally
Spring JDBC is used, so it avoids the complexity of a fully-featured object
relational mapper (ORM) just to store user details. If your application does use an
ORM tool, you might prefer to write a custom
UserDetailsService
to reuse the mapping files
you've probably already created. Returning to JdbcDaoImpl
, an
example configuration is shown below:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.hsqldb.jdbcDriver"/> <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/> <property name="username" value="sa"/> <property name="password" value=""/> </bean> <bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean>
You can use different relational database management systems by modifying the
DriverManagerDataSource
shown above. You can also use a
global data source obtained from JNDI, as with any other Spring
configuration.
By default, JdbcDaoImpl
loads the authorities for a
single user with the assumption that the authorities are mapped directly to
users (see the database schema
appendix). An alternative approach is to partition the authorities
into groups and assign groups to the user. Some people prefer this approach as a
means of administering user rights. See the JdbcDaoImpl
Javadoc for more information on how to enable the use of group authorities. The
group schema is also included in the appendix.
Spring Security's PasswordEncoder
interface is used to
support the use of passwords which are encoded in some way in persistent storage. This
will normally mean that the passwords are “hashed” using a digest algorithm
such as MD5 or SHA.
Password hashing is not unique to Spring Security but is a common source of confusion for users who are not familiar with the concept. A hash (or digest) algorithm is a one-way function which produces a piece of fixed-length output data (the hash) from some input data, such as a password. As an example, the MD5 hash of the string “password” (in hexadecimal) is
5f4dcc3b5aa765d61d8327deb882cf99
A hash is “one-way” in the sense that it is very difficult (effectively impossible) to obtain the original input given the hash value, or indeed any possible input which would produce that hash value. This property makes hash values very useful for authentication purposes. They can be stored in your user database as an alternative to plaintext passwords and even if the values are compromised they do not immediately reveal a password which can be used to login. Note that this also means you have no way of recovering the password once it is encoded.
One potential problem with the use of password hashes that it is relatively easy
to get round the one-way property of the hash if a common word is used for the
input. For example, if you search for the hash value
5f4dcc3b5aa765d61d8327deb882cf99
using google, you will
quickly find the original word “password”. In a similar way, an
attacker can build a dictionary of hashes from a standard word list and use this to
lookup the original password. One way to help prevent this is to have a suitably
strong password policy to try to prevent common words from being used. Another is to
use a “salt” when calculating the hashes. This is an additional string
of known data for each user which is combined with the password before calculating
the hash. Ideally the data should be as random as possible, but in practice any salt
value is usually preferable to none. Spring Security has a
SaltSource
interface which can be used by an
authentication provider to generate a salt value for a particular user. Using a salt
means that an attacker has to build a separate dictionary of hashes for each salt
value, making the attack more complicated (but not impossible).
When an authentication provider (such as Spring Security's
DaoAuthenticationProvider
needs to check the password in
a submitted authentication request against the known value for a user, and the
stored password is encoded in some way, then the submitted value must be encoded
using exactly the same algorithm. It's up to you to check that these are compatible
as Spring Security has no control over the persistent values. If you add password
hashing to your authentication configuration in Spring Security, and your database
contains plaintext passwords, then there is no way authentication can succeed. Even
if you are aware that your database is using MD5 to encode the passwords, for
example, and your application is configured to use Spring Security's
Md5PasswordEncoder
, there are still things that can go
wrong. The database may have the passwords encoded in Base 64, for example while the
enocoder is using hexadecimal strings (the default)[6]. Alternatively your database
may be using upper-case while the output from the encoder is lower-case. Make sure
you write a test to check the output from your configured password encoder with a
known password and salt combination and check that it matches the database value
before going further and attempting to authenticate through your application. For
more information on the default method for merging salt and password, see the
Javadoc for BasePasswordEncoder
. If you want to generate
encoded passwords directly in Java for storage in your user database, then you can
use the encodePassword
method on the
PasswordEncoder
.
[6] You can configure
the encoder to use Base 64 instead of hex by setting the
encodeHashAsBase64
property to
true
. Check the Javadoc for
MessageDigestPasswordEncoder
and its parent
classes for more information.