In this part we cover features which require knowledge of previous chapters as well as some of the more advanced and less-commonly used features of the framework.
Complex applications often will find the need to define access permissions not simply at a web request or method invocation level.
Instead, security decisions need to comprise both who (Authentication
), where (MethodInvocation
) and what (SomeDomainObject
).
In other words, authorization decisions also need to consider the actual domain object instance subject of a method invocation.
Imagine you’re designing an application for a pet clinic. There will be two main groups of users of your Spring-based application: staff of the pet clinic, as well as the pet clinic’s customers. The staff will have access to all of the data, whilst your customers will only be able to see their own customer records. To make it a little more interesting, your customers can allow other users to see their customer records, such as their "puppy preschool" mentor or president of their local "Pony Club". Using Spring Security as the foundation, you have several approaches that can be used:
Customer
domain object instance to determine which users have access.
By using the SecurityContextHolder.getContext().getAuthentication()
, you’ll be able to access the Authentication
object.
AccessDecisionVoter
to enforce the security from the GrantedAuthority[]
s stored in the Authentication
object.
This would mean your AuthenticationManager
would need to populate the Authentication
with custom GrantedAuthority[]
s representing each of the Customer
domain object instances the principal has access to.
AccessDecisionVoter
to enforce the security and open the target Customer
domain object directly.
This would mean your voter needs access to a DAO that allows it to retrieve the Customer
object.
It would then access the Customer
object’s collection of approved users and make the appropriate decision.
Each one of these approaches is perfectly legitimate.
However, the first couples your authorization checking to your business code.
The main problems with this include the enhanced difficulty of unit testing and the fact it would be more difficult to reuse the Customer
authorization logic elsewhere.
Obtaining the GrantedAuthority[]
s from the Authentication
object is also fine, but will not scale to large numbers of Customer
s.
If a user might be able to access 5,000 Customer
s (unlikely in this case, but imagine if it were a popular vet for a large Pony Club!) the amount of memory consumed and time required to construct the Authentication
object would be undesirable.
The final method, opening the Customer
directly from external code, is probably the best of the three.
It achieves separation of concerns, and doesn’t misuse memory or CPU cycles, but it is still inefficient in that both the AccessDecisionVoter
and the eventual business method itself will perform a call to the DAO responsible for retrieving the Customer
object.
Two accesses per method invocation is clearly undesirable.
In addition, with every approach listed you’ll need to write your own access control list (ACL) persistence and business logic from scratch.
Fortunately, there is another alternative, which we’ll talk about below.
Spring Security’s ACL services are shipped in the spring-security-acl-xxx.jar
.
You will need to add this JAR to your classpath to use Spring Security’s domain object instance security capabilities.
Spring Security’s domain object instance security capabilities centre on the concept of an access control list (ACL). Every domain object instance in your system has its own ACL, and the ACL records details of who can and can’t work with that domain object. With this in mind, Spring Security delivers three main ACL-related capabilities to your application:
As indicated by the first bullet point, one of the main capabilities of the Spring Security ACL module is providing a high-performance way of retrieving ACLs. This ACL repository capability is extremely important, because every domain object instance in your system might have several access control entries, and each ACL might inherit from other ACLs in a tree-like structure (this is supported out-of-the-box by Spring Security, and is very commonly used). Spring Security’s ACL capability has been carefully designed to provide high performance retrieval of ACLs, together with pluggable caching, deadlock-minimizing database updates, independence from ORM frameworks (we use JDBC directly), proper encapsulation, and transparent database updating.
Given databases are central to the operation of the ACL module, let’s explore the four main tables used by default in the implementation. The tables are presented below in order of size in a typical Spring Security ACL deployment, with the table with the most rows listed last:
GrantedAuthority
.
Thus, there is a single row for each unique principal or GrantedAuthority
.
When used in the context of receiving a permission, a SID is generally called a "recipient".
As mentioned in the last paragraph, the ACL system uses integer bit masking.
Don’t worry, you need not be aware of the finer points of bit shifting to use the ACL system, but suffice to say that we have 32 bits we can switch on or off.
Each of these bits represents a permission, and by default the permissions are read (bit 0), write (bit 1), create (bit 2), delete (bit 3) and administer (bit 4).
It’s easy to implement your own Permission
instance if you wish to use other permissions, and the remainder of the ACL framework will operate without knowledge of your extensions.
It is important to understand that the number of domain objects in your system has absolutely no bearing on the fact we’ve chosen to use integer bit masking. Whilst you have 32 bits available for permissions, you could have billions of domain object instances (which will mean billions of rows in ACL_OBJECT_IDENTITY and quite probably ACL_ENTRY). We make this point because we’ve found sometimes people mistakenly believe they need a bit for each potential domain object, which is not the case.
Now that we’ve provided a basic overview of what the ACL system does, and what it looks like at a table structure, let’s explore the key interfaces. The key interfaces are:
Acl
: Every domain object has one and only one Acl
object, which internally holds the AccessControlEntry
s as well as knows the owner of the Acl
.
An Acl does not refer directly to the domain object, but instead to an ObjectIdentity
.
The Acl
is stored in the ACL_OBJECT_IDENTITY table.
AccessControlEntry
: An Acl
holds multiple AccessControlEntry
s, which are often abbreviated as ACEs in the framework.
Each ACE refers to a specific tuple of Permission
, Sid
and Acl
.
An ACE can also be granting or non-granting and contain audit settings.
The ACE is stored in the ACL_ENTRY table.
Permission
: A permission represents a particular immutable bit mask, and offers convenience functions for bit masking and outputting information.
The basic permissions presented above (bits 0 through 4) are contained in the BasePermission
class.
Sid
: The ACL module needs to refer to principals and GrantedAuthority[]
s.
A level of indirection is provided by the Sid
interface, which is an abbreviation of "security identity".
Common classes include PrincipalSid
(to represent the principal inside an Authentication
object) and GrantedAuthoritySid
.
The security identity information is stored in the ACL_SID table.
ObjectIdentity
: Each domain object is represented internally within the ACL module by an ObjectIdentity
.
The default implementation is called ObjectIdentityImpl
.
AclService
: Retrieves the Acl
applicable for a given ObjectIdentity
.
In the included implementation (JdbcAclService
), retrieval operations are delegated to a LookupStrategy
.
The LookupStrategy
provides a highly optimized strategy for retrieving ACL information, using batched retrievals (BasicLookupStrategy
) and supporting custom implementations that leverage materialized views, hierarchical queries and similar performance-centric, non-ANSI SQL capabilities.
MutableAclService
: Allows a modified Acl
to be presented for persistence.
It is not essential to use this interface if you do not wish.
Please note that our out-of-the-box AclService and related database classes all use ANSI SQL. This should therefore work with all major databases. At the time of writing, the system had been successfully tested using Hypersonic SQL, PostgreSQL, Microsoft SQL Server and Oracle.
Two samples ship with Spring Security that demonstrate the ACL module. The first is the Contacts Sample, and the other is the Document Management System (DMS) Sample. We suggest taking a look over these for examples.
To get starting using Spring Security’s ACL capability, you will need to store your ACL information somewhere.
This necessitates the instantiation of a DataSource
using Spring.
The DataSource
is then injected into a JdbcMutableAclService
and BasicLookupStrategy
instance.
The latter provides high-performance ACL retrieval capabilities, and the former provides mutator capabilities.
Refer to one of the samples that ship with Spring Security for an example configuration.
You’ll also need to populate the database with the four ACL-specific tables listed in the last section (refer to the ACL samples for the appropriate SQL statements).
Once you’ve created the required schema and instantiated JdbcMutableAclService
, you’ll next need to ensure your domain model supports interoperability with the Spring Security ACL package.
Hopefully ObjectIdentityImpl
will prove sufficient, as it provides a large number of ways in which it can be used.
Most people will have domain objects that contain a public Serializable getId()
method.
If the return type is long, or compatible with long (eg an int), you will find you need not give further consideration to ObjectIdentity
issues.
Many parts of the ACL module rely on long identifiers.
If you’re not using long (or an int, byte etc), there is a very good chance you’ll need to reimplement a number of classes.
We do not intend to support non-long identifiers in Spring Security’s ACL module, as longs are already compatible with all database sequences, the most common identifier data type, and are of sufficient length to accommodate all common usage scenarios.
The following fragment of code shows how to create an Acl
, or modify an existing Acl
:
// Prepare the information we'd like in our access control entry (ACE) ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44)); Sid sid = new PrincipalSid("Samantha"); Permission p = BasePermission.ADMINISTRATION; // Create or update the relevant ACL MutableAcl acl = null; try { acl = (MutableAcl) aclService.readAclById(oi); } catch (NotFoundException nfe) { acl = aclService.createAcl(oi); } // Now grant some permissions via an access control entry (ACE) acl.insertAce(acl.getEntries().length, p, sid, true); aclService.updateAcl(acl);
In the example above, we’re retrieving the ACL associated with the "Foo" domain object with identifier number 44. We’re then adding an ACE so that a principal named "Samantha" can "administer" the object. The code fragment is relatively self-explanatory, except the insertAce method. The first argument to the insertAce method is determining at what position in the Acl the new entry will be inserted. In the example above, we’re just putting the new ACE at the end of the existing ACEs. The final argument is a Boolean indicating whether the ACE is granting or denying. Most of the time it will be granting (true), but if it is denying (false), the permissions are effectively being blocked.
Spring Security does not provide any special integration to automatically create, update or delete ACLs as part of your DAO or repository operations. Instead, you will need to write code like shown above for your individual domain objects. It’s worth considering using AOP on your services layer to automatically integrate the ACL information with your services layer operations. We’ve found this quite an effective approach in the past.
Once you’ve used the above techniques to store some ACL information in the database, the next step is to actually use the ACL information as part of authorization decision logic.
You have a number of choices here.
You could write your own AccessDecisionVoter
or AfterInvocationProvider
that respectively fires before or after a method invocation.
Such classes would use AclService
to retrieve the relevant ACL and then call Acl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
to decide whether permission is granted or denied.
Alternately, you could use our AclEntryVoter
, AclEntryAfterInvocationProvider
or AclEntryAfterInvocationCollectionFilteringProvider
classes.
All of these classes provide a declarative-based approach to evaluating ACL information at runtime, freeing you from needing to write any code.
Please refer to the sample applications to learn how to use these classes.
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.
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" />
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 [19].
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 [20].
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.
HttpSecurity.oauth2Login()
provides a number of configuration options for customizing OAuth 2.0 Login.
The main configuration options are grouped into their protocol endpoint counterparts.
For example, oauth2Login().authorizationEndpoint()
allows configuring the Authorization Endpoint, whereas oauth2Login().tokenEndpoint()
allows configuring the Token Endpoint.
The following code shows an example:
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login() .authorizationEndpoint() ... .redirectionEndpoint() ... .tokenEndpoint() ... .userInfoEndpoint() ... } }
The main goal of the oauth2Login()
DSL was to closely align with the naming, as defined in the specifications.
The OAuth 2.0 Authorization Framework defines the Protocol Endpoints as follows:
The authorization process utilizes two authorization server endpoints (HTTP resources):
As well as one client endpoint:
The OpenID Connect Core 1.0 specification defines the UserInfo Endpoint as follows:
The UserInfo Endpoint is an OAuth 2.0 Protected Resource that returns claims about the authenticated end-user. To obtain the requested claims about the end-user, the client makes a request to the UserInfo Endpoint by using an access token obtained through OpenID Connect Authentication. These claims are normally represented by a JSON object that contains a collection of name-value pairs for the claims.
The following code shows the complete configuration options available for the oauth2Login()
DSL:
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login() .clientRegistrationRepository(this.clientRegistrationRepository()) .authorizedClientRepository(this.authorizedClientRepository()) .authorizedClientService(this.authorizedClientService()) .loginPage("/login") .authorizationEndpoint() .baseUri(this.authorizationRequestBaseUri()) .authorizationRequestRepository(this.authorizationRequestRepository()) .authorizationRequestResolver(this.authorizationRequestResolver()) .and() .redirectionEndpoint() .baseUri(this.authorizationResponseBaseUri()) .and() .tokenEndpoint() .accessTokenResponseClient(this.accessTokenResponseClient()) .and() .userInfoEndpoint() .userAuthoritiesMapper(this.userAuthoritiesMapper()) .userService(this.oauth2UserService()) .oidcUserService(this.oidcUserService()) .customUserType(GitHubOAuth2User.class, "github"); } }
The following sections go into more detail on each of the configuration options available:
By default, the OAuth 2.0 Login Page is auto-generated by the DefaultLoginPageGeneratingFilter
.
The default login page shows each configured OAuth Client with its ClientRegistration.clientName
as a link, which is capable of initiating the Authorization Request (or OAuth 2.0 Login).
Note | |
---|---|
In order for |
The link’s destination for each OAuth Client defaults to the following:
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI
+ "/{registrationId}"
The following line shows an example:
<a href="/oauth2/authorization/google">Google</a>
To override the default login page, configure oauth2Login().loginPage()
and (optionally) oauth2Login().authorizationEndpoint().baseUri()
.
The following listing shows an example:
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login() .loginPage("/login/oauth2") ... .authorizationEndpoint() .baseUri("/login/oauth2/authorization") .... } }
Important | |
---|---|
You need to provide a |
Tip | |
---|---|
As noted earlier, configuring The following line shows an example: <a href="/login/oauth2/authorization/google">Google</a> |
The Redirection Endpoint is used by the Authorization Server for returning the Authorization Response (which contains the authorization credentials) to the client via the Resource Owner user-agent.
Tip | |
---|---|
OAuth 2.0 Login leverages the Authorization Code Grant. Therefore, the authorization credential is the authorization code. |
The default Authorization Response baseUri
(redirection endpoint) is /login/oauth2/code/*
, which is defined in OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI
.
If you would like to customize the Authorization Response baseUri
, configure it as shown in the following example:
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login() .redirectionEndpoint() .baseUri("/login/oauth2/callback/*") .... } }
Important | |
---|---|
You also need to ensure the The following listing shows an example: return CommonOAuth2Provider.GOOGLE.getBuilder("google") .clientId("google-client-id") .clientSecret("google-client-secret") .redirectUriTemplate("{baseUrl}/login/oauth2/callback/{registrationId}") .build(); |
The UserInfo Endpoint includes a number of configuration options, as described in the following sub-sections:
After the user successfully authenticates with the OAuth 2.0 Provider, the OAuth2User.getAuthorities()
(or OidcUser.getAuthorities()
) may be mapped to a new set of GrantedAuthority
instances, which will be supplied to OAuth2AuthenticationToken
when completing the authentication.
Tip | |
---|---|
|
There are a couple of options to choose from when mapping user authorities:
Provide an implementation of GrantedAuthoritiesMapper
and configure it as shown in the following example:
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login() .userInfoEndpoint() .userAuthoritiesMapper(this.userAuthoritiesMapper()) ... } private GrantedAuthoritiesMapper userAuthoritiesMapper() { return (authorities) -> { Set<GrantedAuthority> mappedAuthorities = new HashSet<>(); authorities.forEach(authority -> { if (OidcUserAuthority.class.isInstance(authority)) { OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority; OidcIdToken idToken = oidcUserAuthority.getIdToken(); OidcUserInfo userInfo = oidcUserAuthority.getUserInfo(); // Map the claims found in idToken and/or userInfo // to one or more GrantedAuthority's and add it to mappedAuthorities } else if (OAuth2UserAuthority.class.isInstance(authority)) { OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority; Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes(); // Map the attributes found in userAttributes // to one or more GrantedAuthority's and add it to mappedAuthorities } }); return mappedAuthorities; }; } }
Alternatively, you may register a GrantedAuthoritiesMapper
@Bean
to have it automatically applied to the configuration, as shown in the following example:
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.oauth2Login(); } @Bean public GrantedAuthoritiesMapper userAuthoritiesMapper() { ... } }
This strategy is advanced compared to using a GrantedAuthoritiesMapper
, however, it’s also more flexible as it gives you access to the OAuth2UserRequest
and OAuth2User
(when using an OAuth 2.0 UserService) or OidcUserRequest
and OidcUser
(when using an OpenID Connect 1.0 UserService).
The OAuth2UserRequest
(and OidcUserRequest
) provides you access to the associated OAuth2AccessToken
, which is very useful in the cases where the delegator needs to fetch authority information from a protected resource before it can map the custom authorities for the user.
The following example shows how to implement and configure a delegation-based strategy using an OpenID Connect 1.0 UserService:
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login() .userInfoEndpoint() .oidcUserService(this.oidcUserService()) ... } private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() { final OidcUserService delegate = new OidcUserService(); return (userRequest) -> { // Delegate to the default implementation for loading a user OidcUser oidcUser = delegate.loadUser(userRequest); OAuth2AccessToken accessToken = userRequest.getAccessToken(); Set<GrantedAuthority> mappedAuthorities = new HashSet<>(); // TODO // 1) Fetch the authority information from the protected resource using accessToken // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities // 3) Create a copy of oidcUser but use the mappedAuthorities instead oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo()); return oidcUser; }; } }
CustomUserTypesOAuth2UserService
is an implementation of an OAuth2UserService
that provides support for custom OAuth2User
types.
If the default implementation (DefaultOAuth2User
) does not suit your needs, you can define your own implementation of OAuth2User
.
The following code demonstrates how you would register a custom OAuth2User
type for GitHub:
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login() .userInfoEndpoint() .customUserType(GitHubOAuth2User.class, "github") ... } }
The following code shows an example of a custom OAuth2User
type for GitHub:
public class GitHubOAuth2User implements OAuth2User { private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER"); private Map<String, Object> attributes; private String id; private String name; private String login; private String email; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } @Override public Map<String, Object> getAttributes() { if (this.attributes == null) { this.attributes = new HashMap<>(); this.attributes.put("id", this.getId()); this.attributes.put("name", this.getName()); this.attributes.put("login", this.getLogin()); this.attributes.put("email", this.getEmail()); } return attributes; } public String getId() { return this.id; } public void setId(String id) { this.id = id; } @Override public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getLogin() { return this.login; } public void setLogin(String login) { this.login = login; } public String getEmail() { return this.email; } public void setEmail(String email) { this.email = email; } }
Tip | |
---|---|
|
DefaultOAuth2UserService
is an implementation of an OAuth2UserService
that supports standard OAuth 2.0 Provider’s.
Note | |
---|---|
|
DefaultOAuth2UserService
uses a RestOperations
when requesting the user attributes at the UserInfo Endpoint.
If you need to customize the pre-processing of the UserInfo Request, you can provide DefaultOAuth2UserService.setRequestEntityConverter()
with a custom Converter<OAuth2UserRequest, RequestEntity<?>>
.
The default implementation OAuth2UserRequestEntityConverter
builds a RequestEntity
representation of a UserInfo Request that sets the OAuth2AccessToken
in the Authorization
header by default.
On the other end, if you need to customize the post-handling of the UserInfo Response, you will need to provide DefaultOAuth2UserService.setRestOperations()
with a custom configured RestOperations
.
The default RestOperations
is configured as follows:
RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
OAuth2ErrorResponseErrorHandler
is a ResponseErrorHandler
that can handle an OAuth 2.0 Error (400 Bad Request).
It uses an OAuth2ErrorHttpMessageConverter
for converting the OAuth 2.0 Error parameters to an OAuth2Error
.
Whether you customize DefaultOAuth2UserService
or provide your own implementation of OAuth2UserService
, you’ll need to configure it as shown in the following example:
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login() .userInfoEndpoint() .userService(this.oauth2UserService()) ... } private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() { ... } }
OidcUserService
is an implementation of an OAuth2UserService
that supports OpenID Connect 1.0 Provider’s.
The OidcUserService
leverages the DefaultOAuth2UserService
when requesting the user attributes at the UserInfo Endpoint.
If you need to customize the pre-processing of the UserInfo Request and/or the post-handling of the UserInfo Response, you will need to provide OidcUserService.setOauth2UserService()
with a custom configured DefaultOAuth2UserService
.
Whether you customize OidcUserService
or provide your own implementation of OAuth2UserService
for OpenID Connect 1.0 Provider’s, you’ll need to configure it as shown in the following example:
@EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login() .userInfoEndpoint() .oidcUserService(this.oidcUserService()) ... } private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() { ... } }
Note | |
---|---|
The following documentation is for use within Servlet environments. For all other environments, refer to WebClient for Reactive environments. |
Spring Framework has built in support for setting a Bearer token.
webClient.get() .headers(h -> h.setBearerAuth(token)) ...
Spring Security builds on this support to provide additional benefits:
If an access token is requested and not present, Spring Security will automatically request the access token.
The first step is ensuring to setup the WebClient
correctly.
An example of setting up WebClient
in a servlet environment can be found below:
@Bean WebClient webClient(ClientRegistrationRepository clientRegistrations, OAuth2AuthorizedClientRepository authorizedClients) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients); // (optional) explicitly opt into using the oauth2Login to provide an access token implicitly // oauth.setDefaultOAuth2AuthorizedClient(true); // (optional) set a default ClientRegistration.registrationId // oauth.setDefaultClientRegistrationId("client-registration-id"); return WebClient.builder() .apply(oauth2.oauth2Configuration()) .build(); }
If we set defaultOAuth2AuthorizedClient
to true
in our setup and the user authenticated with oauth2Login (i.e. OIDC), then the current authentication is used to automatically provide the access token.
Alternatively, if we set defaultClientRegistrationId
to a valid ClientRegistration
id, that registration is used to provide the access token.
This is convenient, but in environments where not all endpoints should get the access token, it is dangerous (you might provide the wrong access token to an endpoint).
Mono<String> body = this.webClient .get() .uri(this.uri) .retrieve() .bodyToMono(String.class);
The OAuth2AuthorizedClient
can be explicitly provided by setting it on the request attributes.
In the example below we resolve the OAuth2AuthorizedClient
using Spring WebFlux or Spring MVC argument resolver support.
However, it does not matter how the OAuth2AuthorizedClient
is resolved.
@GetMapping("/explicit") Mono<String> explicit(@RegisteredOAuth2AuthorizedClient("client-id") OAuth2AuthorizedClient authorizedClient) { return this.webClient .get() .uri(this.uri) .attributes(oauth2AuthorizedClient(authorizedClient)) .retrieve() .bodyToMono(String.class); }
Alternatively, it is possible to specify the clientRegistrationId
on the request attributes and the WebClient
will attempt to lookup the OAuth2AuthorizedClient
.
If it is not found, one will automatically be acquired.
Mono<String> body = this.webClient .get() .uri(this.uri) .attributes(clientRegistrationId("client-id")) .retrieve() .bodyToMono(String.class);
Spring Security has its own taglib which provides basic support for accessing security information and applying security constraints in JSPs.
To use any of the tags, you must have the security taglib declared in your JSP:
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
This tag is used to determine whether its contents should be evaluated or not.
In Spring Security 3.0, it can be used in two ways [21].
The first approach uses a web-security expression, specified in the access
attribute of the tag.
The expression evaluation will be delegated to the SecurityExpressionHandler<FilterInvocation>
defined in the application context (you should have web expressions enabled in your <http>
namespace configuration to make sure this service is available).
So, for example, you might have
<sec:authorize access="hasRole('supervisor')"> This content will only be visible to users who have the "supervisor" authority in their list of <tt>GrantedAuthority</tt>s. </sec:authorize>
When used in conjuction with Spring Security’s PermissionEvaluator, the tag can also be used to check permissions. For example:
<sec:authorize access="hasPermission(#domain,'read') or hasPermission(#domain,'write')"> This content will only be visible to users who have read or write permission to the Object found as a request attribute named "domain". </sec:authorize>
A common requirement is to only show a particular link, if the user is actually allowed to click it. How can we determine in advance whether something will be allowed? This tag can also operate in an alternative mode which allows you to define a particular URL as an attribute. If the user is allowed to invoke that URL, then the tag body will be evaluated, otherwise it will be skipped. So you might have something like
<sec:authorize url="/admin"> This content will only be visible to users who are authorized to send requests to the "/admin" URL. </sec:authorize>
To use this tag there must also be an instance of WebInvocationPrivilegeEvaluator
in your application context.
If you are using the namespace, one will automatically be registered.
This is an instance of DefaultWebInvocationPrivilegeEvaluator
, which creates a dummy web request for the supplied URL and invokes the security interceptor to see whether the request would succeed or fail.
This allows you to delegate to the access-control setup you defined using intercept-url
declarations within the <http>
namespace configuration and saves having to duplicate the information (such as the required roles) within your JSPs.
This approach can also be combined with a method
attribute, supplying the HTTP method, for a more specific match.
The Boolean result of evaluating the tag (whether it grants or denies access) can be stored in a page context scope variable by setting the var
attribute to the variable name, avoiding the need for duplicating and re-evaluating the condition at other points in the page.
Hiding a link in a page for unauthorized users doesn’t prevent them from accessing the URL.
They could just type it into their browser directly, for example.
As part of your testing process, you may want to reveal the hidden areas in order to check that links really are secured at the back end.
If you set the system property spring.security.disableUISecurity
to true
, the authorize
tag will still run but will not hide its contents.
By default it will also surround the content with <span class="securityHiddenUI">…</span>
tags.
This allows you to display "hidden" content with a particular CSS style such as a different background colour.
Try running the "tutorial" sample application with this property enabled, for example.
You can also set the properties spring.security.securedUIPrefix
and spring.security.securedUISuffix
if you want to change surrounding text from the default span
tags (or use empty strings to remove it completely).
This tag allows access to the current Authentication
object stored in the security context.
It renders a property of the object directly in the JSP.
So, for example, if the principal
property of the Authentication
is an instance of Spring Security’s UserDetails
object, then using <sec:authentication property="principal.username" />
will render the name of the current user.
Of course, it isn’t necessary to use JSP tags for this kind of thing and some people prefer to keep as little logic as possible in the view.
You can access the Authentication
object in your MVC controller (by calling SecurityContextHolder.getContext().getAuthentication()
) and add the data directly to your model for rendering by the view.
This tag is only valid when used with Spring Security’s ACL module. It checks a comma-separated list of required permissions for a specified domain object. If the current user has all of those permissions, then the tag body will be evaluated. If they don’t, it will be skipped. An example might be
Caution | |
---|---|
In general this tag should be considered deprecated. Instead use the Section 12.6.2, “The authorize Tag”. |
<sec:accesscontrollist hasPermission="1,2" domainObject="${someObject}"> This will be shown if the user has all of the permissions represented by the values "1" or "2" on the given object. </sec:accesscontrollist>
The permissions are passed to the PermissionFactory
defined in the application context, converting them to ACL Permission
instances, so they may be any format which is supported by the factory - they don’t have to be integers, they could be strings like READ
or WRITE
.
If no PermissionFactory
is found, an instance of DefaultPermissionFactory
will be used.
The AclService
from the application context will be used to load the Acl
instance for the supplied object.
The Acl
will be invoked with the required permissions to check if all of them are granted.
This tag also supports the var
attribute, in the same way as the authorize
tag.
If CSRF protection is enabled, this tag inserts a hidden form field with the correct name and value for the CSRF protection token. If CSRF protection is not enabled, this tag outputs nothing.
Normally Spring Security automatically inserts a CSRF form field for any <form:form>
tags you use, but if for some reason you cannot use <form:form>
, csrfInput
is a handy replacement.
You should place this tag within an HTML <form></form>
block, where you would normally place other input fields.
Do NOT place this tag within a Spring <form:form></form:form>
block.
Spring Security handles Spring forms automatically.
<form method="post" action="/do/something"> <sec:csrfInput /> Name:<br /> <input type="text" name="name" /> ... </form>
If CSRF protection is enabled, this tag inserts meta tags containing the CSRF protection token form field and header names and CSRF protection token value. These meta tags are useful for employing CSRF protection within JavaScript in your applications.
You should place csrfMetaTags
within an HTML <head></head>
block, where you would normally place other meta tags.
Once you use this tag, you can access the form field name, header name, and token value easily using JavaScript.
JQuery is used in this example to make the task easier.
<!DOCTYPE html> <html> <head> <title>CSRF Protected JavaScript Page</title> <meta name="description" content="This is the description for this page" /> <sec:csrfMetaTags /> <script type="text/javascript" language="javascript"> var csrfParameter = $("meta[name='_csrf_parameter']").attr("content"); var csrfHeader = $("meta[name='_csrf_header']").attr("content"); var csrfToken = $("meta[name='_csrf']").attr("content"); // using XMLHttpRequest directly to send an x-www-form-urlencoded request var ajax = new XMLHttpRequest(); ajax.open("POST", "https://www.example.org/do/something", true); ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded data"); ajax.send(csrfParameter + "=" + csrfToken + "&name=John&..."); // using XMLHttpRequest directly to send a non-x-www-form-urlencoded request var ajax = new XMLHttpRequest(); ajax.open("POST", "https://www.example.org/do/something", true); ajax.setRequestHeader(csrfHeader, csrfToken); ajax.send("..."); // using JQuery to send an x-www-form-urlencoded request var data = {}; data[csrfParameter] = csrfToken; data["name"] = "John"; ... $.ajax({ url: "https://www.example.org/do/something", type: "POST", data: data, ... }); // using JQuery to send a non-x-www-form-urlencoded request var headers = {}; headers[csrfHeader] = csrfToken; $.ajax({ url: "https://www.example.org/do/something", type: "POST", headers: headers, ... }); <script> </head> <body> ... </body> </html>
If CSRF protection is not enabled, csrfMetaTags
outputs nothing.
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-6.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
The Spring Security Crypto module provides support for symmetric encryption, key generation, and password encoding. The code is distributed as part of the core module but has no dependencies on any other Spring Security (or Spring) code.
The Encryptors class provides factory methods for constructing symmetric encryptors. Using this class, you can create ByteEncryptors to encrypt data in raw byte[] form. You can also construct TextEncryptors to encrypt text strings. Encryptors are thread-safe.
Use the Encryptors.standard factory method to construct a "standard" BytesEncryptor:
Encryptors.standard("password", "salt");
The "standard" encryption method is 256-bit AES using PKCS #5’s PBKDF2 (Password-Based Key Derivation Function #2). This method requires Java 6. The password used to generate the SecretKey should be kept in a secure place and not be shared. The salt is used to prevent dictionary attacks against the key in the event your encrypted data is compromised. A 16-byte random initialization vector is also applied so each encrypted message is unique.
The provided salt should be in hex-encoded String form, be random, and be at least 8 bytes in length. Such a salt may be generated using a KeyGenerator:
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
Use the Encryptors.text factory method to construct a standard TextEncryptor:
Encryptors.text("password", "salt");
A TextEncryptor uses a standard BytesEncryptor to encrypt text data. Encrypted results are returned as hex-encoded strings for easy storage on the filesystem or in the database.
Use the Encryptors.queryableText factory method to construct a "queryable" TextEncryptor:
Encryptors.queryableText("password", "salt");
The difference between a queryable TextEncryptor and a standard TextEncryptor has to do with initialization vector (iv) handling. The iv used in a queryable TextEncryptor#encrypt operation is shared, or constant, and is not randomly generated. This means the same text encrypted multiple times will always produce the same encryption result. This is less secure, but necessary for encrypted data that needs to be queried against. An example of queryable encrypted text would be an OAuth apiKey.
The KeyGenerators class provides a number of convenience factory methods for constructing different types of key generators. Using this class, you can create a BytesKeyGenerator to generate byte[] keys. You can also construct a StringKeyGenerator to generate string keys. KeyGenerators are thread-safe.
Use the KeyGenerators.secureRandom factory methods to generate a BytesKeyGenerator backed by a SecureRandom instance:
BytesKeyGenerator generator = KeyGenerators.secureRandom();
byte[] key = generator.generateKey();
The default key length is 8 bytes. There is also a KeyGenerators.secureRandom variant that provides control over the key length:
KeyGenerators.secureRandom(16);
Use the KeyGenerators.shared factory method to construct a BytesKeyGenerator that always returns the same key on every invocation:
KeyGenerators.shared(16);
The password package of the spring-security-crypto module provides support for encoding passwords.
PasswordEncoder
is the central service interface and has the following signature:
public interface PasswordEncoder { String encode(String rawPassword); boolean matches(String rawPassword, String encodedPassword); }
The matches method returns true if the rawPassword, once encoded, equals the encodedPassword. This method is designed to support password-based authentication schemes.
The BCryptPasswordEncoder
implementation uses the widely supported "bcrypt" algorithm to hash the passwords.
Bcrypt uses a random 16 byte salt value and is a deliberately slow algorithm, in order to hinder password crackers.
The amount of work it does can be tuned using the "strength" parameter which takes values from 4 to 31.
The higher the value, the more work has to be done to calculate the hash.
The default value is 10.
You can change this value in your deployed system without affecting existing passwords, as the value is also stored in the encoded hash.
// Create an encoder with strength 16 BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16); String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result));
The Pbkdf2PasswordEncoder
implementation uses PBKDF2 algorithm to hash the passwords.
In order to defeat password cracking PBKDF2 is a deliberately slow algorithm and should be tuned to take about .5 seconds to verify a password on your system.
// Create an encoder with all the defaults Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder(); String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result));
In most environments, Security is stored on a per Thread
basis.
This means that when work is done on a new Thread
, the SecurityContext
is lost.
Spring Security provides some infrastructure to help make this much easier for users.
Spring Security provides low level abstractions for working with Spring Security in multi-threaded environments.
In fact, this is what Spring Security builds on to integration with the section called “AsyncContext.start(Runnable)” and Section 12.13.4, “Spring MVC Async Integration”.
One of the most fundamental building blocks within Spring Security’s concurrency support is the DelegatingSecurityContextRunnable
.
It wraps a delegate Runnable
in order to initialize the SecurityContextHolder
with a specified SecurityContext
for the delegate.
It then invokes the delegate Runnable ensuring to clear the SecurityContextHolder
afterwards.
The DelegatingSecurityContextRunnable
looks something like this:
public void run() { try { SecurityContextHolder.setContext(securityContext); delegate.run(); } finally { SecurityContextHolder.clearContext(); } }
While very simple, it makes it seamless to transfer the SecurityContext from one Thread to another.
This is important since, in most cases, the SecurityContextHolder acts on a per Thread basis.
For example, you might have used Spring Security’s the section called “<global-method-security>” support to secure one of your services.
You can now easily transfer the SecurityContext
of the current Thread
to the Thread
that invokes the secured service.
An example of how you might do this can be found below:
Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; SecurityContext context = SecurityContextHolder.getContext(); DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(originalRunnable, context); new Thread(wrappedRunnable).start();
The code above performs the following steps:
Runnable
that will be invoking our secured service.
Notice that it is not aware of Spring Security
SecurityContext
that we wish to use from the SecurityContextHolder
and initializes the DelegatingSecurityContextRunnable
DelegatingSecurityContextRunnable
to create a Thread
Since it is quite common to create a DelegatingSecurityContextRunnable
with the SecurityContext
from the SecurityContextHolder
there is a shortcut constructor for it.
The following code is the same as the code above:
Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(originalRunnable); new Thread(wrappedRunnable).start();
The code we have is simple to use, but it still requires knowledge that we are using Spring Security.
In the next section we will take a look at how we can utilize DelegatingSecurityContextExecutor
to hide the fact that we are using Spring Security.
In the previous section we found that it was easy to use the DelegatingSecurityContextRunnable
, but it was not ideal since we had to be aware of Spring Security in order to use it.
Let’s take a look at how DelegatingSecurityContextExecutor
can shield our code from any knowledge that we are using Spring Security.
The design of DelegatingSecurityContextExecutor
is very similar to that of DelegatingSecurityContextRunnable
except it accepts a delegate Executor
instead of a delegate Runnable
.
You can see an example of how it might be used below:
SecurityContext context = SecurityContextHolder.createEmptyContext(); Authentication authentication = new UsernamePasswordAuthenticationToken("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER")); context.setAuthentication(authentication); SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor, context); Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; executor.execute(originalRunnable);
The code performs the following steps:
SecurityContext
to be used for our DelegatingSecurityContextExecutor
.
Note that in this example we simply create the SecurityContext
by hand.
However, it does not matter where or how we get the SecurityContext
(i.e. we could obtain it from the SecurityContextHolder
if we wanted).
Runnable
s
DelegatingSecurityContextExecutor
which is in charge of wrapping any Runnable that is passed into the execute method with a DelegatingSecurityContextRunnable
.
It then passes the wrapped Runnable to the delegateExecutor.
In this instance, the same SecurityContext
will be used for every Runnable submitted to our DelegatingSecurityContextExecutor
.
This is nice if we are running background tasks that need to be run by a user with elevated privileges.
SecurityContext
and the DelegatingSecurityContextExecutor
in our own code, we can inject an already initialized instance of DelegatingSecurityContextExecutor
.
@Autowired private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor public void submitRunnable() { Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; executor.execute(originalRunnable); }
Now our code is unaware that the SecurityContext
is being propagated to the Thread
, then the originalRunnable
is executed, and then the SecurityContextHolder
is cleared out.
In this example, the same user is being used to execute each Thread.
What if we wanted to use the user from SecurityContextHolder
at the time we invoked executor.execute(Runnable)
(i.e. the currently logged in user) to process originalRunnable
?
This can be done by removing the SecurityContext
argument from our DelegatingSecurityContextExecutor
constructor.
For example:
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor);
Now anytime executor.execute(Runnable)
is executed the SecurityContext
is first obtained by the SecurityContextHolder
and then that SecurityContext
is used to create our DelegatingSecurityContextRunnable
.
This means that we are executing our Runnable
with the same user that was used to invoke the executor.execute(Runnable)
code.
Refer to the Javadoc for additional integrations with both the Java concurrent APIs and the Spring Task abstractions. They are quite self-explanatory once you understand the previous code.
Spring Security provides a number of optional integrations with Spring MVC. This section covers the integration in further detail.
Note | |
---|---|
As of Spring Security 4.0, |
To enable Spring Security integration with Spring MVC add the @EnableWebSecurity
annotation to your configuration.
Note | |
---|---|
Spring Security provides the configuration using Spring MVC’s WebMvcConfigurer.
This means that if you are using more advanced options, like integrating with |
Spring Security provides deep integration with how Spring MVC matches on URLs with MvcRequestMatcher
.
This is helpful to ensure your Security rules match the logic used to handle your requests.
In order to use MvcRequestMatcher
you must place the Spring Security Configuration in the same ApplicationContext
as your DispatcherServlet
.
This is necessary because Spring Security’s MvcRequestMatcher
expects a HandlerMappingIntrospector
bean with the name of mvcHandlerMappingIntrospector
to be registered by your Spring MVC configuration that is used to perform the matching.
For a web.xml
this means that you should place your configuration in the DispatcherServlet.xml
.
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- All Spring Configuration (both MVC and Security) are in /WEB-INF/spring/ --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/*.xml</param-value> </context-param> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- Load from the ContextLoaderListener --> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
Below WebSecurityConfiguration
in placed in the DispatcherServlet
s ApplicationContext
.
public class SecurityInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[] { RootConfiguration.class, WebMvcConfiguration.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
Note | |
---|---|
It is always recommended to provide authorization rules by matching on the Providing authorization rules by matching on |
Consider a controller that is mapped as follows:
@RequestMapping("/admin") public String admin() {
If we wanted to restrict access to this controller method to admin users, a developer can provide authorization rules by matching on the HttpServletRequest
with the following:
protected configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/admin").hasRole("ADMIN"); }
or in XML
<http> <intercept-url pattern="/admin" access="hasRole('ADMIN')"/> </http>
With either configuration, the URL /admin
will require the authenticated user to be an admin user.
However, depending on our Spring MVC configuration, the URL /admin.html
will also map to our admin()
method.
Additionally, depending on our Spring MVC configuration, the URL /admin/
will also map to our admin()
method.
The problem is that our security rule is only protecting /admin
.
We could add additional rules for all the permutations of Spring MVC, but this would be quite verbose and tedious.
Instead, we can leverage Spring Security’s MvcRequestMatcher
.
The following configuration will protect the same URLs that Spring MVC will match on by using Spring MVC to match on the URL.
protected configure(HttpSecurity http) throws Exception { http .authorizeRequests() .mvcMatchers("/admin").hasRole("ADMIN"); }
or in XML
<http request-matcher="mvc"> <intercept-url pattern="/admin" access="hasRole('ADMIN')"/> </http>
Spring Security provides AuthenticationPrincipalArgumentResolver
which can automatically resolve the current Authentication.getPrincipal()
for Spring MVC arguments.
By using @EnableWebSecurity
you will automatically have this added to your Spring MVC configuration.
If you use XML based configuration, you must add this yourself.
For example:
<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
Once AuthenticationPrincipalArgumentResolver
is properly configured, you can be entirely decoupled from Spring Security in your Spring MVC layer.
Consider a situation where a custom UserDetailsService
that returns an Object
that implements UserDetails
and your own CustomUser
Object
. The CustomUser
of the currently authenticated user could be accessed using the following code:
@RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); CustomUser custom = (CustomUser) authentication == null ? null : authentication.getPrincipal(); // .. find messages for this user and return them ... }
As of Spring Security 3.2 we can resolve the argument more directly by adding an annotation. For example:
import org.springframework.security.core.annotation.AuthenticationPrincipal; // ... @RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) { // .. find messages for this user and return them ... }
Sometimes it may be necessary to transform the principal in some way.
For example, if CustomUser
needed to be final it could not be extended.
In this situation the UserDetailsService
might returns an Object
that implements UserDetails
and provides a method named getCustomUser
to access CustomUser
.
For example, it might look like:
public class CustomUserUserDetails extends User { // ... public CustomUser getCustomUser() { return customUser; } }
We could then access the CustomUser
using a SpEL expression that uses Authentication.getPrincipal()
as the root object:
import org.springframework.security.core.annotation.AuthenticationPrincipal; // ... @RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") CustomUser customUser) { // .. find messags for this user and return them ... }
We can also refer to Beans in our SpEL expressions. For example, the following could be used if we were using JPA to manage our Users and we wanted to modify and save a property on the current user.
import org.springframework.security.core.annotation.AuthenticationPrincipal; // ... @PutMapping("/users/self") public ModelAndView updateName(@AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") CustomUser attachedCustomUser, @RequestParam String firstName) { // change the firstName on an attached instance which will be persisted to the database attachedCustomUser.setFirstName(firstName); // ... }
We can further remove our dependency on Spring Security by making @AuthenticationPrincipal
a meta annotation on our own annotation.
Below we demonstrate how we could do this on an annotation named @CurrentUser
.
Note | |
---|---|
It is important to realize that in order to remove the dependency on Spring Security, it is the consuming application that would create |
@Target({ElementType.PARAMETER, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @AuthenticationPrincipal public @interface CurrentUser {}
Now that @CurrentUser
has been specified, we can use it to signal to resolve our CustomUser
of the currently authenticated user.
We have also isolated our dependency on Spring Security to a single file.
@RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) { // .. find messages for this user and return them ... }
Spring Web MVC 3.2+ has excellent support for Asynchronous Request Processing.
With no additional configuration, Spring Security will automatically setup the SecurityContext
to the Thread
that executes a Callable
returned by your controllers.
For example, the following method will automatically have its Callable
executed with the SecurityContext
that was available when the Callable
was created:
@RequestMapping(method=RequestMethod.POST) public Callable<String> processUpload(final MultipartFile file) { return new Callable<String>() { public Object call() throws Exception { // ... return "someView"; } }; }
Associating SecurityContext to Callable’s | |
---|---|
More technically speaking, Spring Security integrates with |
There is no automatic integration with a DeferredResult
that is returned by controllers.
This is because DeferredResult
is processed by the users and thus there is no way of automatically integrating with it.
However, you can still use Concurrency Support to provide transparent integration with Spring Security.
Spring Security will automatically include the CSRF Token within forms that use the Spring MVC form tag. For example, the following JSP:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:form="http://www.springframework.org/tags/form" version="2.0"> <jsp:directive.page language="java" contentType="text/html" /> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <!-- ... --> <c:url var="logoutUrl" value="/logout"/> <form:form action="${logoutUrl}" method="post"> <input type="submit" value="Log out" /> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> </form:form> <!-- ... --> </html> </jsp:root>
Will output HTML that is similar to the following:
<!-- ... --> <form action="/context/logout" method="post"> <input type="submit" value="Log out"/> <input type="hidden" name="_csrf" value="f81d4fae-7dec-11d0-a765-00a0c91e6bf6"/> </form> <!-- ... -->
Spring Security provides CsrfTokenArgumentResolver
which can automatically resolve the current CsrfToken
for Spring MVC arguments.
By using @EnableWebSecurity you will automatically have this added to your Spring MVC configuration.
If you use XML based configuraiton, you must add this yourself.
Once CsrfTokenArgumentResolver
is properly configured, you can expose the CsrfToken
to your static HTML based application.
@RestController public class CsrfController { @RequestMapping("/csrf") public CsrfToken csrf(CsrfToken token) { return token; } }
It is important to keep the CsrfToken
a secret from other domains.
This means if you are using Cross Origin Sharing (CORS), you should NOT expose the CsrfToken
to any external domains.
[19] Note that this is different from the default configuration of the underlying DefaultLdapAuthoritiesPopulator
which uses member={0}
.
[20] 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.
[21] The legacy options from Spring Security 2.0 are also supported, but discouraged.