Let's explore some of the most important shared components in Spring Security. Components are considered "shared" if they are central to the framework and the framework cannot operate without them. These Java types represent the building blocks of the remaining system, so it's important to understand that they're there, even if you don't need to directly interact with them.
The most fundamental object is
SecurityContextHolder
. This is where we store
details of the present security context of the application, which
includes details of the principal currently using the application. By
default the SecurityContextHolder
uses a
ThreadLocal
to store these details, which means
that the security context is always available to methods in the same
thread of execution, even if the security context is not explicitly
passed around as an argument to those methods. Using a
ThreadLocal
in this way is quite safe if care is
taken to clear the thread after the present principal's request is
processed. Of course, Spring Security takes care of this for you
automatically so there is no need to worry about it.
Some applications aren't entirely suitable for using a
ThreadLocal
, because of the specific way they work
with threads. For example, a Swing client might want all threads in a
Java Virtual Machine to use the same security context. For this
situation you would use the
SecurityContextHolder.MODE_GLOBAL
. Other
applications might want to have threads spawned by the secure thread
also assume the same security identity. This is achieved by using
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
.
You can change the mode from the default
SecurityContextHolder.MODE_THREADLOCAL
in two ways.
The first is to set a system property. Alternatively, call a static
method on SecurityContextHolder
. Most applications
won't need to change from the default, but if you do, take a look at
the JavaDocs for SecurityContextHolder
to learn
more.
Inside the SecurityContextHolder
we store
details of the principal currently interacting with the application.
Spring Security uses an Authentication
object to
represent this information. Whilst you won't normally need to create
an Authentication
object yourself, it is fairly
common for users to query the Authentication
object. You can use the following code block - from anywhere in your
application - to obtain the name of the authenticated user, for example:
Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (obj instanceof UserDetails) { String username = ((UserDetails)obj).getUsername(); } else { String username = obj.toString(); }
The above code introduces a number of interesting relationships
and key objects. First, you will notice that there is an intermediate
object between SecurityContextHolder
and
Authentication
. The
SecurityContextHolder.getContext()
method is
actually returning a SecurityContext
.
Another item to note from the above code fragment is that you
can obtain a principal from the Authentication
object. The principal is just an Object
. Most of
the time this can be cast into a UserDetails
object. UserDetails
is a central interface in
Spring Security. It represents a principal, but in an extensible and
application-specific way. Think of UserDetails
as
the adapter between your own user database and what Spring Security
needs inside the SecurityContextHolder
. Being a
representation of something from your own user database, quite often
you will cast the UserDetails
to the original
object that your application provided, so you can call
business-specific methods (like getEmail()
,
getEmployeeNumber()
and so on).
By now you're probably wondering, so when do I provide a
UserDetails
object? How do I do that? I thought you
said this thing was declarative and I didn't need to write any Java
code - what gives? The short answer is that there is a special
interface called UserDetailsService
. The only
method on this interface accepts a String
-based
username argument and returns a UserDetails
. Most
authentication providers that ship with Spring Security delegate to a
UserDetailsService
as part of the authentication
process. The UserDetailsService
is used to build
the Authentication
object that is stored in the
SecurityContextHolder
. The good news is that we
provide a number of UserDetailsService
implementations, including one that uses an in-memory map and another
that uses JDBC. Most users tend to write their own, though, with such
implementations often simply sitting on top of an existing Data Access
Object (DAO) that represents their employees, customers, or other
users of the enterprise application. Remember the advantage that
whatever your UserDetailsService returns can always be obtained from
the SecurityContextHolder
, as per the above code
fragment.
Besides the principal, another important method provided by
Authentication
is
getAuthorities(
). This method provides an array of
GrantedAuthority
objects. A
GrantedAuthority
is, not surprisingly, an authority
that is granted to the principal. Such authorities are usually
"roles", such as ROLE_ADMINISTRATOR
or
ROLE_HR_SUPERVISOR
. These roles are later on
configured for web authorization, method authorization and domain
object authorization. Other parts of Spring Security are capable of
interpreting these authorities, and expect them to be present.
GrantedAuthority
objects are usually loaded by the
UserDetailsService
.
Usually the GrantedAuthority
objects are
application-wide permissions. They are not specific to a given domain
object. Thus, you wouldn't likely have a
GrantedAuthority
to represent a permission to
Employee
object number 54, because if there are
thousands of such authorities you would quickly run out of memory (or,
at the very least, cause the application to take a long time to
authenticate a user). Of course, Spring Security is expressly designed
to handle this common requirement, but you'd instead use the project's
domain object security capabilities for this purpose.
Last but not least, sometimes you will need to store the
SecurityContext
between HTTP requests. Other times
the principal will re-authenticate on every request, although most of
the time it will be stored. The
HttpSessionContextIntegrationFilter
is responsible
for storing a SecurityContext
between HTTP
requests. As suggested by the name of the class, the
HttpSession
is used to store this information. You
should never interact directly with the HttpSession
for security purposes. There is simply no justification for doing so -
always use the SecurityContextHolder
instead.
Just to recap, the major building blocks of Spring Security are:
SecurityContextHolder
, to provide any
type access to the SecurityContext
.
SecurityContext
, to hold the
Authentication
and possibly request-specific
security information.
HttpSessionContextIntegrationFilter
, to
store the SecurityContext
in the
HttpSession
between web requests.
Authentication
, to represent the
principal in a Spring Security-specific manner.
GrantedAuthority
, to reflect the
application-wide permissions granted to a principal.
UserDetails
, to provide the necessary
information to build an Authentication object from your
application's DAOs.
UserDetailsService
, to create a
UserDetails
when passed in a
String
-based username (or certificate ID or
alike).
Now that you've gained an understanding of these repeatedly-used components, let's take a closer look at the process of authentication.