123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- <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>
- It is automatically enabled for you if you are using the <link xlink:href="ns-auto-config">auto-config</link> setting.
- 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>
- 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><![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>
|