183 lines
11 KiB
XML
183 lines
11 KiB
XML
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="remember-me"
|
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
<info><title>Remember-Me Authentication</title></info>
|
|
|
|
|
|
<section xml:id="remember-me-overview">
|
|
<info><title>Overview</title></info>
|
|
|
|
<para>Remember-me or persistent-login authentication refers to web sites being able to
|
|
remember the identity of a principal between sessions. This is
|
|
typically accomplished by sending a cookie to the browser, with the
|
|
cookie being detected during future sessions and causing automated
|
|
login to take place. Spring Security provides the necessary hooks for
|
|
these operations to take place, and has two concrete
|
|
remember-me implementations. One uses hashing to preserve the security of
|
|
cookie-based tokens and the other uses a database or other persistent storage
|
|
mechanism to store the generated tokens. </para>
|
|
<para>
|
|
Note that both implemementations require a <interfacename>UserDetailsService</interfacename>.
|
|
If you are using an authentication provider which doesn't use a <interfacename>UserDetailsService</interfacename>
|
|
(for example, the LDAP provider) then it won't work unless you also have a <interfacename>UserDetailsService</interfacename>
|
|
bean in your application context.
|
|
</para>
|
|
</section>
|
|
|
|
<section xml:id="remember-me-hash-token">
|
|
<title>Simple Hash-Based Token Approach</title>
|
|
<para>This approach uses hashing to achieve a useful remember-me strategy.
|
|
In essence a cookie is sent to the browser upon successful interactive authentication, with the
|
|
cookie being composed as follows:
|
|
<programlisting>
|
|
base64(username + ":" + expirationTime + ":" +
|
|
md5Hex(username + ":" + expirationTime + ":" password + ":" + key))
|
|
|
|
username: As identifiable to the <interfacename>UserDetailsService</interfacename>
|
|
password: That matches the one in the retrieved UserDetails
|
|
expirationTime: The date and time when the remember-me token expires,
|
|
expressed in milliseconds
|
|
key: A private key to prevent modification of the remember-me token
|
|
</programlisting></para>
|
|
<para>As such the remember-me token is valid only for the period
|
|
specified, and provided that the username, password and key does not
|
|
change. Notably, this has a potential security issue in that a
|
|
captured remember-me token will be usable from any user agent until
|
|
such time as the token expires. This is the same issue as with digest
|
|
authentication. If a principal is aware a token has been captured,
|
|
they can easily change their password and immediately invalidate all
|
|
remember-me tokens on issue. If more significant security is
|
|
needed you should use the approach described in the next section. Alternatively
|
|
remember-me services should simply not be used at all.</para>
|
|
|
|
<para>If you are familiar with the topics discussed in the chapter on <link xlink:href="ns-config">namespace configuration</link>,
|
|
you can enable remember-me authentication just by adding the <literal><remember-me></literal> element:
|
|
<programlisting><![CDATA[
|
|
<http>
|
|
...
|
|
<remember-me key="myAppKey"/>
|
|
</http>
|
|
]]>
|
|
</programlisting>
|
|
The <interfacename>UserDetailsService</interfacename> will normally be selected automatically. If you have more than one in
|
|
your application context, you need to specify which one should be used with the <literal>user-service-ref</literal> attribute,
|
|
where the value is the name of your <interfacename>UserDetailsService</interfacename> bean.
|
|
</para>
|
|
</section>
|
|
|
|
<section xml:id="remember-me-persistent-token">
|
|
<title>Persistent Token Approach</title>
|
|
<para>This approach is based on the article
|
|
<link xlink:href="http://jaspan.com/improved_persistent_login_cookie_best_practice">http://jaspan.com/improved_persistent_login_cookie_best_practice</link>
|
|
with some minor modifications <footnote><para>Essentially, the username is not included in the cookie, to prevent exposing a valid login
|
|
name unecessarily. There is a discussion on this in the comments section of this article.</para></footnote>.
|
|
To use the this approach with namespace configuration, you would supply a datasource reference:
|
|
<programlisting><![CDATA[
|
|
<http>
|
|
...
|
|
<remember-me data-source-ref="someDataSource"/>
|
|
</http>
|
|
]]>
|
|
</programlisting>
|
|
The database should contain a <literal>persistent_logins</literal> table, created using the following SQL (or equivalent):
|
|
<programlisting>
|
|
create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)
|
|
</programlisting>
|
|
</para>
|
|
<!-- TODO: Add more info on the implementation and behaviour when tokens are stolen etc. Also some info for admins on invalidating tokens using key, or deleting info from db -->
|
|
</section>
|
|
|
|
<section xml:id="remember-me-impls">
|
|
<info><title>Remember-Me Interfaces and Implementations</title></info>
|
|
|
|
<para>Remember-me authentication is not used with basic
|
|
authentication, given it is often not used with
|
|
<literal>HttpSession</literal>s. Remember-me is used with
|
|
<literal>UsernamePasswordAuthenticationProcessingFilter</literal>, and is implemented
|
|
via hooks in the <literal>AbstractAuthenticationProcessingFilter</literal>
|
|
superclass. The hooks will invoke a concrete
|
|
<interfacename>RememberMeServices</interfacename> at the appropriate times. The
|
|
interface looks like this:
|
|
<programlisting language="java">
|
|
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);
|
|
void loginFail(HttpServletRequest request, HttpServletResponse response);
|
|
void loginSuccess(HttpServletRequest request, HttpServletResponse response,
|
|
Authentication successfulAuthentication);
|
|
</programlisting>
|
|
Please refer to the JavaDocs for a fuller discussion on what the
|
|
methods do, although note at this stage that
|
|
<literal>AbstractAuthenticationProcessingFilter</literal> only calls the
|
|
<literal>loginFail()</literal> and <literal>loginSuccess()</literal>
|
|
methods. The <literal>autoLogin()</literal> method is called by
|
|
<classname>RememberMeProcessingFilter</classname> whenever the
|
|
<classname>SecurityContextHolder</classname> does not contain an
|
|
<interfacename>Authentication</interfacename>. This interface therefore provides
|
|
the underlying remember-me implementation with sufficient
|
|
notification of authentication-related events, and delegates to the
|
|
implementation whenever a candidate web request might contain a cookie
|
|
and wish to be remembered. This design allows any number of remember-me implementation
|
|
strategies. We've seen above that Spring Security provides
|
|
two implementations. We'll look at thes in turn.</para>
|
|
|
|
<section>
|
|
<title>TokenBasedRememberMeServices</title>
|
|
<para>
|
|
This implementation supports the simpler approach described in <xref linkend="remember-me-hash-token"/>.
|
|
<classname>TokenBasedRememberMeServices</classname> generates a
|
|
<literal>RememberMeAuthenticationToken</literal>, which is processed
|
|
by <literal>RememberMeAuthenticationProvider</literal>. A
|
|
<literal>key</literal> is shared between this authentication provider
|
|
and the <literal>TokenBasedRememberMeServices</literal>. In addition,
|
|
<literal>TokenBasedRememberMeServices</literal> requires A
|
|
UserDetailsService from which it can retrieve the username and
|
|
password for signature comparison purposes, and generate the
|
|
<literal>RememberMeAuthenticationToken</literal> to contain the
|
|
correct <interfacename>GrantedAuthority</interfacename>[]s. Some sort of logout
|
|
command should be provided by the application that invalidates the cookie if
|
|
the user requests this. <classname>TokenBasedRememberMeServices</classname> also implements Spring Security's
|
|
<interfacename>LogoutHandler</interfacename> interface so can be used with <classname>LogoutFilter</classname>
|
|
to have the cookie cleared automatically.
|
|
</para>
|
|
<para>The beans required in an application context to enable remember-me services are as follows:
|
|
<programlisting language="xml"><![CDATA[
|
|
<bean id="rememberMeProcessingFilter" class=
|
|
"org.springframework.security.web.authentication.rememberme.RememberMeProcessingFilter">
|
|
<property name="rememberMeServices" ref="rememberMeServices"/>
|
|
<property name="authenticationManager" ref="theAuthenticationManager" />
|
|
</bean>
|
|
|
|
<bean id="rememberMeServices" class=
|
|
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
|
|
<property name="userDetailsService" ref="myUserDetailsService"/>
|
|
<property name="key" value="springRocks"/>
|
|
</bean>
|
|
|
|
<bean id="rememberMeAuthenticationProvider" class=
|
|
"org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationProvider">
|
|
<property name="key" value="springRocks"/>
|
|
</bean>
|
|
]]>
|
|
</programlisting>Don't forget to add your
|
|
<interfacename>RememberMeServices</interfacename> implementation to your
|
|
<literal>UsernamePasswordAuthenticationProcessingFilter.setRememberMeServices()</literal>
|
|
property, include the
|
|
<literal>RememberMeAuthenticationProvider</literal> in your
|
|
<literal>AuthenticationManager.setProviders()</literal> list, and add
|
|
<classname>RememberMeProcessingFilter</classname> into your
|
|
<classname>FilterChainProxy</classname> (typically immediately after your
|
|
<literal>UsernamePasswordAuthenticationProcessingFilter</literal>).</para>
|
|
</section>
|
|
<section>
|
|
<title>PersistentTokenBasedRememberMeServices</title>
|
|
<para>
|
|
This class can be used in the same way as <classname>TokenBasedRememberMeServices</classname>, but it additionally
|
|
needs to be configured with a <interfacename>PersistentTokenRepository</interfacename> to store the tokens.
|
|
There are two standard implementations.
|
|
<itemizedlist>
|
|
<listitem><para><classname>InMemoryTokenRepositoryImpl</classname> which is intended for testing only.</para></listitem>
|
|
<listitem><para><classname>JdbcTokenRepositoryImpl</classname> which stores the tokens in a database. </para></listitem>
|
|
</itemizedlist>
|
|
The database schema is described above in <xref linkend="remember-me-persistent-token"/>.
|
|
</para>
|
|
</section>
|
|
</section>
|
|
</chapter> |