Files
spring-security/docs/manual/src/docbook/core-services.xml
Rob Winch 4761614c9f SEC-2291: Fix internal links within reference
Instead of using xlink:href="# use linkend="
2013-08-28 09:12:27 -05:00

319 lines
24 KiB
XML

<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="core-services"
xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Core Services</title>
<para> 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 <interfacename>AuthenticationManager</interfacename>,
<interfacename>UserDetailsService</interfacename> and the
<interfacename>AccessDecisionManager</interfacename>. These crop up regularly throughout the
remainder of this document so it's important you know how they are configured and how they
operate. </para>
<section xml:id="core-services-authentication-manager">
<title>The <interfacename>AuthenticationManager</interfacename>,
<classname>ProviderManager</classname> and
<classname>AuthenticationProvider</classname>s</title>
<para>The <interfacename>AuthenticationManager</interfacename> 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?</para>
<para>The default implementation in Spring Security is called
<classname>ProviderManager</classname> and rather than handling the authentication
request itself, it delegates to a list of configured
<classname>AuthenticationProvider</classname>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 <interfacename>Authentication</interfacename> object. Remember
our good friends, <interfacename>UserDetails</interfacename> and
<interfacename>UserDetailsService</interfacename>? 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 <interfacename>UserDetails</interfacename> and
check the loaded password against the one that has been entered by the user. This is the
approach used by the <classname>DaoAuthenticationProvider</classname> (see below). The
loaded <interfacename>UserDetails</interfacename> object - and particularly the
<literal>GrantedAuthority</literal>s it contains - will be used when building the fully
populated <interfacename>Authentication</interfacename> object which is returned from a
successful authentication and stored in the <classname>SecurityContext</classname>. </para>
<para> If you are using the namespace, an instance of <classname>ProviderManager</classname>
is created and maintained internally, and you add providers to it by using the namespace
authentication provider elements (see <link linkend="ns-auth-manager">the namespace
chapter</link>). In this case, you should not declare a
<classname>ProviderManager</classname> bean in your application context. However, if you
are not using the namespace then you would declare it like so: <programlisting language="xml"><![CDATA[
<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>]]></programlisting></para>
<para>In the above example we have three providers. They are tried in the order shown (which
is implied by the use of a <literal>List</literal>), with each provider able to attempt
authentication, or skip authentication by simply returning <literal>null</literal>. If
all implementations return null, the <literal>ProviderManager</literal> will throw a
<exceptionname>ProviderNotFoundException</exceptionname>. If you're interested in
learning more about chaining providers, please refer to the
<literal>ProviderManager</literal> JavaDocs.</para>
<para> Authentication mechanisms such as a web form-login processing filter are injected
with a reference to the <interfacename>ProviderManager</interfacename> 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,
<classname>DaoAuthenticationProvider</classname> and
<classname>LdapAuthenticationProvider</classname> 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 <classname>AuthenticationProvider</classname>. 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 <classname>CasAuthenticationProvider</classname>. You needn't be too
concerned about this, because if you forget to register a suitable provider, you'll
simply receive a <literal>ProviderNotFoundException</literal> when an attempt to
authenticate is made.</para>
<section xml:id="core-services-erasing-credentials">
<title>Erasing Credentials on Successful Authentication</title>
<para> By default (from Spring Security 3.1 onwards) the
<classname>ProviderManager</classname> will attempt to clear any sensitive
credentials information from the <interfacename>Authentication</interfacename>
object which is returned by a successful authentication request. This prevents
information like passwords being retained longer than necessary. </para>
<para> This may cause issues when you are using a cache of user objects, for example, to
improve performance in a stateless application. If the
<interfacename>Authentication</interfacename> contains a reference to an object in
the cache (such as a <interfacename>UserDetails</interfacename> 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 <interfacename>AuthenticationProvider</interfacename>
which creates the returned <interfacename>Authentication</interfacename> object.
Alternatively, you can disable the
<literal>eraseCredentialsAfterAuthentication</literal> property on
<classname>ProviderManager</classname>. See the Javadoc for more information.
</para>
</section>
<section xml:id="core-services-dao-provider">
<title><literal>DaoAuthenticationProvider</literal></title>
<para>The simplest <interfacename>AuthenticationProvider</interfacename> implemented by
Spring Security is <literal>DaoAuthenticationProvider</literal>, which is also one
of the earliest supported by the framework. It leverages a
<interfacename>UserDetailsService</interfacename> (as a DAO) in order to lookup the
username, password and <interfacename>GrantedAuthority</interfacename>s. It
authenticates the user simply by comparing the password submitted in a
<classname>UsernamePasswordAuthenticationToken</classname> against the one loaded by
the <interfacename>UserDetailsService</interfacename>. Configuring the provider is
quite simple: <programlisting language="xml"><![CDATA[
<bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
</bean>]]></programlisting> The <interfacename>PasswordEncoder</interfacename> is optional. A
<interfacename>PasswordEncoder</interfacename> provides encoding and decoding of
passwords presented in the <interfacename>UserDetails</interfacename> object that is
returned from the configured <interfacename>UserDetailsService</interfacename>. This
will be discussed in more detail <link linkend="core-services-password-encoding"
>below</link>. </para>
</section>
</section>
<section>
<title><interfacename>UserDetailsService</interfacename> Implementations</title>
<para>As mentioned in the earlier in this reference guide, most authentication providers
take advantage of the <interfacename>UserDetails</interfacename> and
<interfacename>UserDetailsService</interfacename> interfaces. Recall that the contract
for <interfacename>UserDetailsService</interfacename> is a single method:</para>
<para>
<programlisting language="java">
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
</programlisting>
</para>
<para>The returned <interfacename>UserDetails</interfacename> 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
<interfacename>UserDetailsService</interfacename>, even if the username and password are
not actually used as part of the authentication decision. They may use the returned
<interfacename>UserDetails</interfacename> object just for its
<literal>GrantedAuthority</literal> information, because some other system (like LDAP or
X.509 or CAS etc) has undertaken the responsibility of actually validating the
credentials.</para>
<para>Given <interfacename>UserDetailsService</interfacename> 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.</para>
<section xml:id="core-services-in-memory-service">
<title>In-Memory Authentication</title>
<para>Is easy to use create a custom <interfacename>UserDetailsService</interfacename>
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
<interfacename>UserDetailsService</interfacename> implementations. For this sort of
situation, a simple option is to use the <literal>user-service</literal> element
from the security <link linkend="ns-minimal">namespace</link>: <programlisting language="xml"><![CDATA[
<user-service id="userDetailsService">
<user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="bobspassword" authorities="ROLE_USER" />
</user-service>
]]>
</programlisting> This also supports the use of an external properties
file: <programlisting language="xml"><![CDATA[
<user-service id="userDetailsService" properties="users.properties"/>
]]></programlisting> The properties file should contain entries in the form
<programlisting language="txt">username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]</programlisting>
For example
<programlisting language="txt">
jimi=jimispassword,ROLE_USER,ROLE_ADMIN,enabled
bob=bobspassword,ROLE_USER,enabled</programlisting></para>
</section>
<section xml:id="core-services-jdbc-user-service">
<title><literal>JdbcDaoImpl</literal></title>
<para>Spring Security also includes a <interfacename>UserDetailsService</interfacename>
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
<interfacename>UserDetailsService</interfacename> to reuse the mapping files you've
probably already created. Returning to <literal>JdbcDaoImpl</literal>, an example
configuration is shown below:</para>
<para>
<programlisting language="xml"><![CDATA[
<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> ]]> </programlisting>
</para>
<para>You can use different relational database management systems by modifying the
<literal>DriverManagerDataSource</literal> shown above. You can also use a global
data source obtained from JNDI, as with any other Spring configuration.</para>
<section>
<title>Authority Groups</title>
<para>By default, <classname>JdbcDaoImpl</classname> loads the authorities for a
single user with the assumption that the authorities are mapped directly to
users (see the <link linkend="appendix-schema">database schema
appendix</link>). 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 <classname>JdbcDaoImpl</classname>
Javadoc for more information on how to enable the use of group authorities. The
group schema is also included in the appendix.</para>
</section>
<!--
<para>If the default schema is unsuitable for your needs, <literal>JdbcDaoImpl</literal>
provides properties that allow customisation of the SQL statements. Please refer to the
JavaDocs for details, but note that the class is not intended for complex custom
subclasses. If you have a complex schema or would like a custom
<interfacename>UserDetails</interfacename> implementation returned, you'd be better off
writing your own <interfacename>UserDetailsService</interfacename>. The base
implementation provided with Spring Security is intended for typical situations, rather
than catering for all possible requirements.</para>
-->
</section>
</section>
<section xml:id="core-services-password-encoding">
<title>Password Encoding</title>
<para linkend="spring-security-crypto-passwordencoders">Spring Security's
<interfacename>PasswordEncoder</interfacename> interface is used to support the use of
passwords which are encoded in some way in persistent storage. You should never store
passwords in plain text. Always use a one-way password hashing algorithm such as bcrypt
which uses a built-in salt value which is different for each stored password. Do not use
a plain hash function such as MD5 or SHA, or even a salted version. Bcrypt is deliberately
designed to be slow and to hinder offline password cracking, whereas standard hash algorithms
are fast and can easily be used to test thousands of passwords in parallel on custom
hardware. You might think this doesn't apply to you since your password database is
secure and offline attacks aren't a risk. If so, do some research and read up on all
the high-profile sites which have been compromised in this way and have been pilloried
for storing their passwords insecurely. It's best to be on the safe side. Using
<code>org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"</code>
is a good choice for security. There are also compatible implementations in other common
programming languages so it a good choice for interoperability too.</para>
<para>
If you are using a legacy system which already has hashed passwords, then you will
need to use an encoder which matches your current algorithm, at least until you can
migrate your users to a more secure scheme (usually this will involve asking the user
to set a new password, since hashes are irreversible). Spring Security has a package
containing legacy password encoding implementation, namely,
<literal>org.springframework.security.authentication.encoding</literal>.
The <classname>DaoAuthenticationProvider</classname> can be injected
with either the new or legacy <interfacename>PasswordEncoder</interfacename>
types.</para>
<section>
<title>What is a hash?</title>
<para>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 <quote>password</quote> (in hexadecimal) is
<programlisting language="txt">
5f4dcc3b5aa765d61d8327deb882cf99
</programlisting>
A hash is <quote>one-way</quote> 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.</para>
</section>
<section>
<title>Adding Salt to a Hash</title>
<para> 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. People tend to choose similar passwords and huge dictionaries of these from
previously hacked sites are available online. For example, if you search for the hash value
<literal>5f4dcc3b5aa765d61d8327deb882cf99</literal> using google, you will quickly
find the original word <quote>password</quote>. 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
<quote>salt</quote> 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. 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).</para>
<para>Bcrypt automatically generates a random salt value for each password when it
is encoded, and stores it in the bcrypt string in a standard format.
<note><para>The legacy approach to handling salt was to inject a
<interfacename>SaltSource</interfacename> into the
<classname>DaoAuthenticationProvider</classname>, which would obtain a salt
value for a particular user and pass it to the
<interfacename>PasswordEncoder</interfacename>. Using bcrypt means you don't have
worry about the details of salt handling (such as where the the value is stored),
as it is all done internally. So we'd strongly recommend you use bcrypt
unless you already have a system in place which stores the salt separately.</para>
</note></para>
</section>
<section>
<title> Hashing and Authentication</title>
<para>When an authentication provider (such as Spring Security's
<classname>DaoAuthenticationProvider</classname>) 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
<classname>Md5PasswordEncoder</classname>, there are still things that can go wrong.
The database may have the passwords encoded in Base 64, for example while the
encoder is using hexadecimal strings (the default). 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. Using a standard
like bcrypt will avoid these issues.
</para>
<para>If you want to generate encoded passwords directly in Java for storage in your
user database, then you can use the <methodname>encode</methodname> method on the
<interfacename>PasswordEncoder</interfacename>.</para>
</section>
</section>
</chapter>