SEC-426: Provide better ACL documentation.
This commit is contained in:
parent
9dea82773c
commit
ce34ef366d
|
@ -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;
|
||||
|
||||
<bean id="basicAclExtendedDao" class="org.springframework.security.acl.basic.jdbc.JdbcExtendedDaoImpl">
|
||||
<property name="dataSource">
|
||||
<ref bean="dataSource"/>
|
||||
</property>
|
||||
<property name="objectPropertiesQuery" value="${acegi.objectPropertiesQuery}"/>
|
||||
</bean>
|
||||
|
||||
<prop key="acegi.objectPropertiesQuery">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 = ?</prop> </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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue