As we saw in the technical overview,
all Authentication
implementations store a list of
GrantedAuthority
objects. These represent the authorities
that have been granted to the principal. The
GrantedAuthority
objects are inserted into the
Authentication
object by the
AuthenticationManager
and are later read by
AccessDecisionManager
s when making authorization
decisions.
GrantedAuthority
is an interface with only one method:
String getAuthority();
This method allows
AccessDecisionManager
s to obtain a precise
String
representation of the
GrantedAuthority
. By returning a representation as a
String
, a GrantedAuthority
can be
easily “read” by most
AccessDecisionManager
s. If a
GrantedAuthority
cannot be precisely represented as a
String
, the GrantedAuthority
is
considered “complex” and getAuthority()
must return
null
.
An example of a “complex” GrantedAuthority
would be an implementation that stores a list of operations and authority thresholds
that apply to different customer account numbers. Representing this complex
GrantedAuthority
as a String
would be
quite difficult, and as a result the getAuthority()
method should
return null
. This will indicate to any
AccessDecisionManager
that it will need to specifically
support the GrantedAuthority
implementation in order to
understand its contents.
Spring Security includes one concrete GrantedAuthority
implementation, GrantedAuthorityImpl
. This allows any user-specified
String
to be converted into a
GrantedAuthority
. All
AuthenticationProvider
s included with the security architecture
use GrantedAuthorityImpl
to populate the
Authentication
object.
As we've also seen in the Technical
Overview chapter, Spring Security provides interceptors which control access to
secure objects such as method invocations or web requests. A pre-invocation decision on
whether the invocation is allowed to proceed is made by the
AccessDecisionManager
.
The AccessDecisionManager
is called by the
AbstractSecurityInterceptor
and is responsible for making
final access control decisions. The
AccessDecisionManager
interface contains three
methods:
void decide(Authentication authentication, Object secureObject, Collection<ConfigAttribute> attrs) throws AccessDeniedException; boolean supports(ConfigAttribute attribute); boolean supports(Class clazz);
The AccessDecisionManager
's
decide
method is passed all the relevant information it
needs in order to make an authorization decision. In particular, passing the secure
Object
enables those arguments contained in the actual secure
object invocation to be inspected. For example, let's assume the secure object was a
MethodInvocation
. It would be easy to query the
MethodInvocation
for any Customer
argument, and then implement some sort of security logic in the
AccessDecisionManager
to ensure the principal is
permitted to operate on that customer. Implementations are expected to throw an
AccessDeniedException
if access is denied.
The supports(ConfigAttribute)
method is called by the
AbstractSecurityInterceptor
at startup time to determine if
the AccessDecisionManager
can process the passed
ConfigAttribute
. The supports(Class)
method is
called by a security interceptor implementation to ensure the configured
AccessDecisionManager
supports the type of secure
object that the security interceptor will present.
Whilst users can implement their own
AccessDecisionManager
to control all aspects of
authorization, Spring Security includes several
AccessDecisionManager
implementations that are based
on voting. Figure 14.1, “Voting Decision Manager” illustrates the relevant
classes.
Using this approach, a series of
AccessDecisionVoter
implementations are polled on an
authorization decision. The AccessDecisionManager
then decides whether or not to throw an AccessDeniedException
based on its assessment of the votes.
The AccessDecisionVoter
interface has three
methods:
int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs); boolean supports(ConfigAttribute attribute); boolean supports(Class clazz);
Concrete implementations return an int
, with possible values
being reflected in the AccessDecisionVoter
static
fields ACCESS_ABSTAIN
, ACCESS_DENIED
and
ACCESS_GRANTED
. A voting implementation will return
ACCESS_ABSTAIN
if it has no opinion on an authorization decision.
If it does have an opinion, it must return either ACCESS_DENIED
or ACCESS_GRANTED
.
There are three concrete AccessDecisionManager
s
provided with Spring Security that tally the votes. The
ConsensusBased
implementation will grant or deny access based on
the consensus of non-abstain votes. Properties are provided to control behavior in
the event of an equality of votes or if all votes are abstain. The
AffirmativeBased
implementation will grant access if one or more
ACCESS_GRANTED
votes were received (i.e. a deny vote will be
ignored, provided there was at least one grant vote). Like the
ConsensusBased
implementation, there is a parameter that controls
the behavior if all voters abstain. The UnanimousBased
provider
expects unanimous ACCESS_GRANTED
votes in order to grant access,
ignoring abstains. It will deny access if there is any
ACCESS_DENIED
vote. Like the other implementations, there is a
parameter that controls the behaviour if all voters abstain.
It is possible to implement a custom
AccessDecisionManager
that tallies votes differently.
For example, votes from a particular
AccessDecisionVoter
might receive additional
weighting, whilst a deny vote from a particular voter may have a veto effect.
The most commonly used AccessDecisionVoter
provided with Spring Security is the simple RoleVoter
,
which treats configuration attributes as simple role names and votes to grant
access if the user has been assigned that role.
It will vote if any ConfigAttribute
begins with
the prefix ROLE_
. It will vote to grant access if there is a
GrantedAuthority
which returns a
String
representation (via the
getAuthority()
method) exactly equal to one or more
ConfigAttributes
starting with the prefix
ROLE_
. If there is no exact match of any
ConfigAttribute
starting with ROLE_
, the
RoleVoter
will vote to deny access. If no
ConfigAttribute
begins with ROLE_
, the
voter will abstain.
Another voter which we've implicitly seen is the
AuthenticatedVoter
, which can be used to differentiate
between anonymous, fully-authenticated and remember-me authenticated users. Many
sites allow certain limited access under remember-me authentication, but require
a user to confirm their identity by logging in for full access.
When we've used the attribute IS_AUTHENTICATED_ANONYMOUSLY
to grant anonymous access, this attribute was being processed by the
AuthenticatedVoter
. See the Javadoc for this class for
more information.
Obviously, you can also implement a custom
AccessDecisionVoter
and you can
put just about any access-control logic you want in it. It might
be specific to your application (business-logic related) or it
might implement some security administration logic. For example, you'll find
a
blog article on the SpringSource web site which describes how to
use a voter to deny access in real-time to users whose accounts have
been suspended.
Whilst the AccessDecisionManager
is called by the
AbstractSecurityInterceptor
before proceeding with the secure
object invocation, some applications need a way of modifying the object actually
returned by the secure object invocation. Whilst you could easily implement your own AOP
concern to achieve this, Spring Security provides a convenient hook that has several
concrete implementations that integrate with its ACL capabilities.
Figure 14.2, “After Invocation Implementation” illustrates Spring Security's
AfterInvocationManager
and its concrete implementations.
Like many other parts of Spring Security, AfterInvocationManager
has a single concrete implementation, AfterInvocationProviderManager
,
which polls a list of AfterInvocationProvider
s. Each
AfterInvocationProvider
is allowed to modify the return object or
throw an AccessDeniedException
. Indeed multiple providers can modify
the object, as the result of the previous provider is passed to the next in the
list.
Please be aware that if you're using AfterInvocationManager
, you
will still need configuration attributes that allow the
MethodSecurityInterceptor
's
AccessDecisionManager
to allow an operation. If you're
using the typical Spring Security included
AccessDecisionManager
implementations, having no
configuration attributes defined for a particular secure method invocation will cause
each AccessDecisionVoter
to abstain from voting. In turn,
if the AccessDecisionManager
property
"allowIfAllAbstainDecisions
" is false
, an
AccessDeniedException
will be thrown. You may avoid this potential
issue by either (i) setting "allowIfAllAbstainDecisions
" to
true
(although this is generally not recommended) or (ii) simply
ensure that there is at least one configuration attribute that an
AccessDecisionVoter
will vote to grant access for. This
latter (recommended) approach is usually achieved through a ROLE_USER
or ROLE_AUTHENTICATED
configuration attribute.
It is a common requirement that a particular role in an application should automatically “include” other roles. For example, in an application which has the concept of an “admin” and a “user” role, you may want an admin to be able to do everything a normal user can. To achieve this, you can either make sure that all admin users are also assigned the “user” role. Alternatively, you can modify every access constraint which requires the “user” role to also include the “admin” role. This can get quite complicated if you have a lot of different roles in your application.
The use of a role-hierarchy allows you to configure which roles (or authorities) should include others.
An extended version of Spring Security's RoleVoter
,
RoleHierarchyVoter
, is configured with a RoleHierarchy
,
from which it obtains all the “reachable authorities” which the user is assigned.
A typical configuration might look like this:
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter"> <constructor-arg ref="roleHierarchy" /> </bean> <bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl"> <property name="hierarchy"> <value> ROLE_ADMIN > ROLE_STAFF ROLE_STAFF > ROLE_USER ROLE_USER > ROLE_GUEST </value> </property> </bean>
Here we have four roles in a hierarchy ROLE_ADMIN => ROLE_STAFF => ROLE_USER => ROLE_GUEST
.
A user who is authenticated with ROLE_ADMIN
, will behave as if they have all four roles when
security contraints are evaluated against an AccessDecisionManager
cconfigured
with the above RoleHierarchyVoter
. The >
symbol can be thought of
as meaning “includes”.
Role hierarchies offer a convenient means of simplifying the access-control configuration data for your application and/or reducing the number of authorities which you need to assign to a user. For more complex requirements you may wish to define a logical mapping between the specific access-rights your application requires and the roles that are assigned to users, translating between the two when loading the user information.