mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 09:12:14 +00:00
346 lines
23 KiB
XML
346 lines
23 KiB
XML
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="authz-arch"
|
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
<info>
|
|
<title>Authorization Architecture</title>
|
|
</info>
|
|
<section xml:id="authz-authorities">
|
|
<info>
|
|
<title>Authorities</title>
|
|
</info>
|
|
<para>As we saw in the <link linkend="tech-granted-authority">technical overview</link>,
|
|
all <interfacename>Authentication</interfacename> implementations store a list of
|
|
<interfacename>GrantedAuthority</interfacename> objects. These represent the authorities
|
|
that have been granted to the principal. The
|
|
<interfacename>GrantedAuthority</interfacename> objects are inserted into the
|
|
<interfacename>Authentication</interfacename> object by the
|
|
<interfacename>AuthenticationManager</interfacename> and are later read by
|
|
<interfacename>AccessDecisionManager</interfacename>s when making authorization
|
|
decisions.</para>
|
|
<para><interfacename>GrantedAuthority</interfacename> is an interface with only one method:
|
|
<programlisting language="java">
|
|
String getAuthority();
|
|
</programlisting> This method allows
|
|
<interfacename>AccessDecisionManager</interfacename>s to obtain a precise
|
|
<literal>String</literal> representation of the
|
|
<interfacename>GrantedAuthority</interfacename>. By returning a representation as a
|
|
<literal>String</literal>, a <interfacename>GrantedAuthority</interfacename> can be
|
|
easily <quote>read</quote> by most
|
|
<interfacename>AccessDecisionManager</interfacename>s. If a
|
|
<interfacename>GrantedAuthority</interfacename> cannot be precisely represented as a
|
|
<literal>String</literal>, the <interfacename>GrantedAuthority</interfacename> is
|
|
considered <quote>complex</quote> and <literal>getAuthority()</literal> must return
|
|
<literal>null</literal>.</para>
|
|
<para>An example of a <quote>complex</quote> <interfacename>GrantedAuthority</interfacename>
|
|
would be an implementation that stores a list of operations and authority thresholds
|
|
that apply to different customer account numbers. Representing this complex
|
|
<interfacename>GrantedAuthority</interfacename> as a <literal>String</literal> would be
|
|
quite difficult, and as a result the <literal>getAuthority()</literal> method should
|
|
return <literal>null</literal>. This will indicate to any
|
|
<interfacename>AccessDecisionManager</interfacename> that it will need to specifically
|
|
support the <interfacename>GrantedAuthority</interfacename> implementation in order to
|
|
understand its contents.</para>
|
|
<para>Spring Security includes one concrete <interfacename>GrantedAuthority</interfacename>
|
|
implementation, <literal>GrantedAuthorityImpl</literal>. This allows any user-specified
|
|
<literal>String</literal> to be converted into a
|
|
<interfacename>GrantedAuthority</interfacename>. All
|
|
<classname>AuthenticationProvider</classname>s included with the security architecture
|
|
use <literal>GrantedAuthorityImpl</literal> to populate the
|
|
<interfacename>Authentication</interfacename> object.</para>
|
|
</section>
|
|
<section xml:id="authz-pre-invocation">
|
|
<info>
|
|
<title>Pre-Invocation Handling</title>
|
|
</info>
|
|
<para> As we've also seen in the <link linkend="secure-objects">Technical
|
|
Overview</link> 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
|
|
<interfacename>AccessDecisionManager</interfacename>. </para>
|
|
<section xml:id="authz-access-decision-manager">
|
|
<title>The AccessDecisionManager</title>
|
|
<para>The <interfacename>AccessDecisionManager</interfacename> is called by the
|
|
<classname>AbstractSecurityInterceptor</classname> and is responsible for making
|
|
final access control decisions. The
|
|
<interfacename>AccessDecisionManager</interfacename> interface contains three
|
|
methods:
|
|
<programlisting language="java">
|
|
void decide(Authentication authentication, Object secureObject,
|
|
Collection<ConfigAttribute> attrs) throws AccessDeniedException;
|
|
boolean supports(ConfigAttribute attribute);
|
|
boolean supports(Class clazz);
|
|
</programlisting>
|
|
The <interfacename>AccessDecisionManager</interfacename>'s
|
|
<methodname>decide</methodname> method is passed all the relevant information it
|
|
needs in order to make an authorization decision. In particular, passing the secure
|
|
<literal>Object</literal> enables those arguments contained in the actual secure
|
|
object invocation to be inspected. For example, let's assume the secure object was a
|
|
<classname>MethodInvocation</classname>. It would be easy to query the
|
|
<classname>MethodInvocation</classname> for any <literal>Customer</literal>
|
|
argument, and then implement some sort of security logic in the
|
|
<interfacename>AccessDecisionManager</interfacename> to ensure the principal is
|
|
permitted to operate on that customer. Implementations are expected to throw an
|
|
<literal>AccessDeniedException</literal> if access is denied.</para>
|
|
<para>The <literal>supports(ConfigAttribute)</literal> method is called by the
|
|
<classname>AbstractSecurityInterceptor</classname> at startup time to determine if
|
|
the <interfacename>AccessDecisionManager</interfacename> can process the passed
|
|
<literal>ConfigAttribute</literal>. The <literal>supports(Class)</literal> method is
|
|
called by a security interceptor implementation to ensure the configured
|
|
<interfacename>AccessDecisionManager</interfacename> supports the type of secure
|
|
object that the security interceptor will present.</para>
|
|
</section>
|
|
<section xml:id="authz-voting-based">
|
|
<title>Voting-Based AccessDecisionManager Implementations</title>
|
|
<para>Whilst users can implement their own
|
|
<interfacename>AccessDecisionManager</interfacename> to control all aspects of
|
|
authorization, Spring Security includes several
|
|
<interfacename>AccessDecisionManager</interfacename> implementations that are based
|
|
on voting. <xref linkend="authz-access-voting"/> illustrates the relevant
|
|
classes.</para>
|
|
<figure xml:id="authz-access-voting">
|
|
<title>Voting Decision Manager</title>
|
|
<mediaobject>
|
|
<imageobject>
|
|
<imagedata align="center" fileref="images/access-decision-voting.png"
|
|
format="PNG" scale="75"/>
|
|
</imageobject>
|
|
</mediaobject>
|
|
</figure>
|
|
<para>Using this approach, a series of
|
|
<interfacename>AccessDecisionVoter</interfacename> implementations are polled on an
|
|
authorization decision. The <interfacename>AccessDecisionManager</interfacename>
|
|
then decides whether or not to throw an <literal>AccessDeniedException</literal>
|
|
based on its assessment of the votes.</para>
|
|
<para>The <interfacename>AccessDecisionVoter</interfacename> interface has three
|
|
methods:
|
|
<programlisting language="java">
|
|
int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);
|
|
boolean supports(ConfigAttribute attribute);
|
|
boolean supports(Class clazz);
|
|
</programlisting>
|
|
Concrete implementations return an <literal>int</literal>, with possible values
|
|
being reflected in the <interfacename>AccessDecisionVoter</interfacename> static
|
|
fields <literal>ACCESS_ABSTAIN</literal>, <literal>ACCESS_DENIED</literal> and
|
|
<literal>ACCESS_GRANTED</literal>. A voting implementation will return
|
|
<literal>ACCESS_ABSTAIN</literal> if it has no opinion on an authorization decision.
|
|
If it does have an opinion, it must return either <literal>ACCESS_DENIED</literal>
|
|
or <literal>ACCESS_GRANTED</literal>.</para>
|
|
<para>There are three concrete <interfacename>AccessDecisionManager</interfacename>s
|
|
provided with Spring Security that tally the votes. The
|
|
<literal>ConsensusBased</literal> 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
|
|
<literal>AffirmativeBased</literal> implementation will grant access if one or more
|
|
<literal>ACCESS_GRANTED</literal> votes were received (i.e. a deny vote will be
|
|
ignored, provided there was at least one grant vote). Like the
|
|
<literal>ConsensusBased</literal> implementation, there is a parameter that controls
|
|
the behavior if all voters abstain. The <literal>UnanimousBased</literal> provider
|
|
expects unanimous <literal>ACCESS_GRANTED</literal> votes in order to grant access,
|
|
ignoring abstains. It will deny access if there is any
|
|
<literal>ACCESS_DENIED</literal> vote. Like the other implementations, there is a
|
|
parameter that controls the behaviour if all voters abstain.</para>
|
|
<para>It is possible to implement a custom
|
|
<interfacename>AccessDecisionManager</interfacename> that tallies votes differently.
|
|
For example, votes from a particular
|
|
<interfacename>AccessDecisionVoter</interfacename> might receive additional
|
|
weighting, whilst a deny vote from a particular voter may have a veto effect.</para>
|
|
<section xml:id="authz-role-voter">
|
|
<title><classname>RoleVoter</classname></title>
|
|
<para> The most commonly used <interfacename>AccessDecisionVoter</interfacename>
|
|
provided with Spring Security is the simple <classname>RoleVoter</classname>,
|
|
which treats configuration attributes as simple role names and votes to grant
|
|
access if the user has been assigned that role.</para>
|
|
<para>It will vote if any <interfacename>ConfigAttribute</interfacename> begins with
|
|
the prefix <literal>ROLE_</literal>. It will vote to grant access if there is a
|
|
<interfacename>GrantedAuthority</interfacename> which returns a
|
|
<literal>String</literal> representation (via the
|
|
<literal>getAuthority()</literal> method) exactly equal to one or more
|
|
<literal>ConfigAttributes</literal> starting with the prefix
|
|
<literal>ROLE_</literal>. If there is no exact match of any
|
|
<literal>ConfigAttribute</literal> starting with <literal>ROLE_</literal>, the
|
|
<literal>RoleVoter</literal> will vote to deny access. If no
|
|
<literal>ConfigAttribute</literal> begins with <literal>ROLE_</literal>, the
|
|
voter will abstain.</para>
|
|
</section>
|
|
<section xml:id="authz-authenticated-voter">
|
|
<title><classname>AuthenticatedVoter</classname></title>
|
|
<para> Another voter which we've implicitly seen is the
|
|
<classname>AuthenticatedVoter</classname>, 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.</para>
|
|
<para>When we've used the attribute <literal>IS_AUTHENTICATED_ANONYMOUSLY</literal>
|
|
to grant anonymous access, this attribute was being processed by the
|
|
<classname>AuthenticatedVoter</classname>. See the Javadoc for this class for
|
|
more information. </para>
|
|
</section>
|
|
<section xml:id="authz-custom-voter">
|
|
<title>Custom Voters</title>
|
|
<para>Obviously, you can also implement a custom
|
|
<interfacename>AccessDecisionVoter</interfacename> 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 <link xlink:href='http://blog.springsource.com/2009/01/02/spring-security-customization-part-2-adjusting-secured-session-in-real-time/'>
|
|
blog article</link> 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.
|
|
</para>
|
|
</section>
|
|
</section>
|
|
</section>
|
|
<section xml:id="authz-after-invocation-handling">
|
|
<info>
|
|
<title>After Invocation Handling</title>
|
|
</info>
|
|
<para>Whilst the <interfacename>AccessDecisionManager</interfacename> is called by the
|
|
<classname>AbstractSecurityInterceptor</classname> 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.</para>
|
|
<para><xref linkend="authz-after-invocation"/> illustrates Spring Security's
|
|
<literal>AfterInvocationManager</literal> and its concrete implementations. <figure
|
|
xml:id="authz-after-invocation">
|
|
<title>After Invocation Implementation</title>
|
|
<mediaobject>
|
|
<imageobject>
|
|
<imagedata align="center" fileref="images/after-invocation.png" format="PNG"
|
|
scale="75"/>
|
|
</imageobject>
|
|
</mediaobject>
|
|
</figure></para>
|
|
<para>Like many other parts of Spring Security, <literal>AfterInvocationManager</literal>
|
|
has a single concrete implementation, <literal>AfterInvocationProviderManager</literal>,
|
|
which polls a list of <literal>AfterInvocationProvider</literal>s. Each
|
|
<literal>AfterInvocationProvider</literal> is allowed to modify the return object or
|
|
throw an <literal>AccessDeniedException</literal>. Indeed multiple providers can modify
|
|
the object, as the result of the previous provider is passed to the next in the
|
|
list.</para>
|
|
<para>Please be aware that if you're using <literal>AfterInvocationManager</literal>, you
|
|
will still need configuration attributes that allow the
|
|
<classname>MethodSecurityInterceptor</classname>'s
|
|
<interfacename>AccessDecisionManager</interfacename> to allow an operation. If you're
|
|
using the typical Spring Security included
|
|
<interfacename>AccessDecisionManager</interfacename> implementations, having no
|
|
configuration attributes defined for a particular secure method invocation will cause
|
|
each <interfacename>AccessDecisionVoter</interfacename> to abstain from voting. In turn,
|
|
if the <interfacename>AccessDecisionManager</interfacename> property
|
|
"<literal>allowIfAllAbstainDecisions</literal>" is <literal>false</literal>, an
|
|
<literal>AccessDeniedException</literal> will be thrown. You may avoid this potential
|
|
issue by either (i) setting "<literal>allowIfAllAbstainDecisions</literal>" to
|
|
<literal>true</literal> (although this is generally not recommended) or (ii) simply
|
|
ensure that there is at least one configuration attribute that an
|
|
<interfacename>AccessDecisionVoter</interfacename> will vote to grant access for. This
|
|
latter (recommended) approach is usually achieved through a <literal>ROLE_USER</literal>
|
|
or <literal>ROLE_AUTHENTICATED</literal> configuration attribute.</para>
|
|
<!-- TODO: Move to ACL section and add reference here -->
|
|
<!--
|
|
<section xml:id="after-invocation-acl-aware">
|
|
<info>
|
|
<title>ACL-Aware AfterInvocationProviders</title>
|
|
</info>
|
|
|
|
<para>A common services layer method we've all written at one stage or another looks like
|
|
this:</para>
|
|
<para>
|
|
<programlisting language="java">public Contact getById(Integer id);</programlisting>
|
|
</para>
|
|
<para>Quite often, only principals with permission to read the <literal>Contact</literal>
|
|
should be allowed to obtain it. In this situation the
|
|
<interfacename>AccessDecisionManager</interfacename> approach provided by the
|
|
<classname>AbstractSecurityInterceptor</classname> will not suffice. This is because the
|
|
identity of the <literal>Contact</literal> is all that is available before the secure object
|
|
is invoked. The <classname>AclEntryAfterInvocationProvider</classname> delivers a solution,
|
|
and is configured as follows: <programlisting language="xml"><![CDATA[
|
|
<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>
|
|
]]></programlisting> In the above example, the <literal>Contact</literal> will be retrieved and
|
|
passed to the <classname>AclEntryAfterInvocationProvider</classname>. The provider will
|
|
thrown an <classname>AccessDeniedException</classname> if one of the listed
|
|
<literal>requirePermission</literal>s is not held by the
|
|
<interfacename>Authentication</interfacename>. The
|
|
<classname>AclEntryAfterInvocationProvider</classname> queries the acl service to
|
|
determine the ACL that applies for this domain object to this
|
|
<interfacename>Authentication</interfacename>.</para>
|
|
<para>Similar to the <classname>AclEntryAfterInvocationProvider</classname> is
|
|
<classname>AclEntryAfterInvocationCollectionFilteringProvider</classname>. It is designed
|
|
to remove <literal>Collection</literal> or array elements for which a principal does not
|
|
have access. It never thrown an <classname>AccessDeniedException</classname> - simply
|
|
silently removes the offending elements. The provider is configured as follows: <programlisting language="xml"><![CDATA[
|
|
<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>
|
|
]]> </programlisting> As you can imagine, the returned <literal>Object</literal> must be a
|
|
<literal>Collection</literal> or array for this provider to operate. It will remove any
|
|
element if the <literal>AclManager</literal> indicates the
|
|
<interfacename>Authentication</interfacename> does not hold one of the listed
|
|
<literal>requirePermission</literal>s.</para>
|
|
<para>The Contacts sample application demonstrates these two
|
|
<literal>AfterInvocationProvider</literal>s.</para>
|
|
</section> -->
|
|
</section>
|
|
<section xml:id="authz-hierarchical-roles">
|
|
<title>Hierarchical Roles</title>
|
|
<para>
|
|
It is a common requirement that a particular role in an application should automatically
|
|
<quote>include</quote> other roles. For example, in an application which has the concept of
|
|
an <quote>admin</quote> and a <quote>user</quote> 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 <quote>user</quote> role. Alternatively, you can modify every access constraint
|
|
which requires the <quote>user</quote> role to also include the <quote>admin</quote> role.
|
|
This can get quite complicated if you have a lot of different roles in your application.
|
|
</para>
|
|
<para>
|
|
The use of a role-hierarchy allows you to configure which roles (or authorities) should include others.
|
|
An extended version of Spring Security's <link linkend="authz-role-voter"><classname>RoleVoter</classname></link>,
|
|
<classname>RoleHierarchyVoter</classname>, is configured with a <interfacename>RoleHierarchy</interfacename>,
|
|
from which it obtains all the <quote>reachable authorities</quote> which the user is assigned.
|
|
A typical configuration might look like this:
|
|
<programlisting language="xml"><![CDATA[
|
|
<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>]]>
|
|
</programlisting>
|
|
Here we have four roles in a hierarchy <literal>ROLE_ADMIN => ROLE_STAFF => ROLE_USER => ROLE_GUEST</literal>.
|
|
A user who is authenticated with <literal>ROLE_ADMIN</literal>, will behave as if they have all four roles when
|
|
security contraints are evaluated against an <interfacename>AccessDecisionManager</interfacename> cconfigured
|
|
with the above <classname>RoleHierarchyVoter</classname>. The <literal>></literal> symbol can be thought of
|
|
as meaning <quote>includes</quote>.
|
|
</para>
|
|
<para>
|
|
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.
|
|
<!-- TODO: Extend when authority-mapping layer is added -->
|
|
</para>
|
|
</section>
|
|
|
|
</chapter>
|