As we'll see 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, ConfigAttributeDefinition config) throws AccessDeniedException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
As can be seen from the first method, the
AccessDecisionManager is passed via method
parameters all information that is likely to be of value in assessing
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 22.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, ConfigAttributeDefinition 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
AccessDecisionManagers 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.