SEC-426: Provide better ACL documentation.

This commit is contained in:
Ben Alex 2008-04-14 06:15:28 +00:00
parent 9dea82773c
commit ce34ef366d
3 changed files with 278 additions and 634 deletions

View File

@ -1,479 +0,0 @@
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="domain-acls-old">
<info><title>Domain Object Security (old ACL module)</title></info>
<section xml:id="domain-acls-overview-old"><info><title>Overview</title></info>
<para>PLEASE NOTE: Acegi Security 1.0.3 contains a preview of a new
ACL module. The new ACL module is a significant rewrite of the
existing ACL module. The new module can be found under the
<literal>org.springframework.security.acls</literal> package, with the
old ACL module under
<literal>org.springframework.security.acl</literal>. We encourage
users to consider testing with the new ACL module and build
applications with it. The old ACL module should be considered
deprecated and may be removed from a future release.</para>
<para>Complex applications often will find the need to define access
permissions not simply at a web request or method invocation level.
Instead, security decisions need to comprise both who
(<literal>Authentication</literal>), where
(<literal>MethodInvocation</literal>) and what
(<literal>SomeDomainObject</literal>). In other words, authorization
decisions also need to consider the actual domain object instance
subject of a method invocation.</para>
<para>Imagine you're designing an application for a pet clinic. There
will be two main groups of users of your Spring-based application:
staff of the pet clinic, as well as the pet clinic's customers. The
staff will have access to all of the data, whilst your customers will
only be able to see their own customer records. To make it a little
more interesting, your customers can allow other users to see their
customer records, such as their "puppy preschool "mentor or president
of their local "Pony Club". Using Spring Security as the foundation,
you have several approaches that can be used:<orderedlist inheritnum="ignore" continuation="restarts">
<listitem>
<para>Write your business methods to enforce the security. You
could consult a collection within the
<literal>Customer</literal> domain object instance to determine
which users have access. By using the
<literal>SecurityContextHolder.getContext().getAuthentication()</literal>,
you'll be able to access the <literal>Authentication</literal>
object.</para>
</listitem>
<listitem>
<para>Write an <literal>AccessDecisionVoter</literal> to enforce
the security from the <literal>GrantedAuthority[]</literal>s
stored in the <literal>Authentication</literal> object. This
would mean your <literal>AuthenticationManager</literal> would
need to populate the <literal>Authentication</literal> with
custom <literal>GrantedAuthority</literal>[]s representing each
of the <literal>Customer</literal> domain object instances the
principal has access to.</para>
</listitem>
<listitem>
<para>Write an <literal>AccessDecisionVoter</literal> to enforce
the security and open the target <literal>Customer</literal>
domain object directly. This would mean your voter needs access
to a DAO that allows it to retrieve the
<literal>Customer</literal> object. It would then access the
<literal>Customer</literal> object's collection of approved
users and make the appropriate decision.</para>
</listitem>
</orderedlist></para>
<para>Each one of these approaches is perfectly legitimate. However,
the first couples your authorization checking to your business code.
The main problems with this include the enhanced difficulty of unit
testing and the fact it would be more difficult to reuse the
<literal>Customer</literal> authorization logic elsewhere. Obtaining
the <literal>GrantedAuthority[]</literal>s from the
<literal>Authentication</literal> object is also fine, but will not
scale to large numbers of <literal>Customer</literal>s. If a user
might be able to access 5,000 <literal>Customer</literal>s (unlikely
in this case, but imagine if it were a popular vet for a large Pony
Club!) the amount of memory consumed and time required to construct
the <literal>Authentication</literal> object would be undesirable. The
final method, opening the <literal>Customer</literal> directly from
external code, is probably the best of the three. It achieves
separation of concerns, and doesn't misuse memory or CPU cycles, but
it is still inefficient in that both the
<literal>AccessDecisionVoter</literal> and the eventual business
method itself will perform a call to the DAO responsible for
retrieving the <literal>Customer</literal> object. Two accesses per
method invocation is clearly undesirable. In addition, with every
approach listed you'll need to write your own access control list
(ACL) persistence and business logic from scratch.</para>
<para>Fortunately, there is another alternative, which we'll talk
about below.</para>
</section>
<section xml:id="domain-acls-basic-old"><info><title>Basic ACL Package</title></info>
<para>Please note that our Basic ACL services are currently being
refactored. We expect release 1.1.0 will contain this new code.
Planned code is already in the Spring Security Subversion sandbox, so
please check there if you have a new application requiring ACLs or are
in the planning stages. The Basic ACL services will be deprecated from
release 1.1.0.</para>
<para>The <literal>org.springframework.security.acl</literal> package
is very simple, comprising only a handful of interfaces and a single
class, as shown in <xref linkend="acl-manager"/>. It provides the basic foundation for
access control list (ACL) lookups.
<figure xml:id="acl-manager">
<title>Access Control List Manager</title>
<mediaobject>
<imageobject role="fo">
<imagedata align="center" fileref="resources/images/ACLSecurity.gif" format="GIF"/>
</imageobject>
<imageobject role="html">
<imagedata align="center" fileref="images/ACLSecurity.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
</para>
<para>The central interface is <literal>AclManager</literal>, which is
defined by two methods:</para>
<para><programlisting>public AclEntry[] getAcls(java.lang.Object domainInstance);
public AclEntry[] getAcls(java.lang.Object domainInstance, Authentication authentication);</programlisting></para>
<para><literal>AclManager</literal> is intended to be used as a
collaborator against your business objects, or, more desirably,
<literal>AccessDecisionVoter</literal>s. This means you use Spring's
normal <literal>ApplicationContext</literal> features to wire up your
<literal>AccessDecisionVoter</literal> (or business method) with an
<literal>AclManager</literal>. Consideration was given to placing the
ACL information in the <literal>ContextHolder</literal>, but it was
felt this would be inefficient both in terms of memory usage as well
as the time spent loading potentially unused ACL information. The
trade-off of needing to wire up a collaborator for those objects
requiring ACL information is rather minor, particularly in a
Spring-managed application.</para>
<para>The first method of the <literal>AclManager</literal> will
return all ACLs applying to the domain object instance passed to it.
The second method does the same, but only returns those ACLs which
apply to the passed <literal>Authentication</literal> object.</para>
<para>The <literal>AclEntry</literal> interface returned by
<literal>AclManager</literal> is merely a marker interface. You will
need to provide an implementation that reflects that ACL permissions
for your application.</para>
<para>Rounding out the
<literal>org.springframework.security.acl</literal> package is an
<literal>AclProviderManager</literal> class, with a corresponding
<literal>AclProvider</literal> interface.
<literal>AclProviderManager</literal> is a concrete implementation of
<literal>AclManager</literal>, which iterates through registered
<literal>AclProvider</literal>s. The first
<literal>AclProvider</literal> that indicates it can authoritatively
provide ACL information for the presented domain object instance will
be used. This is very similar to the
<literal>AuthenticationProvider</literal> interface used for
authentication.</para>
<para>With this background, let's now look at a usable ACL
implementation.</para>
<para>Spring Security includes a production-quality ACL provider
implementation, which is shown in <xref linkend="acl-basic-mgr"/>.
<figure xml:id="acl-basic-mgr">
<title>Basic ACL Manager</title>
<mediaobject>
<imageobject role="fo">
<imagedata align="center" fileref="resources/images/BasicAclProvider.gif" format="GIF"/>
</imageobject>
<imageobject role="html">
<imagedata align="center" fileref="images/BasicAclProvider.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure></para>
<para>The implementation is based on integer masking, which is
commonly used for ACL permissions given its flexibility and speed.
Anyone who has used Unix's <literal>chmod</literal> command will know
all about this type of permission masking (eg <literal>chmod
777</literal>). You'll find the classes and interfaces for the integer
masking ACL package under
<literal>org.springframework.security.acl.basic</literal>.</para>
<para>Extending the <literal>AclEntry</literal> interface is a
<literal>BasicAclEntry</literal> interface, with the main methods
shown below:</para>
<para><programlisting>public AclObjectIdentity getAclObjectIdentity();
public AclObjectIdentity getAclObjectParentIdentity();
public int getMask();
public java.lang.Object getRecipient();</programlisting></para>
<para>As shown, each <literal>BasicAclEntry</literal> has four main
properties. The <literal>mask</literal> is the integer that represents
the permissions granted to the <literal>recipient</literal>. The
<literal>aclObjectIdentity</literal> is able to identify the domain
object instance for which the ACL applies, and the
<literal>aclObjectParentIdentity</literal> optionally specifies the
parent of the domain object instance. Multiple
<literal>BasicAclEntry</literal>s usually exist against a single
domain object instance, and as suggested by the parent identity
property, permissions granted higher in the object hierarchy will
trickle down and be inherited (unless blocked by integer zero).</para>
<para><literal>BasicAclEntry</literal> implementations typically
provide convenience methods, such as
<literal>isReadAllowed()</literal>, to avoid application classes
needing to perform bit masking themselves. The
<literal>SimpleAclEntry</literal> and
<literal>AbstractBasicAclEntry</literal> demonstrate and provide much
of this bit masking logic.</para>
<para>The <literal>AclObjectIdentity</literal> itself is merely a
marker interface, so you need to provide implementations for your
domain objects. However, the package does include a
<literal>NamedEntityObjectIdentity</literal> implementation which will
suit many needs. The <literal>NamedEntityObjectIdentity</literal>
identifies a given domain object instance by the classname of the
instance and the identity of the instance. A
<literal>NamedEntityObjectIdentity</literal> can be constructed
manually (by calling the constructor and providing the classname and
identity <literal>String</literal>s), or by passing in any domain
object that contains a <literal>getId()</literal> method.</para>
<para>The actual <literal>AclProvider</literal> implementation is
named <literal>BasicAclProvider</literal>. It has adopted a similar
design to that used by the authentication-related
<literal>DaoAuthenticationProvder</literal>. Specifically, you define
a <literal>BasicAclDao</literal> against the provider, so different
ACL repository types can be accessed in a pluggable manner. The
<literal>BasicAclProvider</literal> also supports pluggable cache
providers (with Spring Security including an implementation that
fronts EH-CACHE).</para>
<para>The <literal>BasicAclDao</literal> interface is very simple to
implement:</para>
<para><programlisting>public BasicAclEntry[] getAcls(AclObjectIdentity aclObjectIdentity);</programlisting></para>
<para>A <literal>BasicAclDao</literal> implementation needs to
understand the presented <literal>AclObjectIdentity</literal> and how
it maps to a storage repository, find the relevant records, and create
appropriate <literal>BasicAclEntry</literal> objects and return
them.</para>
<para>Spring Security includes a single <literal>BasicAclDao</literal>
implementation called <literal>JdbcDaoImpl</literal>. As implied by
the name, <literal>JdbcDaoImpl</literal> accesses ACL information from
a JDBC database. There is also an extended version of this DAO,
<literal>JdbcExtendedDaoImpl</literal>, which provides CRUD operations
on the JDBC database, although we won't discuss these features here.
The default database schema and some sample data will aid in
understanding its function:</para>
<para><programlisting>CREATE TABLE acl_object_identity (
id IDENTITY NOT NULL,
object_identity VARCHAR_IGNORECASE(250) NOT NULL,
parent_object INTEGER,
acl_class VARCHAR_IGNORECASE(250) NOT NULL,
CONSTRAINT unique_object_identity UNIQUE(object_identity),
FOREIGN KEY (parent_object) REFERENCES acl_object_identity(id)
);
CREATE TABLE acl_permission (
id IDENTITY NOT NULL,
acl_object_identity INTEGER NOT NULL,
recipient VARCHAR_IGNORECASE(100) NOT NULL,
mask INTEGER NOT NULL,
CONSTRAINT unique_recipient UNIQUE(acl_object_identity, recipient),
FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity(id)
);
INSERT INTO acl_object_identity VALUES (1, 'corp.DomainObject:1', null,
'org.springframework.security.acl.basic.SimpleAclEntry');
INSERT INTO acl_object_identity VALUES (2, 'corp.DomainObject:2', 1,
'org.springframework.security.acl.basic.SimpleAclEntry');
INSERT INTO acl_object_identity VALUES (3, 'corp.DomainObject:3', 1,
'org.springframework.security.acl.basic.SimpleAclEntry');
INSERT INTO acl_object_identity VALUES (4, 'corp.DomainObject:4', 1,
'org.springframework.security.acl.basic.SimpleAclEntry');
INSERT INTO acl_object_identity VALUES (5, 'corp.DomainObject:5', 3,
'org.springframework.security.acl.basic.SimpleAclEntry');
INSERT INTO acl_object_identity VALUES (6, 'corp.DomainObject:6', 3,
'org.springframework.security.acl.basic.SimpleAclEntry');
INSERT INTO acl_permission VALUES (null, 1, 'ROLE_SUPERVISOR', 1);
INSERT INTO acl_permission VALUES (null, 2, 'ROLE_SUPERVISOR', 0);
INSERT INTO acl_permission VALUES (null, 2, 'rod', 2);
INSERT INTO acl_permission VALUES (null, 3, 'scott', 14);
INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);</programlisting></para>
<para>As can be seen, database-specific constraints are used
extensively to ensure the integrity of the ACL information. If you
need to use a different database (Hypersonic SQL statements are shown
above), you should try to implement equivalent constraints. The
equivalent Oracle configuration is:</para>
<para><programlisting>CREATE TABLE ACL_OBJECT_IDENTITY (
ID number(19,0) not null,
OBJECT_IDENTITY varchar2(255) NOT NULL,
PARENT_OBJECT number(19,0),
ACL_CLASS varchar2(255) NOT NULL,
primary key (ID)
);
ALTER TABLE ACL_OBJECT_IDENTITY ADD CONTRAINT FK_PARENT_OBJECT foreign key (ID) references ACL_OBJECT_IDENTITY
CREATE SEQUENCE ACL_OBJECT_IDENTITY_SEQ;
CREATE OR REPLACE TRIGGER ACL_OBJECT_IDENTITY_ID
BEFORE INSERT ON ACL_OBJECT_IDENTITY
FOR EACH ROW
BEGIN
SELECT ACL_OBJECT_IDENTITY_SEQ.NEXTVAL INTO :new.id FROM dual;
END;
CREATE TABLE ACL_PERMISSION (
ID number(19,0) not null,
ACL_OBJECT_IDENTITY number(19,0) NOT NULL,
RECIPIENT varchar2(255) NOT NULL,
MASK number(19,0) NOT NULL,
primary key (ID)
);
ALTER TABLE ACL_PERMISSION ADD CONTRAINT UNIQUE_ID_RECIPIENT unique (acl_object_identity, recipient);
CREATE SEQUENCE ACL_PERMISSION_SEQ;
CREATE OR REPLACE TRIGGER ACL_PERMISSION_ID
BEFORE INSERT ON ACL_PERMISSION
FOR EACH ROW
BEGIN
SELECT ACL_PERMISSION_SEQ.NEXTVAL INTO :new.id FROM dual;
END;
&lt;bean id="basicAclExtendedDao" class="org.springframework.security.acl.basic.jdbc.JdbcExtendedDaoImpl"&gt;
&lt;property name="dataSource"&gt;
&lt;ref bean="dataSource"/&gt;
&lt;/property&gt;
&lt;property name="objectPropertiesQuery" value="${acegi.objectPropertiesQuery}"/&gt;
&lt;/bean&gt;
&lt;prop key="acegi.objectPropertiesQuery"&gt;SELECT CHILD.ID, CHILD.OBJECT_IDENTITY, CHILD.ACL_CLASS, PARENT.OBJECT_IDENTITY as PARENT_OBJECT_IDENTITY FROM acl_object_identity as CHILD LEFT OUTER JOIN acl_object_identity as PARENT ON CHILD.parent_object=PARENT.id WHERE CHILD.object_identity = ?&lt;/prop&gt; </programlisting></para>
<para>The <literal>JdbcDaoImpl</literal> will only respond to requests
for <literal>NamedEntityObjectIdentity</literal>s. It converts such
identities into a single <literal>String</literal>, comprising
the<literal> NamedEntityObjectIdentity.getClassname()</literal> +
<literal>":"</literal> +
<literal>NamedEntityObjectIdentity.getId()</literal>. This yields the
type of <literal>object_identity</literal> values shown above. As
indicated by the sample data, each database row corresponds to a
single <literal>BasicAclEntry</literal>. As stated earlier and
demonstrated by <literal>corp.DomainObject:2</literal> in the above
sample data, each domain object instance will often have multiple
<literal>BasicAclEntry</literal>[]s.</para>
<para>As <literal>JdbcDaoImpl</literal> is required to return concrete
<literal>BasicAclEntry</literal> classes, it needs to know which
<literal>BasicAclEntry</literal> implementation it is to create and
populate. This is the role of the <literal>acl_class</literal> column.
<literal>JdbcDaoImpl</literal> will create the indicated class and set
its <literal>mask</literal>, <literal>recipient</literal>,
<literal>aclObjectIdentity</literal> and
<literal>aclObjectParentIdentity</literal> properties.</para>
<para>As you can probably tell from the sample data, the
<literal>parent_object_identity</literal> value can either be null or
in the same format as the <literal>object_identity</literal>. If
non-null, <literal>JdbcDaoImpl</literal> will create a
<literal>NamedEntityObjectIdentity</literal> to place inside the
returned <literal>BasicAclEntry</literal> class.</para>
<para>Returning to the <literal>BasicAclProvider</literal>, before it
can poll the <literal>BasicAclDao</literal> implementation it needs to
convert the domain object instance it was passed into an
<literal>AclObjectIdentity</literal>.
<literal>BasicAclProvider</literal> has a <literal>protected
AclObjectIdentity obtainIdentity(Object domainInstance)</literal>
method that is responsible for this. As a protected method, it enables
subclasses to easily override. The normal implementation checks
whether the passed domain object instance implements the
<literal>AclObjectIdentityAware</literal> interface, which is merely a
getter for an <literal>AclObjectIdentity</literal>. If the domain
object does implement this interface, that is the identity returned.
If the domain object does not implement this interface, the method
will attempt to create an <literal>AclObjectIdentity</literal> by
passing the domain object instance to the constructor of a class
defined by the
<literal>BasicAclProvider.getDefaultAclObjectIdentity()</literal>
method. By default the defined class is
<literal>NamedEntityObjectIdentity</literal>, which was described in
more detail above. Therefore, you will need to either (i) provide a
<literal>getId()</literal> method on your domain objects, (ii)
implement <literal>AclObjectIdentityAware</literal> on your domain
objects, (iii) provide an alternative
<literal>AclObjectIdentity</literal> implementation that will accept
your domain object in its constructor, or (iv) override the
<literal>obtainIdentity(Object)</literal> method.</para>
<para>Once the <literal>AclObjectIdentity</literal> of the domain
object instance is determined, the <literal>BasicAclProvider</literal>
will poll the DAO to obtain its <literal>BasicAclEntry</literal>[]s.
If any of the entries returned by the DAO indicate there is a parent,
that parent will be polled, and the process will repeat until there is
no further parent. The permissions assigned to a
<literal>recipient</literal> closest to the domain object instance
will always take priority and override any inherited permissions. From
the sample data above, the following inherited permissions would
apply:</para>
<para><programlisting>--- Mask integer 0 = no permissions
--- Mask integer 1 = administer
--- Mask integer 2 = read
--- Mask integer 6 = read and write permissions
--- Mask integer 14 = read and write and create permissions
---------------------------------------------------------------------
--- *** INHERITED RIGHTS FOR DIFFERENT INSTANCES AND RECIPIENTS ***
--- INSTANCE RECIPIENT PERMISSION(S) (COMMENT #INSTANCE)
---------------------------------------------------------------------
--- 1 ROLE_SUPERVISOR Administer
--- 2 ROLE_SUPERVISOR None (overrides parent #1)
--- rod Read
--- 3 ROLE_SUPERVISOR Administer (from parent #1)
--- scott Read, Write, Create
--- 4 ROLE_SUPERVISOR Administer (from parent #1)
--- 5 ROLE_SUPERVISOR Administer (from parent #3)
--- scott Read, Write, Create (from parent #3)
--- 6 ROLE_SUPERVISOR Administer (from parent #3)
--- scott Administer (overrides parent #3)</programlisting></para>
<para>So the above explains how a domain object instance has its
<literal>AclObjectIdentity</literal> discovered, and the
<literal>BasicAclDao</literal> will be polled successively until an
array of inherited permissions is constructed for the domain object
instance. The final step is to determine the
<literal>BasicAclEntry</literal>[]s that are actually applicable to a
given <literal>Authentication</literal> object.</para>
<para>As you would recall, the <literal>AclManager</literal> (and all
delegates, up to and including <literal>BasicAclProvider</literal>)
provides a method which returns only those
<literal>BasicAclEntry</literal>[]s applying to a passed
<literal>Authentication</literal> object.
<literal>BasicAclProvider</literal> delivers this functionality by
delegating the filtering operation to an
<literal>EffectiveAclsResolver</literal> implementation. The default
implementation,
<literal>GrantedAuthorityEffectiveAclsResolver</literal>, will iterate
through the <literal>BasicAclEntry</literal>[]s and include only those
where the <literal>recipient</literal> is equal to either the
<literal>Authentication</literal>'s <literal>principal</literal> or
any of the <literal>Authentication</literal>'s
<literal>GrantedAuthority</literal>[]s. Please refer to the JavaDocs
for more information.</para>
<figure xml:id="acl-instantiation">
<title>ACL Instantiation Approach</title>
<mediaobject>
<imageobject role="fo">
<imagedata align="center" fileref="resources/images/Permissions.gif" format="GIF"/>
</imageobject>
<imageobject role="html">
<imagedata align="center" fileref="images/Permissions.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
<para><xref linkend="acl-instantiation"/> explains the key relationships between objects
in the Basic ACL package.</para>
</section>
</chapter>

View File

@ -1,179 +1,304 @@
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="domain-acls">
<info><title>Domain Object Security</title></info>
<section xml:id="domain-acls-overview"><info><title>Overview</title></info>
<para>PLEASE NOTE: Acegi Security 1.0.3 contains a preview of a new
ACL module. The new ACL module is a significant rewrite of the
existing ACL module. The new module can be found under the
<literal>org.springframework.security.acls</literal> package, with the
old ACL module under
<literal>org.springframework.security.acl</literal>. We encourage
users to consider testing with the new ACL module and build
applications with it. The old ACL module should be considered
deprecated and may be removed from a future release.</para>
<para>Complex applications often will find the need to define access
permissions not simply at a web request or method invocation level.
Instead, security decisions need to comprise both who
(<literal>Authentication</literal>), where
(<literal>MethodInvocation</literal>) and what
(<literal>SomeDomainObject</literal>). In other words, authorization
decisions also need to consider the actual domain object instance
subject of a method invocation.</para>
<para>Imagine you're designing an application for a pet clinic. There
will be two main groups of users of your Spring-based application:
staff of the pet clinic, as well as the pet clinic's customers. The
staff will have access to all of the data, whilst your customers will
only be able to see their own customer records. To make it a little
more interesting, your customers can allow other users to see their
customer records, such as their "puppy preschool "mentor or president
of their local "Pony Club". Using Spring Security as the foundation,
you have several approaches that can be used:<orderedlist inheritnum="ignore" continuation="restarts">
<info>
<title>Domain Object Security</title>
</info>
<section xml:id="domain-acls-overview">
<info>
<title>Overview</title>
</info>
<para>PLEASE NOTE: Before release 2.0.0, Spring Security was known as Acegi Security. An ACL
module was provided with the old Acegi Security releases under the
<literal>org.[acegisecurity/springsecurity].acl</literal> package. This old package
is now deprecated and will be removed in a future release of Spring Security. This
chapter covers the new ACL module, which is officially recommended from Spring Security
2.0.0 and above, and can be found under the
<literal>org.springframework.security.acls</literal> package.</para>
<para>Complex applications often will find the need to define access permissions not simply
at a web request or method invocation level. Instead, security decisions need to
comprise both who (<literal>Authentication</literal>), where
(<literal>MethodInvocation</literal>) and what (<literal>SomeDomainObject</literal>). In
other words, authorization decisions also need to consider the actual domain object
instance subject of a method invocation.</para>
<para>Imagine you're designing an application for a pet clinic. There will be two main
groups of users of your Spring-based application: staff of the pet clinic, as well as
the pet clinic's customers. The staff will have access to all of the data, whilst your
customers will only be able to see their own customer records. To make it a little more
interesting, your customers can allow other users to see their customer records, such as
their "puppy preschool" mentor or president of their local "Pony Club". Using Spring
Security as the foundation, you have several approaches that can be used:<orderedlist
inheritnum="ignore" continuation="restarts">
<listitem>
<para>Write your business methods to enforce the security. You
could consult a collection within the
<literal>Customer</literal> domain object instance to determine
which users have access. By using the
<literal>SecurityContextHolder.getContext().getAuthentication()</literal>,
<para>Write your business methods to enforce the security. You could consult a
collection within the <literal>Customer</literal> domain object instance to
determine which users have access. By using the
<literal>SecurityContextHolder.getContext().getAuthentication()</literal>,
you'll be able to access the <literal>Authentication</literal>
object.</para>
object.</para>
</listitem>
<listitem>
<para>Write an <literal>AccessDecisionVoter</literal> to enforce
the security from the <literal>GrantedAuthority[]</literal>s
stored in the <literal>Authentication</literal> object. This
would mean your <literal>AuthenticationManager</literal> would
need to populate the <literal>Authentication</literal> with
custom <literal>GrantedAuthority</literal>[]s representing each
of the <literal>Customer</literal> domain object instances the
principal has access to.</para>
<para>Write an <literal>AccessDecisionVoter</literal> to enforce the security
from the <literal>GrantedAuthority[]</literal>s stored in the
<literal>Authentication</literal> object. This would mean your
<literal>AuthenticationManager</literal> would need to populate the
<literal>Authentication</literal> with custom
<literal>GrantedAuthority</literal>[]s representing each of the
<literal>Customer</literal> domain object instances the principal has
access to.</para>
</listitem>
<listitem>
<para>Write an <literal>AccessDecisionVoter</literal> to enforce
the security and open the target <literal>Customer</literal>
domain object directly. This would mean your voter needs access
to a DAO that allows it to retrieve the
<literal>Customer</literal> object. It would then access the
<literal>Customer</literal> object's collection of approved
users and make the appropriate decision.</para>
<para>Write an <literal>AccessDecisionVoter</literal> to enforce the security
and open the target <literal>Customer</literal> domain object directly. This
would mean your voter needs access to a DAO that allows it to retrieve the
<literal>Customer</literal> object. It would then access the
<literal>Customer</literal> object's collection of approved users and
make the appropriate decision.</para>
</listitem>
</orderedlist></para>
<para>Each one of these approaches is perfectly legitimate. However,
the first couples your authorization checking to your business code.
The main problems with this include the enhanced difficulty of unit
testing and the fact it would be more difficult to reuse the
<literal>Customer</literal> authorization logic elsewhere. Obtaining
the <literal>GrantedAuthority[]</literal>s from the
<literal>Authentication</literal> object is also fine, but will not
scale to large numbers of <literal>Customer</literal>s. If a user
might be able to access 5,000 <literal>Customer</literal>s (unlikely
in this case, but imagine if it were a popular vet for a large Pony
Club!) the amount of memory consumed and time required to construct
the <literal>Authentication</literal> object would be undesirable. The
final method, opening the <literal>Customer</literal> directly from
external code, is probably the best of the three. It achieves
separation of concerns, and doesn't misuse memory or CPU cycles, but
it is still inefficient in that both the
<literal>AccessDecisionVoter</literal> and the eventual business
method itself will perform a call to the DAO responsible for
retrieving the <literal>Customer</literal> object. Two accesses per
method invocation is clearly undesirable. In addition, with every
approach listed you'll need to write your own access control list
(ACL) persistence and business logic from scratch.</para>
<para>Fortunately, there is another alternative, which we'll talk
about below.</para>
<para>Each one of these approaches is perfectly legitimate. However, the first couples your
authorization checking to your business code. The main problems with this include the
enhanced difficulty of unit testing and the fact it would be more difficult to reuse the
<literal>Customer</literal> authorization logic elsewhere. Obtaining the
<literal>GrantedAuthority[]</literal>s from the <literal>Authentication</literal>
object is also fine, but will not scale to large numbers of
<literal>Customer</literal>s. If a user might be able to access 5,000
<literal>Customer</literal>s (unlikely in this case, but imagine if it were a popular
vet for a large Pony Club!) the amount of memory consumed and time required to construct
the <literal>Authentication</literal> object would be undesirable. The final method,
opening the <literal>Customer</literal> directly from external code, is probably the
best of the three. It achieves separation of concerns, and doesn't misuse memory or CPU
cycles, but it is still inefficient in that both the
<literal>AccessDecisionVoter</literal> and the eventual business method itself will
perform a call to the DAO responsible for retrieving the <literal>Customer</literal>
object. Two accesses per method invocation is clearly undesirable. In addition, with
every approach listed you'll need to write your own access control list (ACL)
persistence and business logic from scratch.</para>
<para>Fortunately, there is another alternative, which we'll talk about below.</para>
</section>
<section xml:id="domain-acls-key-concepts"><info><title>Key Concepts</title></info>
<para>The org.springframework.security.acls package should be
consulted for its major interfaces. The key interfaces are:</para>
<section xml:id="domain-acls-key-concepts">
<info>
<title>Key Concepts</title>
</info>
<para>Spring Security's ACL services are shipped in the
<literal>spring-security-acl-xxx.jar</literal>. You will need to add this JAR to your
classpath to use Spring Security's domain object instance security capabilities.</para>
<para>Spring Security's domain object instance security capabilities centre on the concept
of an access control list (ACL). Every domain object instance in your system has its own
ACL, and the ACL records details of who can and can't work with that domain object. With
this in mind, Spring Security delivers three main ACL-related capabilities to your application:<itemizedlist>
<listitem>
<para>A way of efficiently retrieving ACL entries for all of your domain objects
(and modifying those ACLs)</para>
</listitem>
<listitem>
<para>A way of ensuring a given principal is permitted to work with your
objects, before methods are called</para>
</listitem>
<listitem>
<para>A way of ensuring a given principal is permitted to work with your objects
(or something they return), after methods are called</para>
</listitem>
</itemizedlist></para>
<para>As indicated by the first bullet point, one of the main capabilities of the Spring
Security ACL module is providing a high-performance way of retrieving ACLs. This ACL
repository capability is extremely important, because every domain object instance in
your system might have several access control entries, and each ACL might inherit from
other ACLs in a tree-like structure (this is supported out-of-the-box by Spring
Security, and is very commonly used). Spring Security's ACL capability has been
carefully designed to provide high performance retrieval of ACLs, together with
pluggable caching, deadlock-minimizing database updates, independence from ORM
frameworks (we use JDBC directly), proper encapsulation, and transparent database
updating.</para>
<para>Given databases are central to the operation of the ACL module, let's explore the four
main tables used by default in the implementation. The tables are presented below in
order of size in a typical Spring Security ACL deployment, with the table with the most
rows listed last:</para>
<para>
<itemizedlist>
<listitem>
<para>ACL_SID allows us to uniquely identify any principal or authority in the
system ("SID" stands for "security identity"). The only columns are the ID,
a textual representation of the SID, and a flag to indicate whether the
textual representation refers to a prncipal name or a
<literal>GrantedAuthority</literal>. Thus, there is a single row for
each unique principal or <literal>GrantedAuthority</literal>. When used in
the context of receiving a permission, a SID is generally called a
"recipient".</para>
</listitem>
<listitem>
<para>ACL_CLASS allows us to uniquely identify any domain object class in the
system. The only columns are the ID and the Java class name. Thus, there is
a single row for each unique Class we wish to store ACL permissions
for.</para>
</listitem>
<listitem>
<para>ACL_OBJECT_IDENTITY stores information for each unique domain object
instance in the system. Columns include the ID, a foreign key to the
ACL_CLASS table, a unique identifier so we know which ACL_CLASS instance
we're providing information for, the parent, a foreign key to the ACL_SID
table to represent the owner of the domain object instance, and whether we
allow ACL entries to inherit from any parent ACL. We have a single row for
every domain object instance we're storing ACL permissions for.</para>
</listitem>
<listitem>
<para>Finally, ACL_ENTRY stores the individual permissions assigned to each
recipient. Columns include a foreign key to the ACL_OBJECT_IDENTITY, the
recipient (ie a foreign key to ACL_SID), whether we'll be auditing or not,
and the integer bit mask that represents the actual permission being granted
or denied. We have a single row for every recipient that receives a
permission to work with a domain object.</para>
</listitem>
</itemizedlist>
</para>
<para>As mentioned in the last paragraph, the ACL system uses integer bit masking. Don't
worry, you need not be aware of the finer points of bit shifting to use the ACL system,
but suffice to say that we have 32 bits we can switch on or off. Each of these bits
represents a permission, and by default the permissions are read (bit 0), write (bit 1),
create (bit 2), delete (bit 3) and administer (bit 4). It's easy to implement your own
<literal>Permission</literal> instance if you wish to use other permissions, and the
remainder of the ACL framework will operate without knowledge of your extensions.</para>
<para>It is important to understand that the number of domain objects in your system has
absolutely no bearing on the fact we've chosen to use integer bit masking. Whilst you
have 32 bits available for permissions, you could have billions of domain object
instances (which will mean billions of rows in ACL_OBJECT_IDENTITY and quite probably
ACL_ENTRY). We make this point because we've found sometimes people mistakenly believe
they need a bit for each potential domain object, which is not the case.</para>
<para>Now that we've provided a basic overview of what the ACL system does, and what it
looks like at a table structure, let's explore the key interfaces. The key interfaces
are:</para>
<itemizedlist spacing="compact">
<listitem>
<para><literal>Acl</literal>: Every domain object has one and only
one <literal>Acl</literal> object, which internally holds the
<literal>AccessControlEntry</literal>s as well as knows the owner
of the <literal>Acl</literal>. An Acl does not refer directly to
the domain object, but instead to an
<literal>ObjectIdentity</literal>.</para>
<para><literal>Acl</literal>: Every domain object has one and only one
<literal>Acl</literal> object, which internally holds the
<literal>AccessControlEntry</literal>s as well as knows the owner of the
<literal>Acl</literal>. An Acl does not refer directly to the domain object,
but instead to an <literal>ObjectIdentity</literal>. The <literal>Acl</literal>
is stored in the ACL_OBJECT_IDENTITY table.</para>
</listitem>
<listitem>
<para><literal>AccessControlEntry</literal>: An
Acl holds multiple <literal>AccessControlEntry</literal>s, which
are often abbreviated as ACEs in the framework. Each ACE refers to
a specific tuple of <literal>Permission</literal>,
<literal>Sid</literal> and <literal>Acl</literal>. An ACE can also
be granting or non-granting and contain audit settings.</para>
<para><literal>AccessControlEntry</literal>: An <literal>Acl</literal> holds
multiple <literal>AccessControlEntry</literal>s, which are often abbreviated as
ACEs in the framework. Each ACE refers to a specific tuple of
<literal>Permission</literal>, <literal>Sid</literal> and
<literal>Acl</literal>. An ACE can also be granting or non-granting and contain
audit settings. The ACE is stored in the ACL_ENTRY table.</para>
</listitem>
<listitem>
<para><literal>Permission</literal>: A permission represents an
immutable particular bit mask, and offers convenience functions
for bit masking and outputting information.</para>
<para><literal>Permission</literal>: A permission represents a particular immutable
bit mask, and offers convenience functions for bit masking and outputting
information. The basic permissions presented above (bits 0 through 4) are
contained in the <literal>BasePermission</literal> class.</para>
</listitem>
<listitem>
<para><literal>Sid</literal>: The ACL module needs to refer to
principals and <literal>GrantedAuthority[]</literal>s. A level of
indirection is provided by the <literal>Sid</literal> interface.
Common classes include <literal>PrincipalSid</literal> (to
represent the principal inside an
<literal>Authentication</literal> object) and
<literal>GrantedAuthoritySid</literal>.</para>
<para><literal>Sid</literal>: The ACL module needs to refer to principals and
<literal>GrantedAuthority[]</literal>s. A level of indirection is provided
by the <literal>Sid</literal> interface, which is an abbreviation of "security
identity". Common classes include <literal>PrincipalSid</literal> (to represent
the principal inside an <literal>Authentication</literal> object) and
<literal>GrantedAuthoritySid</literal>. The security identity information is
stored in the ACL_SID table.</para>
</listitem>
<listitem>
<para><literal>ObjectIdentity</literal>: Each domain object is
represented internally within the ACL module by an
<literal>ObjectIdentity</literal>.</para>
<para><literal>ObjectIdentity</literal>: Each domain object is represented
internally within the ACL module by an <literal>ObjectIdentity</literal>. The
default implementation is called <literal>ObjectIdentityImpl</literal>.</para>
</listitem>
<listitem>
<para><literal>AclService</literal>: Retrieves the
<literal>Acl</literal> applicable for a given
<literal>ObjectIdentity</literal>.</para>
<para><literal>AclService</literal>: Retrieves the <literal>Acl</literal> applicable
for a given <literal>ObjectIdentity</literal>. In the included implementation
(<literal>JdbcAclService</literal>), retrieval operations are delegated to a
<literal>LookupStrategy</literal>. The <literal>LookupStrategy</literal>
provides a highly optimized strategy for retrieving ACL information, using
batched retrievals <literal>(BasicLookupStrategy</literal>) and supporting
custom implementations that leverage materialized views, hierarchical queries
and similar performance-centric, non-ANSI SQL capabilities.</para>
</listitem>
<listitem>
<para><literal>MutableAclService</literal>: Allows a modified
<literal>Acl</literal> to be presented for persistence. It is not
essential to use this interface if you do not wish.</para>
<para><literal>MutableAclService</literal>: Allows a modified <literal>Acl</literal>
to be presented for persistence. It is not essential to use this interface if
you do not wish.</para>
</listitem>
</itemizedlist>
<para>The ACL module was based on extensive feedback from the user
community following real-world use of the original ACL module. This
feedback resulted in a rearchitecture of the ACL module to offer
significantly enhanced performance (particularly in the area of
database retrieval), significantly better encapsulation, higher
cohesion, and enhanced customisation points.</para>
<para>The Contacts Sample that ships with Acegi Security 1.0.3 offers
a demonstration of the new ACL module. Converting Contacts from using
the old module to the new module was relatively simple, and users of
the old ACL module will likely find their applications can be modified
with relatively little work.</para>
<para>We will document the new ACL module more fully with a subsequent
release. Please note that the new ACL module should be considered a
preview only (ie do not use in production without proper prior
testing), and there is a small chance there may be changes between
1.0.3 and 1.1.0 when it will become final. Nevertheless,
compatibility-affecting changes are considered quite unlikely,
especially given the module is already based on several years of
feedback from users of the original ACL module.</para>
<para>Please note that our out-of-the-box AclService and related database classes all use
ANSI SQL. This should therefore work with all major databases. At the time of writing,
the system had been successfully tested using Hypersonic SQL, PostgreSQL, Microsoft SQL
Server and Oracle.</para>
<para>Two samples ship with Spring Security that demonstrate the ACL module. The first is
the Contacts Sample, and the other is the Document Management System (DMS) Sample. We
suggest taking a look over these for examples.</para>
</section>
</chapter>
<section xml:id="domain-acls-getting-started">
<info>
<title>Getting Started</title>
</info>
<para>To get starting using Spring Security's ACL capability, you will need to store your
ACL information somewhere. This necessitates the instantiation of a
<literal>DataSource</literal> using Spring. The <literal>DataSource</literal> is then
injected into a <literal>JdbcMutableAclService</literal> and
<literal>BasicLookupStrategy</literal> instance. The latter provides
high-performance ACL retrieval capabilities, and the former provides mutator
capabilities. Refer to one of the samples that ship with Spring Security for an example
configuration. You'll also need to populate the database with the four ACL-specific
tables listed in the last section (refer to the ACL samples for the appropriate SQL
statements).</para>
<para>Once you've created the required schema and instantiated
<literal>JdbcMutableAclService</literal>, you'll next need to ensure your domain
model supports interoperability with the Spring Security ACL package. Hopefully
<literal>ObjectIdentityImpl</literal> will prove sufficient, as it provides a large
number of ways in which it can be used. Most people will have domain objects that
contain a <literal>public Serializable getId()</literal> method. If the return type is
long, or compatible with long (eg an int), you will find you need not give further
consideration to <literal>ObjectIdentity</literal> issues. Many parts of the ACL module
rely on long identifiers. If you're not using long (or an int, byte etc), there is a
very good chance you'll need to reimplement a number of classes. We do not intend to
support non-long identifiers in Spring Security's ACL module, as longs are already
compatible with all database sequences, the most common identifier data type, and are of
sufficient length to accommodate all common usage scenarios.</para>
<para>The following fragment of code shows how to create an <literal>Acl</literal>, or
modify an existing
<literal>Acl</literal>:<programlisting>// Prepare the information we'd like in our access control entry (ACE)
ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44));
Sid sid = new PrincipalSid("Samantha");
Permission p = BasePermission.ADMINISTRATION;
// Create or update the relevant ACL
MutableAcl acl = null;
try {
acl = (MutableAcl) aclService.readAclById(oi);
} catch (NotFoundException nfe) {
acl = aclService.createAcl(oi);
}
// Now grant some permissions via an access control entry (ACE)
acl.insertAce(acl.getEntries().length, p, sid, true);
aclService.updateAcl(acl);
</programlisting></para>
<para>In the example above, we're retrieving the ACL associated with the "Foo" domain object
with identifier number 44. We're then adding an ACE so that a principal named "Samantha"
can "administer" the object. The code fragment is relatively self-explanatory, except
the insertAce method. The first argument to the insertAce method is determining at what
position in the Acl the new entry will be inserted. In the example above, we're just
putting the new ACE at the end of the existing ACEs. The final argument is a boolean
indicating whether the ACE is granting or denying. Most of the time it will be granting
(true), but if it is denying (false), the permissions are effectively being blocked.</para>
<para>Spring Security does not provide any special integration to automatically create,
update or delete ACLs as part of your DAO or repository operations. Instead, you will
need to write code like shown above for your individual domain objects. It's worth
considering using AOP on your services layer to automatically integrate the ACL
information with your services layer operations. We've found this quite an effective
approach in the past.</para>
<para>Once you've used the above techniques to store some ACL information in the database,
the next step is to actually use the ACL information as part of authorization decision
logic. You have a number of choices here. You could write your own
<literal>AccessDecisionVoter</literal> or <literal>AfterInvocationProvider</literal>
that respectively fires before or after a method invocation. Such classes would use
<literal>AclService</literal> to retrieve the relevant ACL and then call
<literal>Acl.isGranted(Permission[] permission, Sid[] sids, boolean
administrativeMode)</literal> to decide whether permission is granted or denied.
Alternately, you could use our <literal>AclEntryVoter</literal>,
<literal>AclEntryAfterInvocationProvider</literal> or
<literal>AclEntryAfterInvocationCollectionFilteringProvider</literal> classes. All
of these classes provide a declarative-based approach to evaluating ACL information at
runtime, freeing you from needing to write any code. Please refer to the sample
applications to learn how to use these classes.</para>
</section>
</chapter>

View File

@ -192,9 +192,7 @@
<xi:include href="secured-objects.xml"/>
<xi:include href="domain-acls.xml"/>
<xi:include href="domain-acls-old.xml"/>
</part>
</book>