We have already seen an example of configuring in-memory authentication for a single user. Below is an example to configure multiple users:
@Bean public UserDetailsService userDetailsService() throws Exception { // ensure the passwords are encoded properly UserBuilder users = User.withDefaultPasswordEncoder(); InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(users.username("user").password("password").roles("USER").build()); manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build()); return manager; }
You can find the updates to support JDBC based authentication.
The example below assumes that you have already defined a DataSource
within your application.
The jdbc-javaconfig sample provides a complete example of using JDBC based authentication.
@Autowired private DataSource dataSource; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // ensure the passwords are encoded properly UserBuilder users = User.withDefaultPasswordEncoder(); auth .jdbcAuthentication() .dataSource(dataSource) .withDefaultSchema() .withUser(users.username("user").password("password").roles("USER")) .withUser(users.username("admin").password("password").roles("USER","ADMIN")); }
LDAP is often used by organizations as a central repository for user information and as an authentication service. It can also be used to store the role information for application users.
There are many different scenarios for how an LDAP server may be configured so Spring Security’s LDAP provider is fully configurable. It uses separate strategy interfaces for authentication and role retrieval and provides default implementations which can be configured to handle a wide range of situations.
You should be familiar with LDAP before trying to use it with Spring Security. The following link provides a good introduction to the concepts involved and a guide to setting up a directory using the free LDAP server OpenLDAP: http://www.zytrax.com/books/ldap/. Some familiarity with the JNDI APIs used to access LDAP from Java may also be useful. We don’t use any third-party LDAP libraries (Mozilla, JLDAP etc.) in the LDAP provider, but extensive use is made of Spring LDAP, so some familiarity with that project may be useful if you plan on adding your own customizations.
When using LDAP authentication, it is important to ensure that you configure LDAP connection pooling properly. If you are unfamiliar with how to do this, you can refer to the Java LDAP documentation.
LDAP authentication in Spring Security can be roughly divided into the following stages.
uid=joe,ou=users,dc=spring,dc=io
.
The exception is when the LDAP directory is just being used to retrieve user information and authenticate against it locally. This may not be possible as directories are often set up with limited read access for attributes such as user passwords.
We will look at some configuration scenarios below. For full information on available configuration options, please consult the security namespace schema (information from which should be available in your XML editor).
The first thing you need to do is configure the server against which authentication should take place.
This is done using the <ldap-server>
element from the security namespace.
This can be configured to point at an external LDAP server, using the url
attribute:
<ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />
Note | |
---|---|
|
The <ldap-server>
element can also be used to create an embedded server, which can be very useful for testing and demonstrations.
In this case you use it without the url
attribute:
<ldap-server root="dc=springframework,dc=org"/>
Here we’ve specified that the root DIT of the directory should be "dc=springframework,dc=org", which is the default.
Used this way, the namespace parser will create an embedded Apache Directory server and scan the classpath for any LDIF files, which it will attempt to load into the server.
You can customize this behaviour using the ldif
attribute, which defines an LDIF resource to be loaded:
<ldap-server ldif="classpath:users.ldif" />
This makes it a lot easier to get up and running with LDAP, since it can be inconvenient to work all the time with an external server. It also insulates the user from the complex bean configuration needed to wire up an Apache Directory server. Using plain Spring Beans the configuration would be much more cluttered. You must have the necessary Apache Directory dependency jars available for your application to use. These can be obtained from the LDAP sample application.
This is the most common LDAP authentication scenario.
<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"/>
This simple example would obtain the DN for the user by substituting the user login name in the supplied pattern and attempting to bind as that user with the login password. This is OK if all your users are stored under a single node in the directory. If instead you wished to configure an LDAP search filter to locate the user, you could use the following:
<ldap-authentication-provider user-search-filter="(uid={0})" user-search-base="ou=people"/>
If used with the server definition above, this would perform a search under the DN ou=people,dc=springframework,dc=org
using the value of the user-search-filter
attribute as a filter.
Again the user login name is substituted for the parameter in the filter name, so it will search for an entry with the uid
attribute equal to the user name.
If user-search-base
isn’t supplied, the search will be performed from the root.
How authorities are loaded from groups in the LDAP directory is controlled by the following attributes.
group-search-base
.
Defines the part of the directory tree under which group searches should be performed.
group-role-attribute
.
The attribute which contains the name of the authority defined by the group entry.
Defaults to cn
group-search-filter
.
The filter which is used to search for group membership.
The default is uniqueMember={0}
, corresponding to the groupOfUniqueNames
LDAP class [2].
In this case, the substituted parameter is the full distinguished name of the user.
The parameter {1}
can be used if you want to filter on the login name.
So if we used the following configuration
<ldap-authentication-provider user-dn-pattern="uid={0},ou=people" group-search-base="ou=groups" />
and authenticated successfully as user "ben", the subsequent loading of authorities would perform a search under the directory entry ou=groups,dc=springframework,dc=org
, looking for entries which contain the attribute uniqueMember
with value uid=ben,ou=people,dc=springframework,dc=org
.
By default the authority names will have the prefix ROLE_
prepended.
You can change this using the role-prefix
attribute.
If you don’t want any prefix, use role-prefix="none"
.
For more information on loading authorities, see the Javadoc for the DefaultLdapAuthoritiesPopulator
class.
The namespace configuration options we’ve used above are simple to use and much more concise than using Spring beans explicitly. There are situations when you may need to know how to configure Spring Security LDAP directly in your application context. You may wish to customize the behaviour of some of the classes, for example. If you’re happy using namespace configuration then you can skip this section and the next one.
The main LDAP provider class, LdapAuthenticationProvider
, doesn’t actually do much itself but delegates the work to two other beans, an LdapAuthenticator
and an LdapAuthoritiesPopulator
which are responsible for authenticating the user and retrieving the user’s set of GrantedAuthority
s respectively.
The authenticator is also responsible for retrieving any required user attributes. This is because the permissions on the attributes may depend on the type of authentication being used. For example, if binding as the user, it may be necessary to read them with the user’s own permissions.
There are currently two authentication strategies supplied with Spring Security:
Before it is possible to authenticate a user (by either strategy), the distinguished name (DN) has to be obtained from the login name supplied to the application.
This can be done either by simple pattern-matching (by setting the setUserDnPatterns
array property) or by setting the userSearch
property.
For the DN pattern-matching approach, a standard Java pattern format is used, and the login name will be substituted for the parameter {0}
.
The pattern should be relative to the DN that the configured SpringSecurityContextSource
will bind to (see the section on connecting to the LDAP server for more information on this).
For example, if you are using an LDAP server with the URL ldap://monkeymachine.co.uk/dc=springframework,dc=org
, and have a pattern uid={0},ou=greatapes
, then a login name of "gorilla" will map to a DN uid=gorilla,ou=greatapes,dc=springframework,dc=org
.
Each configured DN pattern will be tried in turn until a match is found.
For information on using a search, see the section on search objects below.
A combination of the two approaches can also be used - the patterns will be checked first and if no matching DN is found, the search will be used.
The class BindAuthenticator
in the package org.springframework.security.ldap.authentication
implements the bind authentication strategy.
It simply attempts to bind as the user.
The beans discussed above have to be able to connect to the server.
They both have to be supplied with a SpringSecurityContextSource
which is an extension of Spring LDAP’s ContextSource
.
Unless you have special requirements, you will usually configure a DefaultSpringSecurityContextSource
bean, which can be configured with the URL of your LDAP server and optionally with the username and password of a "manager" user which will be used by default when binding to the server (instead of binding anonymously).
For more information read the Javadoc for this class and for Spring LDAP’s AbstractContextSource
.
Often a more complicated strategy than simple DN-matching is required to locate a user entry in the directory.
This can be encapsulated in an LdapUserSearch
instance which can be supplied to the authenticator implementations, for example, to allow them to locate a user.
The supplied implementation is FilterBasedLdapUserSearch
.
This bean uses an LDAP filter to match the user object in the directory.
The process is explained in the Javadoc for the corresponding search method on the JDK DirContext class.
As explained there, the search filter can be supplied with parameters.
For this class, the only valid parameter is {0}
which will be replaced with the user’s login name.
After authenticating the user successfully, the LdapAuthenticationProvider
will attempt to load a set of authorities for the user by calling the configured LdapAuthoritiesPopulator
bean.
The DefaultLdapAuthoritiesPopulator
is an implementation which will load the authorities by searching the directory for groups of which the user is a member (typically these will be groupOfNames
or groupOfUniqueNames
entries in the directory).
Consult the Javadoc for this class for more details on how it works.
If you want to use LDAP only for authentication, but load the authorities from a difference source (such as a database) then you can provide your own implementation of this interface and inject that instead.
A typical configuration, using some of the beans we’ve discussed here, might look like this:
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource"> <constructor-arg value="ldap://monkeymachine:389/dc=springframework,dc=org"/> <property name="userDn" value="cn=manager,dc=springframework,dc=org"/> <property name="password" value="password"/> </bean> <bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> <constructor-arg> <bean class="org.springframework.security.ldap.authentication.BindAuthenticator"> <constructor-arg ref="contextSource"/> <property name="userDnPatterns"> <list><value>uid={0},ou=people</value></list> </property> </bean> </constructor-arg> <constructor-arg> <bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator"> <constructor-arg ref="contextSource"/> <constructor-arg value="ou=groups"/> <property name="groupRoleAttribute" value="ou"/> </bean> </constructor-arg> </bean>
This would set up the provider to access an LDAP server with URL ldap://monkeymachine:389/dc=springframework,dc=org
.
Authentication will be performed by attempting to bind with the DN uid=<user-login-name>,ou=people,dc=springframework,dc=org
.
After successful authentication, roles will be assigned to the user by searching under the DN ou=groups,dc=springframework,dc=org
with the default filter (member=<user’s-DN>)
.
The role name will be taken from the "ou" attribute of each match.
To configure a user search object, which uses the filter (uid=<user-login-name>)
for use instead of the DN-pattern (or in addition to it), you would configure the following bean
<bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> <constructor-arg index="0" value=""/> <constructor-arg index="1" value="(uid={0})"/> <constructor-arg index="2" ref="contextSource" /> </bean>
and use it by setting the BindAuthenticator
bean’s userSearch
property.
The authenticator would then call the search object to obtain the correct user’s DN before attempting to bind as this user.
The net result of an authentication using LdapAuthenticationProvider
is the same as a normal Spring Security authentication using the standard UserDetailsService
interface.
A UserDetails
object is created and stored in the returned Authentication
object.
As with using a UserDetailsService
, a common requirement is to be able to customize this implementation and add extra properties.
When using LDAP, these will normally be attributes from the user entry.
The creation of the UserDetails
object is controlled by the provider’s UserDetailsContextMapper
strategy, which is responsible for mapping user objects to and from LDAP context data:
public interface UserDetailsContextMapper { UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<GrantedAuthority> authorities); void mapUserToContext(UserDetails user, DirContextAdapter ctx); }
Only the first method is relevant for authentication.
If you provide an implementation of this interface and inject it into the LdapAuthenticationProvider
, you have control over exactly how the UserDetails object is created.
The first parameter is an instance of Spring LDAP’s DirContextOperations
which gives you access to the LDAP attributes which were loaded during authentication.
The username
parameter is the name used to authenticate and the final parameter is the collection of authorities loaded for the user by the configured LdapAuthoritiesPopulator
.
The way the context data is loaded varies slightly depending on the type of authentication you are using.
With the BindAuthenticator
, the context returned from the bind operation will be used to read the attributes, otherwise the data will be read using the standard context obtained from the configured ContextSource
(when a search is configured to locate the user, this will be the data returned by the search object).
Active Directory supports its own non-standard authentication options, and the normal usage pattern doesn’t fit too cleanly with the standard LdapAuthenticationProvider
.
Typically authentication is performed using the domain username (in the form user@domain
), rather than using an LDAP distinguished name.
To make this easier, Spring Security 3.1 has an authentication provider which is customized for a typical Active Directory setup.
Configuring ActiveDirectoryLdapAuthenticationProvider
is quite straightforward.
You just need to supply the domain name and an LDAP URL supplying the address of the server [3].
An example configuration would then look like this:
<bean id="adAuthenticationProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider"> <constructor-arg value="mydomain.com" /> <constructor-arg value="ldap://adserver.mydomain.com/" /> </bean>
Note that there is no need to specify a separate ContextSource
in order to define the server location - the bean is completely self-contained.
A user named "Sharon", for example, would then be able to authenticate by entering either the username sharon
or the full Active Directory userPrincipalName
, namely [email protected]
.
The user’s directory entry will then be located, and the attributes returned for possible use in customizing the created UserDetails
object (a UserDetailsContextMapper
can be injected for this purpose, as described above).
All interaction with the directory takes place with the identity of the user themselves.
There is no concept of a "manager" user.
By default, the user authorities are obtained from the memberOf
attribute values of the user entry.
The authorities allocated to the user can again be customized using a UserDetailsContextMapper
.
You can also inject a GrantedAuthoritiesMapper
into the provider instance to control the authorities which end up in the Authentication
object.
By default, a failed result will cause a standard Spring Security BadCredentialsException
.
If you set the property convertSubErrorCodesToExceptions
to true
, the exception messages will be parsed to attempt to extract the Active Directory-specific error code and raise a more specific exception.
Check the class Javadoc for more information.
You can find the updates to support LDAP based authentication. The ldap-javaconfig sample provides a complete example of using LDAP based authentication.
@Autowired private DataSource dataSource; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .ldapAuthentication() .userDnPatterns("uid={0},ou=people") .groupSearchBase("ou=groups"); }
The example above uses the following LDIF and an embedded Apache DS LDAP instance.
users.ldif.
dn: ou=groups,dc=springframework,dc=org objectclass: top objectclass: organizationalUnit ou: groups dn: ou=people,dc=springframework,dc=org objectclass: top objectclass: organizationalUnit ou: people dn: uid=admin,ou=people,dc=springframework,dc=org objectclass: top objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson cn: Rod Johnson sn: Johnson uid: admin userPassword: password dn: uid=user,ou=people,dc=springframework,dc=org objectclass: top objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson cn: Dianne Emu sn: Emu uid: user userPassword: password dn: cn=user,ou=groups,dc=springframework,dc=org objectclass: top objectclass: groupOfNames cn: user uniqueMember: uid=admin,ou=people,dc=springframework,dc=org uniqueMember: uid=user,ou=people,dc=springframework,dc=org dn: cn=admin,ou=groups,dc=springframework,dc=org objectclass: top objectclass: groupOfNames cn: admin uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
You can define custom authentication by exposing a custom AuthenticationProvider
as a bean.
For example, the following will customize authentication assuming that SpringAuthenticationProvider
implements AuthenticationProvider
:
Note | |
---|---|
This is only used if the |
@Bean public SpringAuthenticationProvider springAuthenticationProvider() { return new SpringAuthenticationProvider(); }
In practice you will need a more scalable source of user information than a few names added to the application context file.
Most likely you will want to store your user information in something like a database or an LDAP server.
LDAP namespace configuration is dealt with in the LDAP chapter, so we won’t cover it here.
If you have a custom implementation of Spring Security’s UserDetailsService
, called "myUserDetailsService" in your application context, then you can authenticate against this using
<authentication-manager> <authentication-provider user-service-ref='myUserDetailsService'/> </authentication-manager>
If you want to use a database, then you can use
<authentication-manager> <authentication-provider> <jdbc-user-service data-source-ref="securityDataSource"/> </authentication-provider> </authentication-manager>
Where "securityDataSource" is the name of a DataSource
bean in the application context, pointing at a database containing the standard Spring Security user data tables.
Alternatively, you could configure a Spring Security JdbcDaoImpl
bean and point at that using the user-service-ref
attribute:
<authentication-manager> <authentication-provider user-service-ref='myUserDetailsService'/> </authentication-manager> <beans:bean id="myUserDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"> <beans:property name="dataSource" ref="dataSource"/> </beans:bean>
You can also use standard AuthenticationProvider
beans as follows
<authentication-manager> <authentication-provider ref='myAuthenticationProvider'/> </authentication-manager>
where myAuthenticationProvider
is the name of a bean in your application context which implements AuthenticationProvider
.
You can use multiple authentication-provider
elements, in which case the providers will be queried in the order they are declared.
See Section 10.11, “The Authentication Manager and the Namespace” for more information on how the Spring Security AuthenticationManager
is configured using the namespace.
You can define custom authentication by exposing a custom UserDetailsService
as a bean.
For example, the following will customize authentication assuming that SpringDataUserDetailsService
implements UserDetailsService
:
Note | |
---|---|
This is only used if the |
@Bean public SpringDataUserDetailsService springDataUserDetailsService() { return new SpringDataUserDetailsService(); }
You can also customize how passwords are encoded by exposing a PasswordEncoder
as a bean.
For example, if you use bcrypt you can add a bean definition as shown below:
@Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
Spring Security’s PasswordEncoder
interface is used to perform a one way transformation of a password to allow the password to be stored securely.
Given PasswordEncoder
is a one way transformation, it is not intended when the password transformation needs to be two way (i.e. storing credentials used to authenticate to a database).
Typically PasswordEncoder
is used for storing a password that needs to be compared to a user provided password at the time of authentication.
Throughout the years the standard mechanism for storing passwords has evolved. In the beginning passwords were stored in plain text. The passwords were assumed to be safe because the data store the passwords were saved in required credentials to access it. However, malicious users were able to find ways to get large "data dumps" of usernames and passwords using attacks like SQL Injection. As more and more user credentials became public security experts realized we needed to do more to protect users passwords.
Developers were then encouraged to store passwords after running them through a one way hash such as SHA-256. When a user tried to authenticate, the hashed password would be compared to the hash of the password that they typed. This meant that the system only needed to store the one way hash of the password. If a breach occurred, then only the one way hashes of the passwords were exposed. Since the hashes were one way and it was computationally difficult to guess the passwords given the hash, it would not be worth the effort to figure out each password in the system. To defeat this new system malicious users decided to create lookup tables known as Rainbow Tables. Rather than doing the work of guessing each password every time, they computed the password once and stored it in a lookup table.
To mitigate the effectiveness of Rainbow Tables, developers were encouraged to use salted passwords. Instead of using just the password as input to the hash function, random bytes (known as salt) would be generated for every users' password. The salt and the user’s password would be ran through the hash function which produced a unique hash. The salt would be stored alongside the user’s password in clear text. Then when a user tried to authenticate, the hashed password would be compared to the hash of the stored salt and the password that they typed. The unique salt meant that Rainbow Tables were no longer effective because the hash was different for every salt and password combination.
In modern times we realize that cryptographic hashes (like SHA-256) are no longer secure. The reason is that with modern hardware we can perform billions of hash calculations a second. This means that we can crack each password individually with ease.
Developers are now encouraged to leverage adaptive one-way functions to store a password. Validation of passwords with adaptive one-way functions are intentionally resource (i.e. CPU, memory, etc) intensive. An adaptive one-way function allows configuring a "work factor" which can grow as hardware gets better. It is recommended that the "work factor" be tuned to take about 1 second to verify a password on your system. This trade off is to make it difficult for attackers to crack the password, but not so costly it puts excessive burden on your own system. Spring Security has attempted to provide a good starting point for the "work factor", but users are encouraged to customize the "work factor" for their own system since the performance will vary drastically from system to system. Examples of adaptive one-way functions that should be used include bcrypt, PBKDF2, scrypt, and Argon2.
Because adaptive one-way functions are intentionally resource intensive, validating a username and password for every request will degrade performance of an application significantly. There is nothing Spring Security (or any other library) can do to speed up the validation of the password since security is gained by making the validation resource intensive. Users are encouraged to exchange the long term credentials (i.e. username and password) for a short term credential (i.e. session, OAuth Token, etc). The short term credential can be validated quickly without any loss in security.
Prior to Spring Security 5.0 the default PasswordEncoder
was NoOpPasswordEncoder
which required plain text passwords.
Based upon the Password History section you might expect that the default PasswordEncoder
is now something like BCryptPasswordEncoder
.
However, this ignores three real world problems:
Instead Spring Security introduces DelegatingPasswordEncoder
which solves all of the problems by:
You can easily construct an instance of DelegatingPasswordEncoder
using PasswordEncoderFactories
.
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
Alternatively, you may create your own custom instance. For example:
String idForEncode = "bcrypt"; Map encoders = new HashMap<>(); encoders.put(idForEncode, new BCryptPasswordEncoder()); encoders.put("noop", NoOpPasswordEncoder.getInstance()); encoders.put("pbkdf2", new Pbkdf2PasswordEncoder()); encoders.put("scrypt", new SCryptPasswordEncoder()); encoders.put("sha256", new StandardPasswordEncoder()); PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
The general format for a password is:
{id}encodedPassword
Such that id
is an identifier used to look up which PasswordEncoder
should be used and encodedPassword
is the original encoded password for the selected PasswordEncoder
.
The id
must be at the beginning of the password, start with {
and end with }
.
If the id
cannot be found, the id
will be null.
For example, the following might be a list of passwords encoded using different id
.
All of the original passwords are "password".
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG {noop}password {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc {scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= {sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
The first password would have a | |
The second password would have a | |
The third password would have a | |
The fourth password would have a | |
The final password would have a |
Note | |
---|---|
Some users might be concerned that the storage format is provided for a potential hacker.
This is not a concern because the storage of the password does not rely on the algorithm being a secret.
Additionally, most formats are easy for an attacker to figure out without the prefix.
For example, BCrypt passwords often start with |
The idForEncode
passed into the constructor determines which PasswordEncoder
will be used for encoding passwords.
In the DelegatingPasswordEncoder
we constructed above, that means that the result of encoding password
would be delegated to BCryptPasswordEncoder
and be prefixed with {bcrypt}
.
The end result would look like:
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
Matching is done based upon the {id}
and the mapping of the id
to the PasswordEncoder
provided in the constructor.
Our example in the section called “Password Storage Format” provides a working example of how this is done.
By default, the result of invoking matches(CharSequence, String)
with a password and an id
that is not mapped (including a null id) will result in an IllegalArgumentException
.
This behavior can be customized using DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)
.
By using the id
we can match on any password encoding, but encode passwords using the most modern password encoding.
This is important, because unlike encryption, password hashes are designed so that there is no simple way to recover the plaintext.
Since there is no way to recover the plaintext, it makes it difficult to migrate the passwords.
While it is simple for users to migrate NoOpPasswordEncoder
, we chose to include it by default to make it simple for the getting started experience.
If you are putting together a demo or a sample, it is a bit cumbersome to take time to hash the passwords of your users. There are convenience mechanisms to make this easier, but this is still not intended for production.
User user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("user") .build(); System.out.println(user.getPassword()); // {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
If you are creating multiple users, you can also reuse the builder.
UserBuilder users = User.withDefaultPasswordEncoder(); User user = users .username("user") .password("password") .roles("USER") .build(); User admin = users .username("admin") .password("password") .roles("USER","ADMIN") .build();
This does hash the password that is stored, but the passwords are still exposed in memory and in the compiled source code. Therefore, it is still not considered secure for a production environment. For production, you should hash your passwords externally.
The following error occurs when one of the passwords that are stored has no id as described in the section called “Password Storage Format”.
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233) at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)
The easiest way to resolve the error is to switch to explicitly provide the PasswordEncoder
that you passwords are encoded with.
The easiest way to resolve it is to figure out how your passwords are currently being stored and explicitly provide the correct PasswordEncoder
.
If you are migrating from Spring Security 4.2.x you can revert to the previous behavior by exposing a NoOpPasswordEncoder
bean.
For example, if you are using Java Configuration, you can create a configuration that looks like:
Warning | |
---|---|
Reverting to |
@Bean public static NoOpPasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); }
if you are using XML configuration, you can expose a PasswordEncoder
with the id passwordEncoder
:
<b:bean id="passwordEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
Alternatively, you can prefix all of your passwords with the correct id and continue to use DelegatingPasswordEncoder
.
For example, if you are using BCrypt, you would migrate your password from something like:
$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
to
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
For a complete listing of the mappings refer to the Javadoc on PasswordEncoderFactories.
The BCryptPasswordEncoder
implementation uses the widely supported bcrypt algorithm to hash the passwords.
In order to make it more resistent to password cracking, bcrypt is deliberately slow.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
// Create an encoder with strength 16 BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16); String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result));
The Argon2PasswordEncoder
implementation uses the Argon2 algorithm to hash the passwords.
Argon2 is the winner of the Password Hashing Competition.
In order to defeat password cracking on custom hardware, Argon2 is a deliberately slow algorithm that requires large amounts of memory.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
The current implementation if the Argon2PasswordEncoder
requires BouncyCastle.
// Create an encoder with all the defaults Argon2PasswordEncoder encoder = new Argon2PasswordEncoder(); String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result));
The Pbkdf2PasswordEncoder
implementation uses the PBKDF2 algorithm to hash the passwords.
In order to defeat password cracking PBKDF2 is a deliberately slow algorithm.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
This algorithm is a good choice when FIPS certification is required.
// Create an encoder with all the defaults Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder(); String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result));
The SCryptPasswordEncoder
implementation uses scrypt algorithm to hash the passwords.
In order to defeat password cracking on custom hardware scrypt is a deliberately slow algorithm that requires large amounts of memory.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
// Create an encoder with all the defaults SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(); String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result));
There are a significant number of other PasswordEncoder
implementations that exist entirely for backward compatibility.
They are all deprecated to indicate that they are no longer considered secure.
However, there are no plans to remove them since it is difficult to migrate existing legacy systems.
Passwords should always be encoded using a secure hashing algorithm designed for the purpose (not a standard algorithm like SHA or MD5).
This is supported by the <password-encoder>
element.
With bcrypt encoded passwords, the original authentication provider configuration would look like this:
<beans:bean name="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/> <authentication-manager> <authentication-provider> <password-encoder ref="bcryptEncoder"/> <user-service> <user name="jimi" password="$2a$10$ddEWZUl8aU0GdZPPpy7wbu82dvEw/pBpbRvDQRqA41y6mK1CoH00m" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="bob" password="$2a$10$/elFpMBnAYYig6KRR5bvOOYeZr1ie1hSogJryg9qDlhza4oCw1Qka" authorities="ROLE_USER" /> </user-service> </authentication-provider> </authentication-manager>
bcrypt is a good choice for most cases, unless you have a legacy system which forces you to use a different algorithm. If you are using a simple hashing algorithm or, even worse, storing plain text passwords, then you should consider migrating to a more secure option like bcrypt.
The main interface which provides authentication services in Spring Security is the AuthenticationManager
.
This is usually an instance of Spring Security’s ProviderManager
class, which you may already be familiar with if you’ve used the framework before.
If not, it will be covered later, in the technical overview chapter.
The bean instance is registered using the authentication-manager
namespace element.
You can’t use a custom AuthenticationManager
if you are using either HTTP or method security through the namespace, but this should not be a problem as you have full control over the AuthenticationProvider
s that are used.
You may want to register additional AuthenticationProvider
beans with the ProviderManager
and you can do this using the <authentication-provider>
element with the ref
attribute, where the value of the attribute is the name of the provider bean you want to add.
For example:
<authentication-manager> <authentication-provider ref="casAuthenticationProvider"/> </authentication-manager> <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> ... </bean>
Another common requirement is that another bean in the context may require a reference to the AuthenticationManager
.
You can easily register an alias for the AuthenticationManager
and use this name elsewhere in your application context.
<security:authentication-manager alias="authenticationManager"> ... </security:authentication-manager> <bean id="customizedFormLoginFilter" class="com.somecompany.security.web.CustomFormLoginFilter"> <property name="authenticationManager" ref="authenticationManager"/> ... </bean>
HTTP session related functionality is handled by a combination of the SessionManagementFilter
and the SessionAuthenticationStrategy
interface, which the filter delegates to.
Typical usage includes session-fixation protection attack prevention, detection of session timeouts and restrictions on how many sessions an authenticated user may have open concurrently.
You can configure Spring Security to detect the submission of an invalid session ID and redirect the user to an appropriate URL.
This is achieved through the session-management
element:
<http> ... <session-management invalid-session-url="/invalidSession.htm" /> </http>
Note that if you use this mechanism to detect session timeouts, it may falsely report an error if the user logs out and then logs back in without closing the browser. This is because the session cookie is not cleared when you invalidate the session and will be resubmitted even if the user has logged out. You may be able to explicitly delete the JSESSIONID cookie on logging out, for example by using the following syntax in the logout handler:
<http> <logout delete-cookies="JSESSIONID" /> </http>
Unfortunately this can’t be guaranteed to work with every servlet container, so you will need to test it in your environment
Note | |
---|---|
===
If you are running your application behind a proxy, you may also be able to remove the session cookie by configuring the proxy server.
For example, using Apache HTTPD’s mod_headers, the following directive would delete the |
<LocationMatch "/tutorial/logout"> Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT" </LocationMatch>
===
If you wish to place constraints on a single user’s ability to log in to your application, Spring Security supports this out of the box with the following simple additions.
First you need to add the following listener to your web.xml
file to keep Spring Security updated about session lifecycle events:
<listener> <listener-class> org.springframework.security.web.session.HttpSessionEventPublisher </listener-class> </listener>
Then add the following lines to your application context:
<http> ... <session-management> <concurrency-control max-sessions="1" /> </session-management> </http>
This will prevent a user from logging in multiple times - a second login will cause the first to be invalidated. Often you would prefer to prevent a second login, in which case you can use
<http> ... <session-management> <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" /> </session-management> </http>
The second login will then be rejected.
By "rejected", we mean that the user will be sent to the authentication-failure-url
if form-based login is being used.
If the second authentication takes place through another non-interactive mechanism, such as "remember-me", an "unauthorized" (401) error will be sent to the client.
If instead you want to use an error page, you can add the attribute session-authentication-error-url
to the session-management
element.
If you are using a customized authentication filter for form-based login, then you have to configure concurrent session control support explicitly. More details can be found in the Session Management chapter.
Session fixation attacks are a potential risk where it is possible for a malicious attacker to create a session by accessing a site, then persuade another user to log in with the same session (by sending them a link containing the session identifier as a parameter, for example).
Spring Security protects against this automatically by creating a new session or otherwise changing the session ID when a user logs in.
If you don’t require this protection, or it conflicts with some other requirement, you can control the behavior using the session-fixation-protection
attribute on <session-management>
, which has four options
none
- Don’t do anything.
The original session will be retained.
newSession
- Create a new "clean" session, without copying the existing session data (Spring Security-related attributes will still be copied).
migrateSession
- Create a new session and copy all existing session attributes to the new session.
This is the default in Servlet 3.0 or older containers.
changeSessionId
- Do not create a new session.
Instead, use the session fixation protection provided by the Servlet container (HttpServletRequest#changeSessionId()
).
This option is only available in Servlet 3.1 (Java EE 7) and newer containers.
Specifying it in older containers will result in an exception.
This is the default in Servlet 3.1 and newer containers.
When session fixation protection occurs, it results in a SessionFixationProtectionEvent
being published in the application context.
If you use changeSessionId
, this protection will also result in any javax.servlet.http.HttpSessionIdListener
s being notified, so use caution if your code listens for both events.
See the Session Management chapter for additional information.
The SessionManagementFilter
checks the contents of the SecurityContextRepository
against the current contents of the SecurityContextHolder
to determine whether a user has been authenticated during the current request, typically by a non-interactive authentication mechanism, such as pre-authentication or remember-me [4].
If the repository contains a security context, the filter does nothing.
If it doesn’t, and the thread-local SecurityContext
contains a (non-anonymous) Authentication
object, the filter assumes they have been authenticated by a previous filter in the stack.
It will then invoke the configured SessionAuthenticationStrategy
.
If the user is not currently authenticated, the filter will check whether an invalid session ID has been requested (because of a timeout, for example) and will invoke the configured InvalidSessionStrategy
, if one is set.
The most common behaviour is just to redirect to a fixed URL and this is encapsulated in the standard implementation SimpleRedirectInvalidSessionStrategy
.
The latter is also used when configuring an invalid session URL through the namespace,as described earlier.
SessionAuthenticationStrategy
is used by both SessionManagementFilter
and AbstractAuthenticationProcessingFilter
, so if you are using a customized form-login class, for example, you will need to inject it into both of these.
In this case, a typical configuration, combining the namespace and custom beans might look like this:
<http> <custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" /> <session-management session-authentication-strategy-ref="sas"/> </http> <beans:bean id="myAuthFilter" class= "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> <beans:property name="sessionAuthenticationStrategy" ref="sas" /> ... </beans:bean> <beans:bean id="sas" class= "org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
Note that the use of the default, SessionFixationProtectionStrategy
may cause issues if you are storing beans in the session which implement HttpSessionBindingListener
, including Spring session-scoped beans.
See the Javadoc for this class for more information.
Spring Security is able to prevent a principal from concurrently authenticating to the same application more than a specified number of times. Many ISVs take advantage of this to enforce licensing, whilst network administrators like this feature because it helps prevent people from sharing login names. You can, for example, stop user "Batman" from logging onto the web application from two different sessions. You can either expire their previous login or you can report an error when they try to log in again, preventing the second login. Note that if you are using the second approach, a user who has not explicitly logged out (but who has just closed their browser, for example) will not be able to log in again until their original session expires.
Concurrency control is supported by the namespace, so please check the earlier namespace chapter for the simplest configuration. Sometimes you need to customize things though.
The implementation uses a specialized version of SessionAuthenticationStrategy
, called ConcurrentSessionControlAuthenticationStrategy
.
Note | |
---|---|
Previously the concurrent authentication check was made by the |
To use concurrent session support, you’ll need to add the following to web.xml
:
<listener> <listener-class> org.springframework.security.web.session.HttpSessionEventPublisher </listener-class> </listener>
In addition, you will need to add the ConcurrentSessionFilter
to your FilterChainProxy
.
The ConcurrentSessionFilter
requires two constructor arguments, sessionRegistry
, which generally points to an instance of SessionRegistryImpl
, and sessionInformationExpiredStrategy
, which defines the strategy to apply when a session has expired.
A configuration using the namespace to create the FilterChainProxy
and other default beans might look like this:
<http> <custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" /> <custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" /> <session-management session-authentication-strategy-ref="sas"/> </http> <beans:bean id="redirectSessionInformationExpiredStrategy" class="org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy"> <beans:constructor-arg name="invalidSessionUrl" value="/session-expired.htm" /> </beans:bean> <beans:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter"> <beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" /> <beans:constructor-arg name="sessionInformationExpiredStrategy" ref="redirectSessionInformationExpiredStrategy" /> </beans:bean> <beans:bean id="myAuthFilter" class= "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> <beans:property name="sessionAuthenticationStrategy" ref="sas" /> <beans:property name="authenticationManager" ref="authenticationManager" /> </beans:bean> <beans:bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy"> <beans:constructor-arg> <beans:list> <beans:bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy"> <beans:constructor-arg ref="sessionRegistry"/> <beans:property name="maximumSessions" value="1" /> <beans:property name="exceptionIfMaximumExceeded" value="true" /> </beans:bean> <beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy"> </beans:bean> <beans:bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy"> <beans:constructor-arg ref="sessionRegistry"/> </beans:bean> </beans:list> </beans:constructor-arg> </beans:bean> <beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
Adding the listener to web.xml
causes an ApplicationEvent
to be published to the Spring ApplicationContext
every time a HttpSession
commences or terminates.
This is critical, as it allows the SessionRegistryImpl
to be notified when a session ends.
Without it, a user will never be able to log back in again once they have exceeded their session allowance, even if they log out of another session or it times out.
Setting up concurrency-control, either through the namespace or using plain beans has the useful side effect of providing you with a reference to the SessionRegistry
which you can use directly within your application, so even if you don’t want to restrict the number of sessions a user may have, it may be worth setting up the infrastructure anyway.
You can set the maximumSession
property to -1 to allow unlimited sessions.
If you’re using the namespace, you can set an alias for the internally-created SessionRegistry
using the session-registry-alias
attribute, providing a reference which you can inject into your own beans.
The getAllPrincipals()
method supplies you with a list of the currently authenticated users.
You can list a user’s sessions by calling the getAllSessions(Object principal, boolean includeExpiredSessions)
method, which returns a list of SessionInformation
objects.
You can also expire a user’s session by calling expireNow()
on a SessionInformation
instance.
When the user returns to the application, they will be prevented from proceeding.
You may find these methods useful in an administration application, for example.
Have a look at the Javadoc for more information.
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.
Note that both implementations require a UserDetailsService
.
If you are using an authentication provider which doesn’t use a UserDetailsService
(for example, the LDAP provider) then it won’t work unless you also have a UserDetailsService
bean in your application context.
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:
base64(username + ":" + expirationTime + ":" + md5Hex(username + ":" + expirationTime + ":" password + ":" + key)) username: As identifiable to the UserDetailsService 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
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.
If you are familiar with the topics discussed in the chapter on namespace configuration, you can enable remember-me authentication just by adding the <remember-me>
element:
<http> ... <remember-me key="myAppKey"/> </http>
The UserDetailsService
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 user-service-ref
attribute, where the value is the name of your UserDetailsService
bean.
This approach is based on the article http://jaspan.com/improved_persistent_login_cookie_best_practice with some minor modifications [5]. To use the this approach with namespace configuration, you would supply a datasource reference:
<http> ... <remember-me data-source-ref="someDataSource"/> </http>
The database should contain a persistent_logins
table, created using the following SQL (or equivalent):
create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)
Remember-me is used with UsernamePasswordAuthenticationFilter
, and is implemented via hooks in the AbstractAuthenticationProcessingFilter
superclass.
It is also used within BasicAuthenticationFilter
.
The hooks will invoke a concrete RememberMeServices
at the appropriate times.
The interface looks like this:
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response); void loginFail(HttpServletRequest request, HttpServletResponse response); void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication);
Please refer to the Javadoc for a fuller discussion on what the methods do, although note at this stage that AbstractAuthenticationProcessingFilter
only calls the loginFail()
and loginSuccess()
methods.
The autoLogin()
method is called by RememberMeAuthenticationFilter
whenever the SecurityContextHolder
does not contain an Authentication
.
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 these in turn.
This implementation supports the simpler approach described in Section 10.13.2, “Simple Hash-Based Token Approach”.
TokenBasedRememberMeServices
generates a RememberMeAuthenticationToken
, which is processed by RememberMeAuthenticationProvider
.
A key
is shared between this authentication provider and the TokenBasedRememberMeServices
.
In addition, TokenBasedRememberMeServices
requires A UserDetailsService from which it can retrieve the username and password for signature comparison purposes, and generate the RememberMeAuthenticationToken
to contain the correct GrantedAuthority
s.
Some sort of logout command should be provided by the application that invalidates the cookie if the user requests this.
TokenBasedRememberMeServices
also implements Spring Security’s LogoutHandler
interface so can be used with LogoutFilter
to have the cookie cleared automatically.
The beans required in an application context to enable remember-me services are as follows:
<bean id="rememberMeFilter" class= "org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter"> <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.authentication.RememberMeAuthenticationProvider"> <property name="key" value="springRocks"/> </bean>
Don’t forget to add your RememberMeServices
implementation to your UsernamePasswordAuthenticationFilter.setRememberMeServices()
property, include the RememberMeAuthenticationProvider
in your AuthenticationManager.setProviders()
list, and add RememberMeAuthenticationFilter
into your FilterChainProxy
(typically immediately after your UsernamePasswordAuthenticationFilter
).
This class can be used in the same way as TokenBasedRememberMeServices
, but it additionally needs to be configured with a PersistentTokenRepository
to store the tokens.
There are two standard implementations.
InMemoryTokenRepositoryImpl
which is intended for testing only.
JdbcTokenRepositoryImpl
which stores the tokens in a database.
The database schema is described above in Section 10.13.3, “Persistent Token Approach”.
The namespace supports OpenID login either instead of, or in addition to normal form-based login, with a simple change:
<http> <intercept-url pattern="/**" access="ROLE_USER" /> <openid-login /> </http>
You should then register yourself with an OpenID provider (such as myopenid.com), and add the user information to your in-memory <user-service>
:
<user name="https://jimi.hendrix.myopenid.com/" authorities="ROLE_USER" />
You should be able to login using the myopenid.com
site to authenticate.
It is also possible to select a specific UserDetailsService
bean for use OpenID by setting the user-service-ref
attribute on the openid-login
element.
See the previous section on authentication providers for more information.
Note that we have omitted the password attribute from the above user configuration, since this set of user data is only being used to load the authorities for the user.
A random password will be generated internally, preventing you from accidentally using this user data as an authentication source elsewhere in your configuration.
Support for OpenID attribute exchange. As an example, the following configuration would attempt to retrieve the email and full name from the OpenID provider, for use by the application:
<openid-login> <attribute-exchange> <openid-attribute name="email" type="https://axschema.org/contact/email" required="true"/> <openid-attribute name="name" type="https://axschema.org/namePerson"/> </attribute-exchange> </openid-login>
The "type" of each OpenID attribute is a URI, determined by a particular schema, in this case https://axschema.org/.
If an attribute must be retrieved for successful authentication, the required
attribute can be set.
The exact schema and attributes supported will depend on your OpenID provider.
The attribute values are returned as part of the authentication process and can be accessed afterwards using the following code:
OpenIDAuthenticationToken token = (OpenIDAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); List<OpenIDAttribute> attributes = token.getAttributes();
The OpenIDAttribute
contains the attribute type and the retrieved value (or values in the case of multi-valued attributes).
We’ll see more about how the SecurityContextHolder
class is used when we look at core Spring Security components in the technical overview chapter.
Multiple attribute exchange configurations are also be supported, if you wish to use multiple identity providers.
You can supply multiple attribute-exchange
elements, using an identifier-matcher
attribute on each.
This contains a regular expression which will be matched against the OpenID identifier supplied by the user.
See the OpenID sample application in the codebase for an example configuration, providing different attribute lists for the Google, Yahoo and MyOpenID providers.
It’s generally considered good security practice to adopt a "deny-by-default" where you explicitly specify what is allowed and disallow everything else.
Defining what is accessible to unauthenticated users is a similar situation, particularly for web applications.
Many sites require that users must be authenticated for anything other than a few URLs (for example the home and login pages).
In this case it is easiest to define access configuration attributes for these specific URLs rather than have for every secured resource.
Put differently, sometimes it is nice to say ROLE_SOMETHING
is required by default and only allow certain exceptions to this rule, such as for login, logout and home pages of an application.
You could also omit these pages from the filter chain entirely, thus bypassing the access control checks, but this may be undesirable for other reasons, particularly if the pages behave differently for authenticated users.
This is what we mean by anonymous authentication.
Note that there is no real conceptual difference between a user who is "anonymously authenticated" and an unauthenticated user.
Spring Security’s anonymous authentication just gives you a more convenient way to configure your access-control attributes.
Calls to servlet API calls such as getCallerPrincipal
, for example, will still return null even though there is actually an anonymous authentication object in the SecurityContextHolder
.
There are other situations where anonymous authentication is useful, such as when an auditing interceptor queries the SecurityContextHolder
to identify which principal was responsible for a given operation.
Classes can be authored more robustly if they know the SecurityContextHolder
always contains an Authentication
object, and never null
.
Anonymous authentication support is provided automatically when using the HTTP configuration Spring Security 3.0 and can be customized (or disabled) using the <anonymous>
element.
You don’t need to configure the beans described here unless you are using traditional bean configuration.
Three classes that together provide the anonymous authentication feature.
AnonymousAuthenticationToken
is an implementation of Authentication
, and stores the GrantedAuthority
s which apply to the anonymous principal.
There is a corresponding AnonymousAuthenticationProvider
, which is chained into the ProviderManager
so that AnonymousAuthenticationToken
s are accepted.
Finally, there is an AnonymousAuthenticationFilter
, which is chained after the normal authentication mechanisms and automatically adds an AnonymousAuthenticationToken
to the SecurityContextHolder
if there is no existing Authentication
held there.
The definition of the filter and authentication provider appears as follows:
<bean id="anonymousAuthFilter" class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter"> <property name="key" value="foobar"/> <property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/> </bean> <bean id="anonymousAuthenticationProvider" class="org.springframework.security.authentication.AnonymousAuthenticationProvider"> <property name="key" value="foobar"/> </bean>
The key
is shared between the filter and authentication provider, so that tokens created by the former are accepted by the latter [6].
The userAttribute
is expressed in the form of usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority]
.
This is the same syntax as used after the equals sign for the userMap
property of InMemoryDaoImpl
.
As explained earlier, the benefit of anonymous authentication is that all URI patterns can have security applied to them. For example:
<bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/> <property name="securityMetadata"> <security:filter-security-metadata-source> <security:intercept-url pattern='/index.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/> <security:intercept-url pattern='/hello.htm' access='ROLE_ANONYMOUS,ROLE_USER'/> <security:intercept-url pattern='/logoff.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/> <security:intercept-url pattern='/login.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/> <security:intercept-url pattern='/**' access='ROLE_USER'/> </security:filter-security-metadata-source>" + </property> </bean>
Rounding out the anonymous authentication discussion is the AuthenticationTrustResolver
interface, with its corresponding AuthenticationTrustResolverImpl
implementation.
This interface provides an isAnonymous(Authentication)
method, which allows interested classes to take into account this special type of authentication status.
The ExceptionTranslationFilter
uses this interface in processing AccessDeniedException
s.
If an AccessDeniedException
is thrown, and the authentication is of an anonymous type, instead of throwing a 403 (forbidden) response, the filter will instead commence the AuthenticationEntryPoint
so the principal can authenticate properly.
This is a necessary distinction, otherwise principals would always be deemed "authenticated" and never be given an opportunity to login via form, basic, digest or some other normal authentication mechanism.
You will often see the ROLE_ANONYMOUS
attribute in the above interceptor configuration replaced with IS_AUTHENTICATED_ANONYMOUSLY
, which is effectively the same thing when defining access controls.
This is an example of the use of the AuthenticatedVoter
which we will see in the authorization chapter.
It uses an AuthenticationTrustResolver
to process this particular configuration attribute and grant access to anonymous users.
The AuthenticatedVoter
approach is more powerful, since it allows you to differentiate between anonymous, remember-me and fully-authenticated users.
If you don’t need this functionality though, then you can stick with ROLE_ANONYMOUS
, which will be processed by Spring Security’s standard RoleVoter
.
Spring MVC resolves parameters of type Principal
using its own argument resolver.
This means that a construct like this one:
@GetMapping("/") public String method(Authentication authentication) { if (authentication instanceof AnonymousAuthenticationToken) { return "anonymous"; } else { return "not anonymous"; } }
will always return "not anonymous", even for anonymous requests.
The reason is that Spring MVC resolves the parameter using HttpServletRequest#getPrincipal
, which is null
when the request is anonymous.
If you’d like to obtain the Authentication
in anonymous requests, use @CurrentSecurityContext
instead:
@GetMapping("/") public String method(@CurrentSecurityContext SecurityContext context) { return context.getAuthentication().getName(); }
There are situations where you want to use Spring Security for authorization, but the user has already been reliably authenticated by some external system prior to accessing the application. We refer to these situations as "pre-authenticated" scenarios. Examples include X.509, Siteminder and authentication by the Java EE container in which the application is running. When using pre-authentication, Spring Security has to
The details will depend on the external authentication mechanism.
A user might be identified by their certificate information in the case of X.509, or by an HTTP request header in the case of Siteminder.
If relying on container authentication, the user will be identified by calling the getUserPrincipal()
method on the incoming HTTP request.
In some cases, the external mechanism may supply role/authority information for the user but in others the authorities must be obtained from a separate source, such as a UserDetailsService
.
Because most pre-authentication mechanisms follow the same pattern, Spring Security has a set of classes which provide an internal framework for implementing pre-authenticated authentication providers.
This removes duplication and allows new implementations to be added in a structured fashion, without having to write everything from scratch.
You don’t need to know about these classes if you want to use something like X.509 authentication, as it already has a namespace configuration option which is simpler to use and get started with.
If you need to use explicit bean configuration or are planning on writing your own implementation then an understanding of how the provided implementations work will be useful.
You will find classes under the org.springframework.security.web.authentication.preauth
.
We just provide an outline here so you should consult the Javadoc and source where appropriate.
This class will check the current contents of the security context and, if empty, it will attempt to extract user information from the HTTP request and submit it to the AuthenticationManager
.
Subclasses override the following methods to obtain this information:
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request); protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
After calling these, the filter will create a PreAuthenticatedAuthenticationToken
containing the returned data and submit it for authentication.
By "authentication" here, we really just mean further processing to perhaps load the user’s authorities, but the standard Spring Security authentication architecture is followed.
Like other Spring Security authentication filters, the pre-authentication filter has an authenticationDetailsSource
property which by default will create a WebAuthenticationDetails
object to store additional information such as the session-identifier and originating IP address in the details
property of the Authentication
object.
In cases where user role information can be obtained from the pre-authentication mechanism, the data is also stored in this property, with the details implementing the GrantedAuthoritiesContainer
interface.
This enables the authentication provider to read the authorities which were externally allocated to the user.
We’ll look at a concrete example next.
If the filter is configured with an authenticationDetailsSource
which is an instance of this class, the authority information is obtained by calling the isUserInRole(String role)
method for each of a pre-determined set of "mappable roles".
The class gets these from a configured MappableAttributesRetriever
.
Possible implementations include hard-coding a list in the application context and reading the role information from the <security-role>
information in a web.xml
file.
The pre-authentication sample application uses the latter approach.
There is an additional stage where the roles (or attributes) are mapped to Spring Security GrantedAuthority
objects using a configured Attributes2GrantedAuthoritiesMapper
.
The default will just add the usual ROLE_
prefix to the names, but it gives you full control over the behaviour.
The pre-authenticated provider has little more to do than load the UserDetails
object for the user.
It does this by delegating to an AuthenticationUserDetailsService
.
The latter is similar to the standard UserDetailsService
but takes an Authentication
object rather than just user name:
public interface AuthenticationUserDetailsService { UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException; }
This interface may have also other uses but with pre-authentication it allows access to the authorities which were packaged in the Authentication
object, as we saw in the previous section.
The PreAuthenticatedGrantedAuthoritiesUserDetailsService
class does this.
Alternatively, it may delegate to a standard UserDetailsService
via the UserDetailsByNameServiceWrapper
implementation.
The AuthenticationEntryPoint
was discussed in the technical overview chapter.
Normally it is responsible for kick-starting the authentication process for an unauthenticated user (when they try to access a protected resource), but in the pre-authenticated case this doesn’t apply.
You would only configure the ExceptionTranslationFilter
with an instance of this class if you aren’t using pre-authentication in combination with other authentication mechanisms.
It will be called if the user is rejected by the AbstractPreAuthenticatedProcessingFilter
resulting in a null authentication.
It always returns a 403
-forbidden response code if called.
X.509 authentication is covered in its own chapter. Here we’ll look at some classes which provide support for other pre-authenticated scenarios.
An external authentication system may supply information to the application by setting specific headers on the HTTP request.
A well-known example of this is Siteminder, which passes the username in a header called SM_USER
.
This mechanism is supported by the class RequestHeaderAuthenticationFilter
which simply extracts the username from the header.
It defaults to using the name SM_USER
as the header name.
See the Javadoc for more details.
Tip | |
---|---|
Note that when using a system like this, the framework performs no authentication checks at all and it is extremely important that the external system is configured properly and protects all access to the application. If an attacker is able to forge the headers in their original request without this being detected then they could potentially choose any username they wished. |
A typical configuration using this filter would look like this:
<security:http> <!-- Additional http configuration omitted --> <security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" /> </security:http> <bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter"> <property name="principalRequestHeader" value="SM_USER"/> <property name="authenticationManager" ref="authenticationManager" /> </bean> <bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"> <property name="preAuthenticatedUserDetailsService"> <bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <property name="userDetailsService" ref="userDetailsService"/> </bean> </property> </bean> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="preauthAuthProvider" /> </security:authentication-manager>
We’ve assumed here that the security namespace is being used for configuration.
It’s also assumed that you have added a UserDetailsService
(called "userDetailsService") to your configuration to load the user’s roles.
The class J2eePreAuthenticatedProcessingFilter
will extract the username from the userPrincipal
property of the HttpServletRequest
.
Use of this filter would usually be combined with the use of Java EE roles as described above in the section called “J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource”.
There is a sample application in the codebase which uses this approach, so get hold of the code from github and have a look at the application context file if you are interested.
The code is in the samples/xml/preauth
directory.
Spring Security provides a package able to delegate authentication requests to the Java Authentication and Authorization Service (JAAS). This package is discussed in detail below.
The AbstractJaasAuthenticationProvider
is the basis for the provided JAAS AuthenticationProvider
implementations.
Subclasses must implement a method that creates the LoginContext
.
The AbstractJaasAuthenticationProvider
has a number of dependencies that can be injected into it that are discussed below.
Most JAAS LoginModule
s require a callback of some sort.
These callbacks are usually used to obtain the username and password from the user.
In a Spring Security deployment, Spring Security is responsible for this user interaction (via the authentication mechanism).
Thus, by the time the authentication request is delegated through to JAAS, Spring Security’s authentication mechanism will already have fully-populated an Authentication
object containing all the information required by the JAAS LoginModule
.
Therefore, the JAAS package for Spring Security provides two default callback handlers, JaasNameCallbackHandler
and JaasPasswordCallbackHandler
.
Each of these callback handlers implement JaasAuthenticationCallbackHandler
.
In most cases these callback handlers can simply be used without understanding the internal mechanics.
For those needing full control over the callback behavior, internally AbstractJaasAuthenticationProvider
wraps these JaasAuthenticationCallbackHandler
s with an InternalCallbackHandler
.
The InternalCallbackHandler
is the class that actually implements JAAS normal CallbackHandler
interface.
Any time that the JAAS LoginModule
is used, it is passed a list of application context configured InternalCallbackHandler
s.
If the LoginModule
requests a callback against the InternalCallbackHandler
s, the callback is in-turn passed to the JaasAuthenticationCallbackHandler
s being wrapped.
JAAS works with principals.
Even "roles" are represented as principals in JAAS.
Spring Security, on the other hand, works with Authentication
objects.
Each Authentication
object contains a single principal, and multiple GrantedAuthority
s.
To facilitate mapping between these different concepts, Spring Security’s JAAS package includes an AuthorityGranter
interface.
An AuthorityGranter
is responsible for inspecting a JAAS principal and returning a set of String
s, representing the authorities assigned to the principal.
For each returned authority string, the AbstractJaasAuthenticationProvider
creates a JaasGrantedAuthority
(which implements Spring Security’s GrantedAuthority
interface) containing the authority string and the JAAS principal that the AuthorityGranter
was passed.
The AbstractJaasAuthenticationProvider
obtains the JAAS principals by firstly successfully authenticating the user’s credentials using the JAAS LoginModule
, and then accessing the LoginContext
it returns.
A call to LoginContext.getSubject().getPrincipals()
is made, with each resulting principal passed to each AuthorityGranter
defined against the AbstractJaasAuthenticationProvider.setAuthorityGranters(List)
property.
Spring Security does not include any production AuthorityGranter
s given that every JAAS principal has an implementation-specific meaning.
However, there is a TestAuthorityGranter
in the unit tests that demonstrates a simple AuthorityGranter
implementation.
The DefaultJaasAuthenticationProvider
allows a JAAS Configuration
object to be injected into it as a dependency.
It then creates a LoginContext
using the injected JAAS Configuration
.
This means that DefaultJaasAuthenticationProvider
is not bound any particular implementation of Configuration
as JaasAuthenticationProvider
is.
In order to make it easy to inject a Configuration
into DefaultJaasAuthenticationProvider
, a default in-memory implementation named InMemoryConfiguration
is provided.
The implementation constructor accepts a Map
where each key represents a login configuration name and the value represents an Array
of AppConfigurationEntry
s.
InMemoryConfiguration
also supports a default Array
of AppConfigurationEntry
objects that will be used if no mapping is found within the provided Map
.
For details, refer to the class level javadoc of InMemoryConfiguration
.
While the Spring configuration for InMemoryConfiguration
can be more verbose than the standarad JAAS configuration files, using it in conjuction with DefaultJaasAuthenticationProvider
is more flexible than JaasAuthenticationProvider
since it not dependant on the default Configuration
implementation.
An example configuration of DefaultJaasAuthenticationProvider
using InMemoryConfiguration
is provided below.
Note that custom implementations of Configuration
can easily be injected into DefaultJaasAuthenticationProvider
as well.
<bean id="jaasAuthProvider" class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider"> <property name="configuration"> <bean class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration"> <constructor-arg> <map> <!-- SPRINGSECURITY is the default loginContextName for AbstractJaasAuthenticationProvider --> <entry key="SPRINGSECURITY"> <array> <bean class="javax.security.auth.login.AppConfigurationEntry"> <constructor-arg value="sample.SampleLoginModule" /> <constructor-arg> <util:constant static-field= "javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED"/> </constructor-arg> <constructor-arg> <map></map> </constructor-arg> </bean> </array> </entry> </map> </constructor-arg> </bean> </property> <property name="authorityGranters"> <list> <!-- You will need to write your own implementation of AuthorityGranter --> <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/> </list> </property> </bean>
The JaasAuthenticationProvider
assumes the default Configuration
is an instance of ConfigFile.
This assumption is made in order to attempt to update the Configuration
.
The JaasAuthenticationProvider
then uses the default Configuration
to create the LoginContext
.
Let’s assume we have a JAAS login configuration file, /WEB-INF/login.conf
, with the following contents:
JAASTest { sample.SampleLoginModule required; };
Like all Spring Security beans, the JaasAuthenticationProvider
is configured via the application context.
The following definitions would correspond to the above JAAS login configuration file:
<bean id="jaasAuthenticationProvider" class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider"> <property name="loginConfig" value="/WEB-INF/login.conf"/> <property name="loginContextName" value="JAASTest"/> <property name="callbackHandlers"> <list> <bean class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler"/> <bean class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler"/> </list> </property> <property name="authorityGranters"> <list> <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/> </list> </property> </bean>
If configured, the JaasApiIntegrationFilter
will attempt to run as the Subject
on the JaasAuthenticationToken
.
This means that the Subject
can be accessed using:
Subject subject = Subject.getSubject(AccessController.getContext());
This integration can easily be configured using the jaas-api-provision attribute. This feature is useful when integrating with legacy or external API’s that rely on the JAAS Subject being populated.
JA-SIG produces an enterprise-wide single sign on system known as CAS. Unlike other initiatives, JA-SIG’s Central Authentication Service is open source, widely used, simple to understand, platform independent, and supports proxy capabilities. Spring Security fully supports CAS, and provides an easy migration path from single-application deployments of Spring Security through to multiple-application deployments secured by an enterprise-wide CAS server.
You can learn more about CAS at https://www.apereo.org. You will also need to visit this site to download the CAS Server files.
Whilst the CAS web site contains documents that detail the architecture of CAS, we present the general overview again here within the context of Spring Security. Spring Security 3.x supports CAS 3. At the time of writing, the CAS server was at version 3.4.
Somewhere in your enterprise you will need to setup a CAS server. The CAS server is simply a standard WAR file, so there isn’t anything difficult about setting up your server. Inside the WAR file you will customise the login and other single sign on pages displayed to users.
When deploying a CAS 3.4 server, you will also need to specify an AuthenticationHandler
in the deployerConfigContext.xml
included with CAS.
The AuthenticationHandler
has a simple method that returns a boolean as to whether a given set of Credentials is valid.
Your AuthenticationHandler
implementation will need to link into some type of backend authentication repository, such as an LDAP server or database.
CAS itself includes numerous AuthenticationHandler
s out of the box to assist with this.
When you download and deploy the server war file, it is set up to successfully authenticate users who enter a password matching their username, which is useful for testing.
Apart from the CAS server itself, the other key players are of course the secure web applications deployed throughout your enterprise. These web applications are known as "services". There are three types of services. Those that authenticate service tickets, those that can obtain proxy tickets, and those that authenticate proxy tickets. Authenticating a proxy ticket differs because the list of proxies must be validated and often times a proxy ticket can be reused.
The basic interaction between a web browser, CAS server and a Spring Security-secured service is as follows:
ExceptionTranslationFilter
will detect the AccessDeniedException
or AuthenticationException
.
Authentication
object (or lack thereof) caused an AuthenticationException
, the ExceptionTranslationFilter
will call the configured AuthenticationEntryPoint
.
If using CAS, this will be the CasAuthenticationEntryPoint
class.
CasAuthenticationEntryPoint
will redirect the user’s browser to the CAS server.
It will also indicate a service
parameter, which is the callback URL for the Spring Security service (your application).
For example, the URL to which the browser is redirected might be https://my.company.com/cas/login?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas.
PasswordHandler
(or AuthenticationHandler
if using CAS 3.0) discussed above to decide whether the username and password is valid.
ticket
parameter, which is an opaque string representing the "service ticket".
Continuing our earlier example, the URL the browser is redirected to might be https://server3.company.com/webapp/login/cas?ticket=ST-0-ER94xMJmn6pha35CQRoZ.
CasAuthenticationFilter
is always listening for requests to /login/cas
(this is configurable, but we’ll use the defaults in this introduction).
The processing filter will construct a UsernamePasswordAuthenticationToken
representing the service ticket.
The principal will be equal to CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER
, whilst the credentials will be the service ticket opaque value.
This authentication request will then be handed to the configured AuthenticationManager
.
AuthenticationManager
implementation will be the ProviderManager
, which is in turn configured with the CasAuthenticationProvider
.
The CasAuthenticationProvider
only responds to UsernamePasswordAuthenticationToken
s containing the CAS-specific principal (such as CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER
) and CasAuthenticationToken
s (discussed later).
CasAuthenticationProvider
will validate the service ticket using a TicketValidator
implementation.
This will typically be a Cas20ServiceTicketValidator
which is one of the classes included in the CAS client library.
In the event the application needs to validate proxy tickets, the Cas20ProxyTicketValidator
is used.
The TicketValidator
makes an HTTPS request to the CAS server in order to validate the service ticket.
It may also include a proxy callback URL, which is included in this example: https://my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas&ticket=ST-0-ER94xMJmn6pha35CQRoZ&pgtUrl=https://server3.company.com/webapp/login/cas/proxyreceptor.
pgtUrl
parameter), CAS will include a pgtIou
string in the XML response.
This pgtIou
represents a proxy-granting ticket IOU.
The CAS server will then create its own HTTPS connection back to the pgtUrl
.
This is to mutually authenticate the CAS server and the claimed service URL.
The HTTPS connection will be used to send a proxy granting ticket to the original web application.
For example, https://server3.company.com/webapp/login/cas/proxyreceptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKH.
Cas20TicketValidator
will parse the XML received from the CAS server.
It will return to the CasAuthenticationProvider
a TicketResponse
, which includes the username (mandatory), proxy list (if any were involved), and proxy-granting ticket IOU (if the proxy callback was requested).
CasAuthenticationProvider
will call a configured CasProxyDecider
.
The CasProxyDecider
indicates whether the proxy list in the TicketResponse
is acceptable to the service.
Several implementations are provided with Spring Security: RejectProxyTickets
, AcceptAnyCasProxy
and NamedCasProxyDecider
.
These names are largely self-explanatory, except NamedCasProxyDecider
which allows a List
of trusted proxies to be provided.
CasAuthenticationProvider
will next request a AuthenticationUserDetailsService
to load the GrantedAuthority
objects that apply to the user contained in the Assertion
.
CasAuthenticationProvider
constructs a CasAuthenticationToken
including the details contained in the TicketResponse
and the GrantedAuthority
s.
CasAuthenticationFilter
, which places the created CasAuthenticationToken
in the security context.
AuthenticationException
(or a custom destination depending on the configuration).
It’s good that you’re still here! Let’s now look at how this is configured
The web application side of CAS is made easy due to Spring Security. It is assumed you already know the basics of using Spring Security, so these are not covered again below. We’ll assume a namespace based configuration is being used and add in the CAS beans as required. Each section builds upon the previous section. A fullCAS sample application can be found in the Spring Security Samples.
This section describes how to setup Spring Security to authenticate Service Tickets.
Often times this is all a web application requires.
You will need to add a ServiceProperties
bean to your application context.
This represents your CAS service:
<bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> <property name="service" value="https://localhost:8443/cas-sample/login/cas"/> <property name="sendRenew" value="false"/> </bean>
The service
must equal a URL that will be monitored by the CasAuthenticationFilter
.
The sendRenew
defaults to false, but should be set to true if your application is particularly sensitive.
What this parameter does is tell the CAS login service that a single sign on login is unacceptable.
Instead, the user will need to re-enter their username and password in order to gain access to the service.
The following beans should be configured to commence the CAS authentication process (assuming you’re using a namespace configuration):
<security:http entry-point-ref="casEntryPoint"> ... <security:custom-filter position="CAS_FILTER" ref="casFilter" /> </security:http> <bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> </bean> <bean id="casEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> <property name="loginUrl" value="https://localhost:9443/cas/login"/> <property name="serviceProperties" ref="serviceProperties"/> </bean>
For CAS to operate, the ExceptionTranslationFilter
must have its authenticationEntryPoint
property set to the CasAuthenticationEntryPoint
bean.
This can easily be done using entry-point-ref as is done in the example above.
The CasAuthenticationEntryPoint
must refer to the ServiceProperties
bean (discussed above), which provides the URL to the enterprise’s CAS login server.
This is where the user’s browser will be redirected.
The CasAuthenticationFilter
has very similar properties to the UsernamePasswordAuthenticationFilter
(used for form-based logins).
You can use these properties to customize things like behavior for authentication success and failure.
Next you need to add a CasAuthenticationProvider
and its collaborators:
<security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="casAuthenticationProvider" /> </security:authentication-manager> <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> <property name="authenticationUserDetailsService"> <bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <constructor-arg ref="userService" /> </bean> </property> <property name="serviceProperties" ref="serviceProperties" /> <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> <constructor-arg index="0" value="https://localhost:9443/cas" /> </bean> </property> <property name="key" value="an_id_for_this_auth_provider_only"/> </bean> <security:user-service id="userService"> <!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that NoOpPasswordEncoder should be used. This is not safe for production, but makes reading in samples easier. Normally passwords should be hashed using BCrypt --> <security:user name="joe" password="{noop}joe" authorities="ROLE_USER" /> ... </security:user-service>
The CasAuthenticationProvider
uses a UserDetailsService
instance to load the authorities for a user, once they have been authenticated by CAS.
We’ve shown a simple in-memory setup here.
Note that the CasAuthenticationProvider
does not actually use the password for authentication, but it does use the authorities.
The beans are all reasonably self-explanatory if you refer back to the How CAS Works section.
This completes the most basic configuration for CAS. If you haven’t made any mistakes, your web application should happily work within the framework of CAS single sign on. No other parts of Spring Security need to be concerned about the fact CAS handled authentication. In the following sections we will discuss some (optional) more advanced configurations.
The CAS protocol supports Single Logout and can be easily added to your Spring Security configuration. Below are updates to the Spring Security configuration that handle Single Logout
<security:http entry-point-ref="casEntryPoint"> ... <security:logout logout-success-url="/cas-logout.jsp"/> <security:custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/> <security:custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/> </security:http> <!-- This filter handles a Single Logout Request from the CAS Server --> <bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/> <!-- This filter redirects to the CAS Server to signal Single Logout should be performed --> <bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"> <constructor-arg value="https://localhost:9443/cas/logout"/> <constructor-arg> <bean class= "org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/> </constructor-arg> <property name="filterProcessesUrl" value="/logout/cas"/> </bean>
The logout
element logs the user out of the local application, but does not terminate the session with the CAS server or any other applications that have been logged into.
The requestSingleLogoutFilter
filter will allow the URL of /spring_security_cas_logout
to be requested to redirect the application to the configured CAS Server logout URL.
Then the CAS Server will send a Single Logout request to all the services that were signed into.
The singleLogoutFilter
handles the Single Logout request by looking up the HttpSession
in a static Map
and then invalidating it.
It might be confusing why both the logout
element and the singleLogoutFilter
are needed.
It is considered best practice to logout locally first since the SingleSignOutFilter
just stores the HttpSession
in a static Map
in order to call invalidate on it.
With the configuration above, the flow of logout would be:
/logout
which would log the user out of the local application and send the user to the logout success page.
/cas-logout.jsp
, should instruct the user to click a link pointing to /logout/cas
in order to logout out of all applications.
SingleSignOutFilter
processes the logout request by invaliditing the original session.
The next step is to add the following to your web.xml
<filter> <filter-name>characterEncodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class> org.jasig.cas.client.session.SingleSignOutHttpSessionListener </listener-class> </listener>
When using the SingleSignOutFilter you might encounter some encoding issues.
Therefore it is recommended to add the CharacterEncodingFilter
to ensure that the character encoding is correct when using the SingleSignOutFilter
.
Again, refer to JASIG’s documentation for details.
The SingleSignOutHttpSessionListener
ensures that when an HttpSession
expires, the mapping used for single logout is removed.
This section describes how to authenticate to a service using CAS. In other words, this section discusses how to setup a client that uses a service that authenticates with CAS. The next section describes how to setup a stateless service to Authenticate using CAS.
In order to authenticate to a stateless service, the application needs to obtain a proxy granting ticket (PGT). This section describes how to configure Spring Security to obtain a PGT building upon thencas-st[Service Ticket Authentication] configuration.
The first step is to include a ProxyGrantingTicketStorage
in your Spring Security configuration.
This is used to store PGT’s that are obtained by the CasAuthenticationFilter
so that they can be used to obtain proxy tickets.
An example configuration is shown below
<!-- NOTE: In a real application you should not use an in memory implementation. You will also want to ensure to clean up expired tickets by calling ProxyGrantingTicketStorage.cleanup() --> <bean id="pgtStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>
The next step is to update the CasAuthenticationProvider
to be able to obtain proxy tickets.
To do this replace the Cas20ServiceTicketValidator
with a Cas20ProxyTicketValidator
.
The proxyCallbackUrl
should be set to a URL that the application will receive PGT’s at.
Last, the configuration should also reference the ProxyGrantingTicketStorage
so it can use a PGT to obtain proxy tickets.
You can find an example of the configuration changes that should be made below.
<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> ... <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator"> <constructor-arg value="https://localhost:9443/cas"/> <property name="proxyCallbackUrl" value="https://localhost:8443/cas-sample/login/cas/proxyreceptor"/> <property name="proxyGrantingTicketStorage" ref="pgtStorage"/> </bean> </property> </bean>
The last step is to update the CasAuthenticationFilter
to accept PGT and to store them in the ProxyGrantingTicketStorage
.
It is important the proxyReceptorUrl
matches the proxyCallbackUrl
of the Cas20ProxyTicketValidator
.
An example configuration is shown below.
<bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> ... <property name="proxyGrantingTicketStorage" ref="pgtStorage"/> <property name="proxyReceptorUrl" value="/login/cas/proxyreceptor"/> </bean>
Now that Spring Security obtains PGTs, you can use them to create proxy tickets which can be used to authenticate to a stateless service.
The CAS sample application contains a working example in the ProxyTicketSampleServlet
.
Example code can be found below:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // NOTE: The CasAuthenticationToken can also be obtained using // SecurityContextHolder.getContext().getAuthentication() final CasAuthenticationToken token = (CasAuthenticationToken) request.getUserPrincipal(); // proxyTicket could be reused to make calls to the CAS service even if the // target url differs final String proxyTicket = token.getAssertion().getPrincipal().getProxyTicketFor(targetUrl); // Make a remote call using the proxy ticket final String serviceUrl = targetUrl+"?ticket="+URLEncoder.encode(proxyTicket, "UTF-8"); String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8"); ... }
The CasAuthenticationProvider
distinguishes between stateful and stateless clients.
A stateful client is considered any that submits to the filterProcessUrl
of the CasAuthenticationFilter
.
A stateless client is any that presents an authentication request to CasAuthenticationFilter
on a URL other than the filterProcessUrl
.
Because remoting protocols have no way of presenting themselves within the context of an HttpSession
, it isn’t possible to rely on the default practice of storing the security context in the session between requests.
Furthermore, because the CAS server invalidates a ticket after it has been validated by the TicketValidator
, presenting the same proxy ticket on subsequent requests will not work.
One obvious option is to not use CAS at all for remoting protocol clients.
However, this would eliminate many of the desirable features of CAS.
As a middle-ground, the CasAuthenticationProvider
uses a StatelessTicketCache
.
This is used solely for stateless clients which use a principal equal to CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER
.
What happens is the CasAuthenticationProvider
will store the resulting CasAuthenticationToken
in the StatelessTicketCache
, keyed on the proxy ticket.
Accordingly, remoting protocol clients can present the same proxy ticket and the CasAuthenticationProvider
will not need to contact the CAS server for validation (aside from the first request).
Once authenticated, the proxy ticket could be used for URLs other than the original target service.
This section builds upon the previous sections to accommodate proxy ticket authentication. The first step is to specify to authenticate all artifacts as shown below.
<bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> ... <property name="authenticateAllArtifacts" value="true"/> </bean>
The next step is to specify serviceProperties
and the authenticationDetailsSource
for the CasAuthenticationFilter
.
The serviceProperties
property instructs the CasAuthenticationFilter
to attempt to authenticate all artifacts instead of only ones present on the filterProcessUrl
.
The ServiceAuthenticationDetailsSource
creates a ServiceAuthenticationDetails
that ensures the current URL, based upon the HttpServletRequest
, is used as the service URL when validating the ticket.
The method for generating the service URL can be customized by injecting a custom AuthenticationDetailsSource
that returns a custom ServiceAuthenticationDetails
.
<bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> ... <property name="serviceProperties" ref="serviceProperties"/> <property name="authenticationDetailsSource"> <bean class= "org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource"> <constructor-arg ref="serviceProperties"/> </bean> </property> </bean>
You will also need to update the CasAuthenticationProvider
to handle proxy tickets.
To do this replace the Cas20ServiceTicketValidator
with a Cas20ProxyTicketValidator
.
You will need to configure the statelessTicketCache
and which proxies you want to accept.
You can find an example of the updates required to accept all proxies below.
<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> ... <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator"> <constructor-arg value="https://localhost:9443/cas"/> <property name="acceptAnyProxy" value="true"/> </bean> </property> <property name="statelessTicketCache"> <bean class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache"> <property name="cache"> <bean class="net.sf.ehcache.Cache" init-method="initialise" destroy-method="dispose"> <constructor-arg value="casTickets"/> <constructor-arg value="50"/> <constructor-arg value="true"/> <constructor-arg value="false"/> <constructor-arg value="3600"/> <constructor-arg value="900"/> </bean> </property> </bean> </property> </bean>
The most common use of X.509 certificate authentication is in verifying the identity of a server when using SSL, most commonly when using HTTPS from a browser. The browser will automatically check that the certificate presented by a server has been issued (ie digitally signed) by one of a list of trusted certificate authorities which it maintains.
You can also use SSL with "mutual authentication"; the server will then request a valid certificate from the client as part of the SSL handshake. The server will authenticate the client by checking that its certificate is signed by an acceptable authority. If a valid certificate has been provided, it can be obtained through the servlet API in an application. Spring Security X.509 module extracts the certificate using a filter. It maps the certificate to an application user and loads that user’s set of granted authorities for use with the standard Spring Security infrastructure.
You should be familiar with using certificates and setting up client authentication for your servlet container before attempting to use it with Spring Security. Most of the work is in creating and installing suitable certificates and keys. For example, if you’re using Tomcat then read the instructions here https://tomcat.apache.org/tomcat-9.0-doc/ssl-howto.html. It’s important that you get this working before trying it out with Spring Security
Enabling X.509 client authentication is very straightforward.
Just add the <x509/>
element to your http security namespace configuration.
<http> ... <x509 subject-principal-regex="CN=(.*?)," user-service-ref="userService"/>; </http>
The element has two optional attributes:
subject-principal-regex
.
The regular expression used to extract a username from the certificate’s subject name.
The default value is shown above.
This is the username which will be passed to the UserDetailsService
to load the authorities for the user.
user-service-ref
.
This is the bean Id of the UserDetailsService
to be used with X.509.
It isn’t needed if there is only one defined in your application context.
The subject-principal-regex
should contain a single group.
For example the default expression "CN=(.*?)," matches the common name field.
So if the subject name in the certificate is "CN=Jimi Hendrix, OU=…", this will give a user name of "Jimi Hendrix".
The matches are case insensitive.
So "emailAddress=(.*?)," will match "EMAILADDRESS=[email protected],CN=…" giving a user name "[email protected]".
If the client presents a certificate and a valid username is successfully extracted, then there should be a valid Authentication
object in the security context.
If no certificate is found, or no corresponding user could be found then the security context will remain empty.
This means that you can easily use X.509 authentication with other options such as a form-based login.
There are some pre-generated certificates in the samples/certificate
directory in the Spring Security project.
You can use these to enable SSL for testing if you don’t want to generate your own.
The file server.jks
contains the server certificate, private key and the issuing certificate authority certificate.
There are also some client certificate files for the users from the sample applications.
You can install these in your browser to enable SSL client authentication.
To run tomcat with SSL support, drop the server.jks
file into the tomcat conf
directory and add the following connector to the server.xml
file
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" keystoreFile="${catalina.home}/conf/server.jks" keystoreType="JKS" keystorePass="password" truststoreFile="${catalina.home}/conf/server.jks" truststoreType="JKS" truststorePass="password" />
clientAuth
can also be set to want
if you still want SSL connections to succeed even if the client doesn’t provide a certificate.
Clients which don’t present a certificate won’t be able to access any objects secured by Spring Security unless you use a non-X.509 authentication mechanism, such as form authentication.
The AbstractSecurityInterceptor
is able to temporarily replace the Authentication
object in the SecurityContext
and SecurityContextHolder
during the secure object callback phase.
This only occurs if the original Authentication
object was successfully processed by the AuthenticationManager
and AccessDecisionManager
.
The RunAsManager
will indicate the replacement Authentication
object, if any, that should be used during the SecurityInterceptorCallback
.
By temporarily replacing the Authentication
object during the secure object callback phase, the secured invocation will be able to call other objects which require different authentication and authorization credentials.
It will also be able to perform any internal security checks for specific GrantedAuthority
objects.
Because Spring Security provides a number of helper classes that automatically configure remoting protocols based on the contents of the SecurityContextHolder
, these run-as replacements are particularly useful when calling remote web services
A RunAsManager
interface is provided by Spring Security:
Authentication buildRunAs(Authentication authentication, Object object, List<ConfigAttribute> config); boolean supports(ConfigAttribute attribute); boolean supports(Class clazz);
The first method returns the Authentication
object that should replace the existing Authentication
object for the duration of the method invocation.
If the method returns null
, it indicates no replacement should be made.
The second method is used by the AbstractSecurityInterceptor
as part of its startup validation of configuration attributes.
The supports(Class)
method is called by a security interceptor implementation to ensure the configured RunAsManager
supports the type of secure object that the security interceptor will present.
One concrete implementation of a RunAsManager
is provided with Spring Security.
The RunAsManagerImpl
class returns a replacement RunAsUserToken
if any ConfigAttribute
starts with RUN_AS_
.
If any such ConfigAttribute
is found, the replacement RunAsUserToken
will contain the same principal, credentials and granted authorities as the original Authentication
object, along with a new SimpleGrantedAuthority
for each RUN_AS_
ConfigAttribute
.
Each new SimpleGrantedAuthority
will be prefixed with ROLE_
, followed by the RUN_AS
ConfigAttribute
.
For example, a RUN_AS_SERVER
will result in the replacement RunAsUserToken
containing a ROLE_RUN_AS_SERVER
granted authority.
The replacement RunAsUserToken
is just like any other Authentication
object.
It needs to be authenticated by the AuthenticationManager
, probably via delegation to a suitable AuthenticationProvider
.
The RunAsImplAuthenticationProvider
performs such authentication.
It simply accepts as valid any RunAsUserToken
presented.
To ensure malicious code does not create a RunAsUserToken
and present it for guaranteed acceptance by the RunAsImplAuthenticationProvider
, the hash of a key is stored in all generated tokens.
The RunAsManagerImpl
and RunAsImplAuthenticationProvider
is created in the bean context with the same key:
<bean id="runAsManager" class="org.springframework.security.access.intercept.RunAsManagerImpl"> <property name="key" value="my_run_as_password"/> </bean> <bean id="runAsAuthenticationProvider" class="org.springframework.security.access.intercept.RunAsImplAuthenticationProvider"> <property name="key" value="my_run_as_password"/> </bean>
By using the same key, each RunAsUserToken
can be validated it was created by an approved RunAsManagerImpl
.
The RunAsUserToken
is immutable after creation for security reasons
You might be wondering where the login form came from when you were prompted to log in, since we made no mention of any HTML files or JSPs. Since Spring Security’s default configuration does not explicitly set a URL for the login page, Spring Security generates one automatically, based on the features that are enabled and using standard values for the URL which processes the submitted login, the default target URL the user will be sent to after logging in and so on.
While the automatically generated log in page is convenient to get up and running quickly, most applications will want to provide their own login page.
When we want to change the default configuration, we can customize the WebSecurityConfigurerAdapter
that we mentioned earlier by extending it like so:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // ... }
And then override the configure
method as seen below:
protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests .anyRequest().authenticated() ) .formLogin(formLogin -> formLogin .loginPage("/login") .permitAll() ); }
The updated configuration specifies the location of the log in page. | |
We must grant all users (i.e. unauthenticated users) access to our log in page.
The |
An example log in page implemented with JSPs for our current configuration can be seen below:
Note | |
---|---|
The login page below represents our current configuration. We could easily update our configuration if some of the defaults do not meet our needs. |
<c:url value="/login" var="loginUrl"/> <form action="${loginUrl}" method="post"> <c:if test="${param.error != null}"> <p> Invalid username and password. </p> </c:if> <c:if test="${param.logout != null}"> <p> You have been logged out. </p> </c:if> <p> <label for="username">Username</label> <input type="text" id="username" name="username"/> </p> <p> <label for="password">Password</label> <input type="password" id="password" name="password"/> </p> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <button type="submit" class="btn">Log in</button> </form>
A POST to the | |
If the query parameter | |
If the query parameter | |
The username must be present as the HTTP parameter named username | |
The password must be present as the HTTP parameter named password | |
We must the section called “Include the CSRF Token” To learn more read the Section 5.1.1, “Cross Site Request Forgery (CSRF)” section of the reference |
You might be wondering where the login form came from when you were prompted to log in, since we made no mention of any HTML files or JSPs. In fact, since we didn’t explicitly set a URL for the login page, Spring Security generates one automatically, based on the features that are enabled and using standard values for the URL which processes the submitted login, the default target URL the user will be sent to after logging in and so on. However, the namespace offers plenty of support to allow you to customize these options. For example, if you want to supply your own login page, you could use:
<http> <intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/> <intercept-url pattern="/**" access="ROLE_USER" /> <form-login login-page='/login.jsp'/> </http>
Also note that we’ve added an extra intercept-url
element to say that any requests for the login page should be available to anonymous users [7] and also the AuthenticatedVoter class for more details on how the value IS_AUTHENTICATED_ANONYMOUSLY
is processed.].
Otherwise the request would be matched by the pattern /** and it wouldn’t be possible to access the login page itself!
This is a common configuration error and will result in an infinite loop in the application.
Spring Security will emit a warning in the log if your login page appears to be secured.
It is also possible to have all requests matching a particular pattern bypass the security filter chain completely, by defining a separate http
element for the pattern like this:
<http pattern="/css/**" security="none"/> <http pattern="/login.jsp*" security="none"/> <http use-expressions="false"> <intercept-url pattern="/**" access="ROLE_USER" /> <form-login login-page='/login.jsp'/> </http>
From Spring Security 3.1 it is now possible to use multiple http
elements to define separate security filter chain configurations for different request patterns.
If the pattern
attribute is omitted from an http
element, it matches all requests.
Creating an unsecured pattern is a simple example of this syntax, where the pattern is mapped to an empty filter chain [8].
We’ll look at this new syntax in more detail in the chapter on the Security Filter Chain.
It’s important to realise that these unsecured requests will be completely oblivious to any Spring Security web-related configuration or additional attributes such as requires-channel
, so you will not be able to access information on the current user or call secured methods during the request.
Use access='IS_AUTHENTICATED_ANONYMOUSLY'
as an alternative if you still want the security filter chain to be applied.
If you want to use basic authentication instead of form login, then change the configuration to
<http use-expressions="false"> <intercept-url pattern="/**" access="ROLE_USER" /> <http-basic /> </http>
Basic authentication will then take precedence and will be used to prompt for a login when a user attempts to access a protected resource. Form login is still available in this configuration if you wish to use it, for example through a login form embedded in another web page.
Basic and digest authentication are alternative authentication mechanisms which are popular in web applications. Basic authentication is often used with stateless clients which pass their credentials on each request. It’s quite common to use it in combination with form-based authentication where an application is used through both a browser-based user interface and as a web-service. However, basic authentication transmits the password as plain text so it should only really be used over an encrypted transport layer such as HTTPS.
BasicAuthenticationFilter
is responsible for processing basic authentication credentials presented in HTTP headers.
This can be used for authenticating calls made by Spring remoting protocols (such as Hessian and Burlap), as well as normal browser user agents (such as Firefox and Internet Explorer).
The standard governing HTTP Basic Authentication is defined by RFC 1945, Section 11, and BasicAuthenticationFilter
conforms with this RFC.
Basic Authentication is an attractive approach to authentication, because it is very widely deployed in user agents and implementation is extremely simple (it’s just a Base64 encoding of the username:password, specified in an HTTP header).
To implement HTTP Basic Authentication, you need to add a BasicAuthenticationFilter
to your filter chain.
The application context should contain BasicAuthenticationFilter
and its required collaborator:
<bean id="basicAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="authenticationEntryPoint" ref="authenticationEntryPoint"/> </bean> <bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint"> <property name="realmName" value="Name Of Your Realm"/> </bean>
The configured AuthenticationManager
processes each authentication request.
If authentication fails, the configured AuthenticationEntryPoint
will be used to retry the authentication process.
Usually you will use the filter in combination with a BasicAuthenticationEntryPoint
, which returns a 401 response with a suitable header to retry HTTP Basic authentication.
If authentication is successful, the resulting Authentication
object will be placed into the SecurityContextHolder
as usual.
If the authentication event was successful, or authentication was not attempted because the HTTP header did not contain a supported authentication request, the filter chain will continue as normal.
The only time the filter chain will be interrupted is if authentication fails and the AuthenticationEntryPoint
is called.
DigestAuthenticationFilter
is capable of processing digest authentication credentials presented in HTTP headers.
Digest Authentication attempts to solve many of the weaknesses of Basic authentication, specifically by ensuring credentials are never sent in clear text across the wire.
Many user agents support Digest Authentication, including Mozilla Firefox and Internet Explorer.
The standard governing HTTP Digest Authentication is defined by RFC 2617, which updates an earlier version of the Digest Authentication standard prescribed by RFC 2069.
Most user agents implement RFC 2617.
Spring Security’s DigestAuthenticationFilter
is compatible with the “auth” quality of protection (qop
) prescribed by RFC 2617, which also provides backward compatibility with RFC 2069.
Digest Authentication is a more attractive option if you need to use unencrypted HTTP (i.e. no TLS/HTTPS) and wish to maximise security of the authentication process.
Indeed Digest Authentication is a mandatory requirement for the WebDAV protocol, as noted by RFC 2518 Section 17.1.
Note | |
---|---|
You should not use Digest in modern applications because it is not considered secure. The most obvious problem is that you must store your passwords in plaintext, encrypted, or an MD5 format. All of these storage formats are considered insecure. Instead, you should use a one way adaptive password hash (i.e. bCrypt, PBKDF2, SCrypt, etc). |
Central to Digest Authentication is a "nonce". This is a value the server generates. Spring Security’s nonce adopts the following format:
base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key)) expirationTime: The date and time when the nonce expires, expressed in milliseconds key: A private key to prevent modification of the nonce token
The DigestAuthenticationEntryPoint
has a property specifying the key
used for generating the nonce tokens, along with a nonceValiditySeconds
property for determining the expiration time (default 300, which equals five minutes).
Whist ever the nonce is valid, the digest is computed by concatenating various strings including the username, password, nonce, URI being requested, a client-generated nonce (merely a random value which the user agent generates each request), the realm name etc, then performing an MD5 hash.
Both the server and user agent perform this digest computation, resulting in different hash codes if they disagree on an included value (eg password).
In Spring Security implementation, if the server-generated nonce has merely expired (but the digest was otherwise valid), the DigestAuthenticationEntryPoint
will send a "stale=true"
header.
This tells the user agent there is no need to disturb the user (as the password and username etc is correct), but simply to try again using a new nonce.
An appropriate value for the nonceValiditySeconds
parameter of DigestAuthenticationEntryPoint
depends on your application.
Extremely secure applications should note that an intercepted authentication header can be used to impersonate the principal until the expirationTime
contained in the nonce is reached.
This is the key principle when selecting an appropriate setting, but it would be unusual for immensely secure applications to not be running over TLS/HTTPS in the first instance.
Because of the more complex implementation of Digest Authentication, there are often user agent issues. For example, Internet Explorer fails to present an “opaque” token on subsequent requests in the same session. Spring Security filters therefore encapsulate all state information into the “nonce” token instead. In our testing, Spring Security’s implementation works reliably with Mozilla Firefox and Internet Explorer, correctly handling nonce timeouts etc.
Now that we’ve reviewed the theory, let’s see how to use it.
To implement HTTP Digest Authentication, it is necessary to define DigestAuthenticationFilter
in the filter chain.
The application context will need to define the DigestAuthenticationFilter
and its required collaborators:
<bean id="digestFilter" class= "org.springframework.security.web.authentication.www.DigestAuthenticationFilter"> <property name="userDetailsService" ref="jdbcDaoImpl"/> <property name="authenticationEntryPoint" ref="digestEntryPoint"/> <property name="userCache" ref="userCache"/> </bean> <bean id="digestEntryPoint" class= "org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint"> <property name="realmName" value="Contacts Realm via Digest Authentication"/> <property name="key" value="acegi"/> <property name="nonceValiditySeconds" value="10"/> </bean>
The configured UserDetailsService
is needed because DigestAuthenticationFilter
must have direct access to the clear text password of a user.
Digest Authentication will NOT work if you are using encoded passwords in your DAO [9].
The DAO collaborator, along with the UserCache
, are typically shared directly with a DaoAuthenticationProvider
.
The authenticationEntryPoint
property must be DigestAuthenticationEntryPoint
, so that DigestAuthenticationFilter
can obtain the correct realmName
and key
for digest calculations.
Like BasicAuthenticationFilter
, if authentication is successful an Authentication
request token will be placed into the SecurityContextHolder
.
If the authentication event was successful, or authentication was not attempted because the HTTP header did not contain a Digest Authentication request, the filter chain will continue as normal.
The only time the filter chain will be interrupted is if authentication fails and the AuthenticationEntryPoint
is called, as discussed in the previous paragraph.
Digest Authentication’s RFC offers a range of additional features to further increase security. For example, the nonce can be changed on every request. Despite this, Spring Security implementation was designed to minimise the complexity of the implementation (and the doubtless user agent incompatibilities that would emerge), and avoid needing to store server-side state. You are invited to review RFC 2617 if you wish to explore these features in more detail. As far as we are aware, Spring Security’s implementation does comply with the minimum standards of this RFC.
When using the WebSecurityConfigurerAdapter
, logout capabilities are automatically applied.
The default is that accessing the URL /logout
will log the user out by:
SecurityContextHolder
/login?logout
Similar to configuring login capabilities, however, you also have various options to further customize your logout requirements:
protected void configure(HttpSecurity http) throws Exception { http .logout(logout -> logout .logoutUrl("/my/logout") .logoutSuccessUrl("/my/index") .logoutSuccessHandler(logoutSuccessHandler) .invalidateHttpSession(true) .addLogoutHandler(logoutHandler) .deleteCookies(cookieNamesToClear) ) ... }
Provides logout support.
This is automatically applied when using | |
The URL that triggers log out to occur (default is | |
The URL to redirect to after logout has occurred.
The default is | |
Let’s you specify a custom | |
Specify whether to invalidate the | |
Adds a | |
Allows specifying the names of cookies to be removed on logout success.
This is a shortcut for adding a |
Note | |
---|---|
=== Logouts can of course also be configured using the XML Namespace notation. Please see the documentation for the logout element in the Spring Security XML Namespace section for further details. === |
Generally, in order to customize logout functionality, you can add
LogoutHandler
and/or
LogoutSuccessHandler
implementations.
For many common scenarios, these handlers are applied under the
covers when using the fluent API.
The logout
element adds support for logging out by navigating to a particular URL.
The default logout URL is /logout
, but you can set it to something else using the logout-url
attribute.
More information on other available attributes may be found in the namespace appendix.
Generally, LogoutHandler
implementations indicate classes that are able to participate in logout handling.
They are expected to be invoked to perform necessary clean-up.
As such they should
not throw exceptions.
Various implementations are provided:
Please see Section 10.13.4, “Remember-Me Interfaces and Implementations” for details.
Instead of providing LogoutHandler
implementations directly, the fluent API also provides shortcuts that provide the respective LogoutHandler
implementations under the covers.
E.g. deleteCookies()
allows specifying the names of one or more cookies to be removed on logout success.
This is a shortcut compared to adding a CookieClearingLogoutHandler
.
The LogoutSuccessHandler
is called after a successful logout by the LogoutFilter
, to handle e.g.
redirection or forwarding to the appropriate destination.
Note that the interface is almost the same as the LogoutHandler
but may raise an exception.
The following implementations are provided:
As mentioned above, you don’t need to specify the SimpleUrlLogoutSuccessHandler
directly.
Instead, the fluent API provides a shortcut by setting the logoutSuccessUrl()
.
This will setup the SimpleUrlLogoutSuccessHandler
under the covers.
The provided URL will be redirected to after a logout has occurred.
The default is /login?logout
.
The HttpStatusReturningLogoutSuccessHandler
can be interesting in REST API type scenarios.
Instead of redirecting to a URL upon the successful logout, this LogoutSuccessHandler
allows you to provide a plain HTTP status code to be returned.
If not configured a status code 200 will be returned by default.
If you aren’t using form login, OpenID or basic authentication through the namespace, you may want to define an authentication filter and entry point using a traditional bean syntax and link them into the namespace, as we’ve just seen.
The corresponding AuthenticationEntryPoint
can be set using the entry-point-ref
attribute on the <http>
element.
The CAS sample application is a good example of the use of custom beans with the namespace, including this syntax. If you aren’t familiar with authentication entry points, they are discussed in the technical overview chapter.
[2] Note that this is different from the default configuration of the underlying DefaultLdapAuthoritiesPopulator
which uses member={0}
.
[3] It is also possible to obtain the server’s IP address using a DNS lookup. This is not currently supported, but hopefully will be in a future version.
[4] Authentication by mechanisms which perform a redirect after authenticating (such as form-login) will not be detected by SessionManagementFilter
, as the filter will not be invoked during the authenticating request. Session-management functionality has to be handled separately in these cases.
[5] 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.
[6] The use of the key
property should not be regarded as providing any real security here. It is merely a book-keeping exercise. If you are sharing a ProviderManager
which contains an AnonymousAuthenticationProvider
in a scenario where it is possible for an authenticating client to construct the Authentication
object (such as with RMI invocations), then a malicious client could submit an AnonymousAuthenticationToken
which it had created itself (with chosen username and authority list). If the key
is guessable or can be found out, then the token would be accepted by the anonymous provider. This isn’t a problem with normal usage but if you are using RMI you would be best to use a customized ProviderManager
which omits the anonymous provider rather than sharing the one you use for your HTTP authentication mechanisms.
[7] See the chapter on Section 10.15, “Anonymous Authentication”
[8] The use of multiple <http>
elements is an important feature, allowing the namespace to simultaneously support both stateful and stateless paths within the same application, for example. The previous syntax, using the attribute filters="none"
on an intercept-url
element is incompatible with this change and is no longer supported in 3.1.
[9] It is possible to encode the password in the format HEX( MD5(username:realm:password) ) provided the DigestAuthenticationFilter.passwordAlreadyEncoded
is set to true
. However, other password encodings will not work with digest authentication.