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, List<ConfigAttribute> config) 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 13.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, List<ConfigAttribute> config); 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
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. RoleVoter
is case sensitive on comparisons as well as
the ROLE_
prefix.
It is also possible to implement a custom
AccessDecisionVoter
. Several examples are provided in
Spring Security unit tests, including ContactSecurityVoter
and
DenyVoter
. The ContactSecurityVoter
abstains
from voting decisions where a CONTACT_OWNED_BY_CURRENT_USER
ConfigAttribute
is not found. If voting, it queries the
MethodInvocation
to extract the owner of the
Contact
object that is subject of the method call. It votes to
grant access if the Contact
owner matches the principal presented in
the Authentication
object. It could have just as easily
compared the Contact
owner with some
GrantedAuthority
the
Authentication
object presented. All of this is
achieved with relatively few lines of code and demonstrates the flexibility of the
authorization model.
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 13.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. Let's now consider
our ACL-aware implementations of AfterInvocationProvider
.
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
A common services layer method we've all written at one stage or another looks like this:
public Contact getById(Integer id);
Quite often, only principals with permission to read the Contact
should be allowed to obtain it. In this situation the
AccessDecisionManager
approach provided by the
AbstractSecurityInterceptor
will not suffice. This is because the
identity of the Contact
is all that is available before the secure object
is invoked. The AclEntryAfterInvocationProvider
delivers a solution,
and is configured as follows:
<bean id="afterAclRead" class="org.springframework.security.acls.afterinvocation.AclEntryAfterInvocationProvider"> <constructor-arg ref="aclService"/> <constructor-arg> <list> <ref local="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/> <ref local="org.springframework.security.acls.domain.BasePermission.READ"/> </list> </constructor-arg> </bean>
In the above example, the Contact
will be retrieved and
passed to the AclEntryAfterInvocationProvider
. The provider will
thrown an AccessDeniedException
if one of the listed
requirePermission
s is not held by the
Authentication
. The
AclEntryAfterInvocationProvider
queries the acl service to
determine the ACL that applies for this domain object to this
Authentication
.
Similar to the AclEntryAfterInvocationProvider
is
AclEntryAfterInvocationCollectionFilteringProvider
. It is designed
to remove Collection
or array elements for which a principal does not
have access. It never thrown an AccessDeniedException
- simply
silently removes the offending elements. The provider is configured as follows:
<bean id="afterAclCollectionRead" class="org.springframework.security.acls.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider"> <constructor-arg ref="aclService"/> <constructor-arg> <list> <ref local="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/> <ref local="org.springframework.security.acls.domain.BasePermission.READ"/> </list> </constructor-arg> </bean>
As you can imagine, the returned Object
must be a
Collection
or array for this provider to operate. It will remove any
element if the AclManager
indicates the
Authentication
does not hold one of the listed
requirePermission
s.
The Contacts sample application demonstrates these two
AfterInvocationProvider
s.
AuthorizeTag
is used to include content if the current principal holds
certain GrantedAuthority
s.
The following JSP fragment illustrates how to use the
AuthorizeTag
:
<security:authorize ifAllGranted="ROLE_SUPERVISOR"> <td> <a href="del.htm?id=<c:out value="${contact.id}"/>">Del</a> </td> </security:authorize>
This tag would cause the tag's body to be output if the principal has been granted ROLE_SUPERVISOR.
The security:authorize
tag declares the following attributes:
ifAllGranted
: All the listed roles must be granted for the tag to
output its body.
ifAnyGranted
: Any of the listed roles must be granted for the tag
to output its body.
ifNotGranted
: None of the listed roles must be granted for the
tag to output its body.
You'll note that in each attribute you can list multiple roles. Simply separate the roles
using a comma. The authorize
tag ignores whitespace in attributes.
The tag library logically ANDs all of it's parameters together. This means that if you
combine two or more attributes, all attributes must be true for the tag to output it's body.
Don't add an ifAllGranted="ROLE_SUPERVISOR"
, followed by an
ifNotGranted="ROLE_SUPERVISOR"
, or you'll be surprised to never see the
tag's body.
By requiring all attributes to return true, the authorize tag allows you to create more
complex authorization scenarios. For example, you could declare an
ifAllGranted="ROLE_SUPERVISOR"
and an
ifNotGranted="ROLE_NEWBIE_SUPERVISOR"
in the same tag, in order to
prevent new supervisors from seeing the tag body. However it would no doubt be simpler to use
ifAllGranted="ROLE_EXPERIENCED_SUPERVISOR"
rather than inserting NOT
conditions into your design.
One last item: the tag verifies the authorizations in a specific order: first
ifNotGranted
, then ifAllGranted
, and finally,
if AnyGranted
.
AccessControlListTag
is used to include content if the current
principal has an ACL to the indicated domain object.
The following JSP fragment illustrates how to use the
AccessControlListTag
:
<security:accesscontrollist domainObject="${contact}" hasPermission="8,16"> <td><a href="<c:url value="del.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Del</a></td> </security:accesscontrollist>
This tag would cause the tag's body to be output if the principal holds either
permission 16 or permission 1 for the "contact" domain object. The numbers are actually
integers that are used with BasePermission
bit masking. Please refer to the
ACL section of this reference guide to understand more about the ACL capabilities of Spring
Security.
AclTag
is part of the old ACL module and should be considered
deprecated. For the sake of historical reference, works exactly the samae as
AccessControlListTag
.