Author: shane.bryzak(a)jboss.com
Date: 2010-07-23 05:17:40 -0400 (Fri, 23 Jul 2010)
New Revision: 13484
Modified:
modules/security/trunk/dist/pom.xml
modules/security/trunk/dist/src/main/assembly/assembly.xml
modules/security/trunk/docs/src/main/docbook/en-US/security-general.xml
modules/security/trunk/examples/idmconsole/pom.xml
modules/security/trunk/pom.xml
Log:
minor distribution fixes
Modified: modules/security/trunk/dist/pom.xml
===================================================================
--- modules/security/trunk/dist/pom.xml 2010-07-23 08:37:34 UTC (rev 13483)
+++ modules/security/trunk/dist/pom.xml 2010-07-23 09:17:40 UTC (rev 13484)
@@ -1,94 +1,102 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <parent>
- <groupId>org.jboss.seam.security</groupId>
- <artifactId>seam-security-parent</artifactId>
- <version>3.0.0-SNAPSHOT</version>
- <relativePath>../pom.xml</relativePath>
- </parent>
-
- <artifactId>seam-security-distribution</artifactId>
- <name>Seam Security Distribution</name>
- <description>Builds a Seam Security release distribution</description>
- <packaging>pom</packaging>
-
- <properties>
- <maven.deploy.skip>true</maven.deploy.skip>
- </properties>
-
- <dependencies>
- <dependency>
- <groupId>org.jboss.seam.security</groupId>
- <artifactId>seam-security-api</artifactId>
- <optional>true</optional>
- </dependency>
-
- <dependency>
- <groupId>org.jboss.seam.security</groupId>
- <artifactId>seam-security-impl</artifactId>
- <optional>true</optional>
- </dependency>
-
- <dependency>
- <groupId>org.jboss.seam.security</groupId>
- <artifactId>seam-security-api</artifactId>
- <version>${project.version}</version>
- <classifier>sources</classifier>
- <optional>true</optional>
- </dependency>
-
- <dependency>
- <groupId>org.jboss.seam.security</groupId>
- <artifactId>seam-security-api</artifactId>
- <version>${project.version}</version>
- <classifier>javadoc</classifier>
- <optional>true</optional>
- </dependency>
-
-
- <dependency>
- <groupId>org.jboss.seam.security</groupId>
- <artifactId>seam-security-impl</artifactId>
- <version>${project.version}</version>
- <classifier>sources</classifier>
- </dependency>
-
- <dependency>
- <groupId>org.jboss.seam.security</groupId>
- <artifactId>seam-security-impl</artifactId>
- <version>${project.version}</version>
- <classifier>javadoc</classifier>
- <optional>true</optional>
- </dependency>
-
+<?xml version="1.0" encoding="UTF-8"?>
+<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.jboss.seam.security</groupId>
+ <artifactId>seam-security-parent</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>seam-security-distribution</artifactId>
+ <name>Seam Security Distribution</name>
+ <description>Builds a Seam Security release distribution</description>
+ <packaging>pom</packaging>
+
+ <properties>
+ <maven.deploy.skip>true</maven.deploy.skip>
+ </properties>
+
+ <dependencies>
<dependency>
<groupId>org.jboss.seam.security</groupId>
+ <artifactId>seam-security-api</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.seam.security</groupId>
+ <artifactId>seam-security-impl</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.seam.security</groupId>
+ <artifactId>seam-security-api</artifactId>
+ <version>${project.version}</version>
+ <classifier>sources</classifier>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.seam.security</groupId>
+ <artifactId>seam-security-api</artifactId>
+ <version>${project.version}</version>
+ <classifier>javadoc</classifier>
+ <optional>true</optional>
+ </dependency>
+
+
+ <dependency>
+ <groupId>org.jboss.seam.security</groupId>
+ <artifactId>seam-security-impl</artifactId>
+ <version>${project.version}</version>
+ <classifier>sources</classifier>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.seam.security</groupId>
+ <artifactId>seam-security-impl</artifactId>
+ <version>${project.version}</version>
+ <classifier>javadoc</classifier>
+ <optional>true</optional>
+ </dependency>
+
+ <!--dependency>
+ <groupId>org.jboss.seam.security</groupId>
<artifactId>seam-security-reference-guide</artifactId>
- <version>${project.version}</version>
- <type>war</type>
+ <version>${project.version}</version>
+ <type>war</type>
<optional>true</optional>
- </dependency>
-
+ </dependency-->
+
+ <!--dependency>
+ <groupId>org.jboss.seam.security</groupId>
+ <artifactId>seam-security-seamspace-example</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ <optional>true</optional>
+ </dependency-->
+
<dependency>
- <groupId>org.jboss.seam.security</groupId>
- <artifactId>seam-security-seamspace-example</artifactId>
- <version>${project.version}</version>
- <type>zip</type>
+ <groupId>org.jboss.seam.security</groupId>
+ <artifactId>seam-security-idmconsole-example</artifactId>
+ <version>${project.version}</version>
+ <type>war</type>
<optional>true</optional>
- </dependency>
-
- </dependencies>
-
- <build>
- <finalName>seam-security</finalName>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-assembly-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
-</project>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <finalName>seam-security</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
Modified: modules/security/trunk/dist/src/main/assembly/assembly.xml
===================================================================
--- modules/security/trunk/dist/src/main/assembly/assembly.xml 2010-07-23 08:37:34 UTC
(rev 13483)
+++ modules/security/trunk/dist/src/main/assembly/assembly.xml 2010-07-23 09:17:40 UTC
(rev 13484)
@@ -57,7 +57,8 @@
<unpack>true</unpack>
<useProjectArtifact>false</useProjectArtifact>
<includes>
-
<include>org.jboss.seam.security:seam-security-seamspace-example</include>
+
<!--include>org.jboss.seam.security:seam-security-seamspace-example</include-->
+
<include>org.jboss.seam.security:seam-security-idmconsole-example</include>
</includes>
</dependencySet>
<!-- Pull in docbook artifacts -->
Modified: modules/security/trunk/docs/src/main/docbook/en-US/security-general.xml
===================================================================
--- modules/security/trunk/docs/src/main/docbook/en-US/security-general.xml 2010-07-23
08:37:34 UTC (rev 13483)
+++ modules/security/trunk/docs/src/main/docbook/en-US/security-general.xml 2010-07-23
09:17:40 UTC (rev 13484)
@@ -8,5195 +8,8 @@
<title>Overview</title>
<para>
- The Seam Security API provides a multitude of security-related features for your
Seam-based application, covering
- such areas as:
+ Seam Security documentation will be available in version 3.0.0.RC1
</para>
-
- <itemizedlist>
- <listitem>
- <para>
- Authentication - an extensible, JAAS-based authentication layer that allows
users to authenticate
- against any security provider.
- </para>
- </listitem>
- <listitem>
- <para>
- Identity Management - an API for managing a Seam application's users and
roles at runtime.
- </para>
- </listitem>
- <listitem>
- <para>
- Authorization - an extremely comprehensive authorization framework, supporting
user roles, persistent and
- rule-based permissions, and a pluggable permission resolver for easily
implementing customised security logic.
- </para>
- </listitem>
- <listitem>
- <para>
- Permission Management - a set of built-in Seam components to allow easy
management of an application's
- security policy.
- </para>
- </listitem>
- <listitem>
- <para>
- CAPTCHA support - to assist in the prevention of automated software/scripts
abusing your Seam-based site.
- </para>
- </listitem>
- <listitem>
- <para>
- And much more
- </para>
- </listitem>
- </itemizedlist>
-
- <para>
- This chapter will cover each of these features in detail.
- </para>
-
</sect1>
-
- <sect1>
- <title>Disabling Security</title>
-
- <para>
- In some situations it may be necessary to disable Seam Security, for instances
during unit tests or because you
- are using a different approach to security, such as native JAAS. Simply call the
static method
- <literal>Identity.setSecurityEnabled(false)</literal> to disable the
security infrastructure. Of course, it's not
- very convenient to have to call a static method when you want to configure the
application, so as an alternative
- you can control this setting in components.xml:
- </para>
-
- <itemizedlist>
- <listitem>
- <para>Entity Security</para>
- </listitem>
- <listitem>
- <para>Hibernate Security Interceptor</para>
- </listitem>
- <listitem>
- <para>Seam Security Interceptor</para>
- </listitem>
- <listitem>
- <para>Page restrictions</para>
- </listitem>
- <listitem>
- <para>Servlet API security integration</para>
- </listitem>
- </itemizedlist>
-
- <para>
- Assuming you are planning to take advantage of what Seam Security has to offer, the
rest of this chapter documents
- the plethora of options you have for giving your user an identity in the eyes of
the security model
- (authentication) and locking down the application by establishing constraints
(authorization). Let's begin with
- the task of authentication since that's the foundation of any security model.
- </para>
-
- </sect1>
-
- <sect1>
- <title>Authentication</title>
-
- <para>
- The authentication features provided by Seam Security are built upon JAAS (Java
Authentication and Authorization Service),
- and as such provide a robust and highly configurable API for handling user
authentication. However, for less complex
- authentication requirements Seam offers a much more simplified method of
authentication that hides the complexity of JAAS.
- </para>
- <sect2>
- <title>Configuring an Authenticator component</title>
-
- <note>
- <para>
- If you use Seam's Identity Management features (discussed later in this
chapter) then it is not necessary to create
- an authenticator component (and you can skip this section).
- </para>
- </note>
-
- <para>
- The simplified authentication method provided by Seam uses a built-in JAAS login
module, <literal>SeamLoginModule</literal>, which
- delegates authentication to one of your own Seam components. This login module
is already configured inside Seam as
- part of a default application policy and as such does not require any additional
configuration files. It allows you to
- write an authentication method using the entity classes that are provided by your
own application, or alternatively to
- authenticate with some other third party provider. Configuring this simplified
form of authentication requires the
- <literal>identity</literal> component to be configured in
<literal>components.xml</literal>:
- </para>
-
- <programlisting role="XML"><![CDATA[<components
xmlns="http://jboss.com/products/seam/components"
-
xmlns:core="http://jboss.com/products/seam/core"
-
xmlns:security="http://jboss.com/products/seam/security"
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation=
- "http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.2.xsd
-
http://jboss.com/products/seam/security
http://jboss.com/products/seam/security-2.2.xsd">
-
- <security:identity
authenticate-method="#{authenticator.authenticate}"/>
-
-</components>]]></programlisting>
-
- <para>
- The EL expression <literal>#{authenticator.authenticate}</literal> is
a method binding that indicates
- the <literal>authenticate</literal> method of the
<literal>authenticator</literal> component will be used
- to authenticate the user.
- </para>
-
- </sect2>
-
- <sect2>
- <title>Writing an authentication method</title>
-
- <para>
- The <literal>authenticate-method</literal> property specified for
<literal>identity</literal> in
- <literal>components.xml</literal> specifies which method will be used
by <literal>SeamLoginModule</literal>
- to authenticate users. This method takes no parameters, and is expected to
return a boolean, which indicates
- whether authentication is successful or not. The user's username and
password can be obtained from
- <literal>Credentials.getUsername()</literal> and
<literal>Credentials.getPassword()</literal>,
- respectively (you can get a reference to the
<literal>credentials</literal> component via
- <literal>Identity.instance().getCredentials()</literal>). Any roles
that the user is a member of
- should be assigned using <literal>Identity.addRole()</literal>.
Here's a complete example of an
- authentication method inside a POJO component:
- </para>
-
- <programlisting
role="JAVA"><![CDATA[@Name("authenticator")
-public class Authenticator {
- @In EntityManager entityManager;
- @In Credentials credentials;
- @In Identity identity;
-
- public boolean authenticate() {
- try {
- User user = (User) entityManager.createQuery(
- "from User where username = :username and password = :password")
- .setParameter("username", credentials.getUsername())
- .setParameter("password", credentials.getPassword())
- .getSingleResult();
-
- if (user.getRoles() != null) {
- for (UserRole mr : user.getRoles())
- identity.addRole(mr.getName());
- }
-
- return true;
- }
- catch (NoResultException ex) {
- return false;
- }
-
- }
-
-}]]></programlisting>
-
- <para>
- In the above example, both <literal>User</literal> and
<literal>UserRole</literal> are application-specific
- entity beans. The <literal>roles</literal> parameter is populated
with the roles that the user is a member
- of, which should be added to the <literal>Set</literal> as literal
string values, e.g. "admin", "user".
- In this case, if the user record is not found and a
<literal>NoResultException</literal> thrown, the
- authentication method returns <literal>false</literal> to indicate
the authentication failed.
- </para>
-
- <tip>
- <para>
- When writing an authenticator method, it is important that it is kept minimal
and free from
- any side-effects. This is because there is no guarantee as to how many times
the authenticator
- method will be called by the security API, and as such it may be invoked
multiple times during
- a single request. Because of this, any special code that should execute upon
a successful or
- failed authentication should be written by implementing an event observer.
See the section on
- Security Events further down in this chapter for more information about which
events are
- raised by Seam Security.
- </para>
- </tip>
-
- <sect3>
- <title>Identity.addRole()</title>
-
- <para>
- The <literal>Identity.addRole()</literal> method behaves
differently depending on whether the current
- session is authenticated or not. If the session is not authenticated, then
<literal>addRole()</literal>
- should <emphasis>only</emphasis> be called during the
authentication process. When called here, the
- role name is placed into a temporary list of pre-authenticated roles. Once
authentication is successful,
- the pre-authenticated roles then become "real" roles, and calling
<literal>Identity.hasRole()</literal>
- for those roles will then return true. The following sequence diagram
represents the list of pre-authenticated
- roles as a first class object to show more clearly how it fits in to the
authentication process.
-
- </para>
-
- <mediaobject>
- <imageobject role="fo">
- <imagedata fileref="images/security-addrole.png"
align="center" scalefit="1"/>
- </imageobject>
- <imageobject role="html">
- <imagedata fileref="images/security-addrole.png"
align="center"/>
- </imageobject>
- </mediaobject>
-
- <para>
- If the current session is already authenticated, then calling
<literal>Identity.addRole()</literal> will
- have the expected effect of immediately granting the specified role to the
current user.
- </para>
-
- </sect3>
-
- <sect3>
- <title>Writing an event observer for security-related events</title>
-
- <para>
- Say for example, that upon a successful login that some user statistics must
be
- updated. This would be done by writing an event observer for the
- <literal>org.jboss.seam.security.loginSuccessful</literal> event,
like this:
- </para>
-
- <programlisting role="JAVA"><![CDATA[ @In UserStats
userStats;
-
- @Observer("org.jboss.seam.security.loginSuccessful")
- public void updateUserStats()
- {
- userStats.setLastLoginDate(new Date());
- userStats.incrementLoginCount();
- }]]></programlisting>
-
- <para>
- This observer method can be placed anywhere, even in the Authenticator
component itself.
- You can find more information about security-related events later in this
chapter.
- </para>
- </sect3>
-
- </sect2>
-
- <sect2>
- <title>Writing a login form</title>
-
- <para>
- The <literal>credentials</literal> component provides both
<literal>username</literal> and <literal>password</literal>
- properties, catering for the most common authentication scenario. These
properties can be bound directly to the
- username and password fields on a login form. Once these properties are set,
calling
- <literal>identity.login()</literal> will authenticate the user using
the provided credentials.
- Here's an example of a simple login form:
- </para>
-
- <programlisting role="XHTML"><![CDATA[<div>
- <h:outputLabel for="name" value="Username"/>
- <h:inputText id="name" value="#{credentials.username}"/>
-</div>
-
-<div>
- <h:outputLabel for="password" value="Password"/>
- <h:inputSecret id="password"
value="#{credentials.password}"/>
-</div>
-
-<div>
- <h:commandButton value="Login"
action="#{identity.login}"/>
-</div>]]></programlisting>
-
- <para>
- Similarly, logging out the user is done by calling
<literal>#{identity.logout}</literal>. Calling this
- action will clear the security state of the currently authenticated user, and
invalidate the user's session.
- </para>
-
- </sect2>
-
- <sect2>
- <title>Configuration Summary</title>
- <para>
- So to sum up, there are the three easy steps to configure authentication:
- </para>
-
- <itemizedlist>
- <listitem>
- <para>
- Configure an authentication method in
<literal>components.xml</literal>.
- </para>
- </listitem>
- <listitem>
- <para>
- Write an authentication method.
- </para>
- </listitem>
- <listitem>
- <para>
- Write a login form so that the user can authenticate.
- </para>
- </listitem>
- </itemizedlist>
-
- </sect2>
-
- <sect2>
- <title>Remember Me</title>
-
- <para>
- Seam Security supports the same kind of "Remember Me" functionality
that is commonly encountered in many
- online web-based applications. It is actually supported in two different
"flavours", or modes - the first
- mode allows the username to be stored in the user's browser as a cookie, and
leaves the entering of the
- password up to the browser (many modern browsers are capable of remembering
passwords).
- </para>
-
- <para>
- The second mode supports the storing of a unique token in a cookie, and allows a
user to authenticate
- automatically upon returning to the site, without having to provide a password.
- </para>
-
- <warning>
- <para>
- Automatic client authentication with a persistent cookie stored on the client
machine is dangerous.
- While convenient for users, any cross-site scripting security hole in your
website would have dramatically more
- serious effects than usual. Without the authentication cookie, the only cookie
to steal for an attacker with XSS
- is the cookie of the current session of a user. This means the attack only
works when the user has an open session -
- which should be a short timespan. However, it is much more attractive and
dangerous if an attacker has the possibility
- to steal a persistent Remember Me cookie that allows him to login without
authentication, at any time. Note that this
- all depends on how well you protect your website against XSS attacks - it's
up to you to make sure that your website
- is 100% XSS safe - a non-trival achievement for any website that allows user
input to be rendered on a page.
- </para>
-
- <para>
- Browser vendors recognized this issue and introduced a "Remember
Passwords" feature - today almost all browsers support
- this. Here, the browser remembers the login username and password for a
particular website and domain, and fills out the
- login form automatically when you don't have an active session with the
website. If you as a website designer then offer
- a convenient login keyboard shortcut, this approach is almost as convenient as
a "Remember Me" cookie and much safer.
- Some browsers (e.g. Safari on OS X) even store the login form data in the
encrypted global operation system keychain.
- Or, in a networked environment, the keychain can be transported with the user
(between laptop and desktop for example),
- while browser cookies are usually not synchronized.
- </para>
-
- <para>
- To summarize: While everyone is doing it, persistent "Remember Me"
cookies with automatic authentication are a bad
- practice and should not be used. Cookies that "remember" only the
users login name, and fill out the login form with
- that username as a convenience, are not an issue.
- </para>
- </warning>
-
- <para>
- To enable the remember me feature for the default (safe, username only) mode, no
special configuration is required.
- In your login form, simply bind the remember me checkbox to
<literal>rememberMe.enabled</literal>, like in the following
- example:
- </para>
-
- <programlisting role="XHTML"><![CDATA[ <div>
- <h:outputLabel for="name" value="User name"/>
- <h:inputText id="name" value="#{credentials.username}"/>
- </div>
-
- <div>
- <h:outputLabel for="password" value="Password"/>
- <h:inputSecret id="password" value="#{credentials.password}"
redisplay="true"/>
- </div>
-
- <div class="loginRow">
- <h:outputLabel for="rememberMe" value="Remember me"/>
- <h:selectBooleanCheckbox id="rememberMe"
value="#{rememberMe.enabled}"/>
- </div>]]></programlisting>
-
- <sect3>
- <title>Token-based Remember-me Authentication</title>
-
- <para>
- To use the automatic, token-based mode of the remember me feature, you must
first configure a token store. The
- most common scenario is to store these authentication tokens within a database
(which Seam supports), however it
- is possible to implement your own token store by implementing the
<literal>org.jboss.seam.security.TokenStore</literal>
- interface. This section will assume you will be using the provided
<literal>JpaTokenStore</literal> implementation
- to store authentication tokens inside a database table.
- </para>
-
- <para>
- The first step is to create a new Entity which will contain the tokens. The
following example shows a possible
- structure that you may use:
- </para>
-
- <programlisting role="JAVA"><![CDATA[@Entity
-public class AuthenticationToken implements Serializable {
- private Integer tokenId;
- private String username;
- private String value;
-
- @Id @GeneratedValue
- public Integer getTokenId() {
- return tokenId;
- }
-
- public void setTokenId(Integer tokenId) {
- this.tokenId = tokenId;
- }
-
- @TokenUsername
- public String getUsername() {
- return username;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
-
- @TokenValue
- public String getValue() {
- return value;
- }
-
- public void setValue(String value) {
- this.value = value;
- }
-}]]></programlisting>
-
- <para>
- As you can see from this listing, a couple of special annotations,
<literal>@TokenUsername</literal> and
- <literal>@TokenValue</literal> are used to configure the username
and token properties of the entity. These
- annotations are required for the entity that will contain the authentication
tokens.
- </para>
-
- <para>
- The next step is to configure <literal>JpaTokenStore</literal> to
use this entity bean to store and retrieve
- authentication tokens. This is done in
<literal>components.xml</literal> by specifying the
<literal>token-class</literal>
- attribute:
- </para>
-
- <programlisting role="XML"><![CDATA[
- <security:jpa-token-store
token-class="org.jboss.seam.example.seamspace.AuthenticationToken"/>
- ]]></programlisting>
-
- <para>
- Once this is done, the last thing to do is to configure the
<literal>RememberMe</literal> component in
- <literal>components.xml</literal> also. Its
<literal>mode</literal> should be set to
<literal>autoLogin</literal>:
- </para>
-
- <programlisting role="XML"><![CDATA[ <security:remember-me
mode="autoLogin"/>
- ]]></programlisting>
-
- <para>
- That is all that is required - automatic authentication will now occur for
users revisiting your site (as long as they
- check the "remember me" checkbox).
- </para>
-
- <para>
- To ensure that users are automatically authenticated when returning to the
site, the following section
- should be placed in components.xml:
- </para>
-
- <programlisting role="XML"><![CDATA[ <event
type="org.jboss.seam.security.notLoggedIn">
- <action execute="#{redirect.captureCurrentView}"/>
- <action execute="#{identity.tryLogin()}"/>
- </event>
- <event type="org.jboss.seam.security.loginSuccessful">
- <action execute="#{redirect.returnToCapturedView}"/>
- </event>]]></programlisting>
-
- </sect3>
-
- </sect2>
-
- <sect2>
- <title>Handling Security Exceptions</title>
-
- <para>
- To prevent users from receiving the default error page in response to a security
error, it's recommended that
- <literal>pages.xml</literal> is configured to redirect security
errors to a more "pretty" page. The two
- main types of exceptions thrown by the security API are:
- </para>
-
- <itemizedlist>
- <listitem>
- <para>
- <literal>NotLoggedInException</literal> - This exception is
thrown if the user attempts to access a
- restricted action or page when they are not logged in.
- </para>
- </listitem>
- <listitem>
- <para>
- <literal>AuthorizationException</literal> - This exception is
only thrown if the user is already logged in,
- and they have attempted to access a restricted action or page for which they
do not have the necessary
- privileges.
- </para>
- </listitem>
- </itemizedlist>
-
- <para>
- In the case of a <literal>NotLoggedInException</literal>, it is
recommended that the user is redirected to
- either a login or registration page so that they can log in. For an
<literal>AuthorizationException</literal>,
- it may be useful to redirect the user to an error page. Here's an example of
a <literal>pages.xml</literal>
- file that redirects both of these security exceptions:
- </para>
-
- <programlisting role="XML"><![CDATA[<pages>
-
- ...
-
- <exception class="org.jboss.seam.security.NotLoggedInException">
- <redirect view-id="/login.xhtml">
- <message>You must be logged in to perform this action</message>
- </redirect>
- </exception>
-
- <exception class="org.jboss.seam.security.AuthorizationException">
- <end-conversation/>
- <redirect view-id="/security_error.xhtml">
- <message>You do not have the necessary security privileges to perform
this action.</message>
- </redirect>
- </exception>
-
-</pages>]]></programlisting>
-
- <para>
- Most web applications require even more sophisticated handling of login
redirection, so
- Seam includes some special functionality for handling this problem.
- </para>
-
- </sect2>
-
- <sect2>
- <title>Login Redirection</title>
-
- <para>
- You can ask Seam to redirect the user to a login screen when an unauthenticated
user tries
- to access a particular view (or wildcarded view id) as follows:
- </para>
-
- <programlisting role="XML"><![CDATA[<pages
login-view-id="/login.xhtml">
-
- <page view-id="/members/*" login-required="true"/>
-
- ...
-
-</pages>]]></programlisting>
-
- <tip>
- <para>
- This is less of a blunt instrument than the exception handler shown above, but
should
- probably be used in conjunction with it.
- </para>
- </tip>
-
- <para>
- After the user logs in, we want to automatically send them back where they came
from, so
- they can retry the action that required logging in. If you add the following
event listeners
- to <literal>components.xml</literal>, attempts to access a restricted
view while not logged
- in will be remembered, so that upon the user successfully logging in they will be
redirected
- to the originally requested view, with any page parameters that existed in the
original
- request.
- </para>
-
- <programlisting role="XML"><![CDATA[<event
type="org.jboss.seam.security.notLoggedIn">
- <action execute="#{redirect.captureCurrentView}"/>
-</event>
-
-<event type="org.jboss.seam.security.postAuthenticate">
- <action execute="#{redirect.returnToCapturedView}"/>
-</event>]]></programlisting>
-
- <para>
- Note that login redirection is implemented as a conversation-scoped mechanism, so
don't end
- the conversation in your <literal>authenticate()</literal> method.
- </para>
-
- </sect2>
-
- <sect2>
- <title>HTTP Authentication</title>
-
- <para>
- Although not recommended for use unless absolutely necessary, Seam provides means
for authenticating
- using either HTTP Basic or HTTP Digest (RFC 2617) methods. To use either form of
authentication,
- the <literal>authentication-filter</literal> component must be
enabled in components.xml:
- </para>
-
- <programlisting role="XML"><![CDATA[
- <web:authentication-filter url-pattern="*.seam"
auth-type="basic"/>
- ]]></programlisting>
-
- <para>
- To enable the filter for basic authentication, set
<literal>auth-type</literal> to <literal>basic</literal>,
- or for digest authentication, set it to <literal>digest</literal>.
If using digest authentication, the
- <literal>key</literal> and <literal>realm</literal> must
also be set:
- </para>
-
- <programlisting role="XML"><![CDATA[
- <web:authentication-filter url-pattern="*.seam"
auth-type="digest" key="AA3JK34aSDlkj" realm="My App"/>
- ]]></programlisting>
-
- <para>
- The <literal>key</literal> can be any String value. The
<literal>realm</literal> is the name of the
- authentication realm that is presented to the user when they authenticate.
- </para>
-
- <sect3>
- <title>Writing a Digest Authenticator</title>
-
- <para>
- If using digest authentication, your authenticator class should extend the
abstract class
-
<literal>org.jboss.seam.security.digest.DigestAuthenticator</literal>, and use
the
- <literal>validatePassword()</literal> method to validate the
user's plain text password
- against the digest request. Here is an example:
- </para>
-
- <programlisting role="JAVA"><![CDATA[
- public boolean authenticate()
- {
- try
- {
- User user = (User) entityManager.createQuery(
- "from User where username = :username")
- .setParameter("username", identity.getUsername())
- .getSingleResult();
-
- return validatePassword(user.getPassword());
- }
- catch (NoResultException ex)
- {
- return false;
- }
- }
- ]]></programlisting>
- </sect3>
-
- </sect2>
-
- <sect2>
- <title>Advanced Authentication Features</title>
-
- <para>
- This section explores some of the advanced features provided by the security API
for addressing more complex
- security requirements.
- </para>
-
- <sect3>
- <title>Using your container's JAAS configuration</title>
-
- <para>
- If you would rather not use the simplified JAAS configuration provided by the
Seam Security API, you may
- instead delegate to the default system JAAS configuration by providing a
<literal>jaas-config-name</literal>
- property in <literal>components.xml</literal>. For example, if you
are using JBoss AS and wish to use
- the <literal>other</literal> policy (which uses the
<literal>UsersRolesLoginModule</literal> login module
- provided by JBoss AS), then the entry in
<literal>components.xml</literal> would look like this:
- </para>
-
- <programlisting role="XML"><![CDATA[<security:identity
jaas-config-name="other"/>]]></programlisting>
-
- <para>
- Please keep in mind that doing this does not mean that your user will be
authenticated in whichever
- container your Seam application is deployed in. It merely instructs Seam
Security to authenticate
- itself using the configured JAAS security policy.
- </para>
- </sect3>
- </sect2>
- </sect1>
-
- <sect1>
- <title>Identity Management</title>
-
- <para>
- Identity Management provides a standard API for the management of a Seam
application's users and roles,
- regardless of which identity store (database, LDAP, etc) is used on the backend.
At the center
- of the Identity Management API is the
<literal>identityManager</literal> component, which provides
- all the methods for creating, modifying and deleting users, granting and revoking
roles, changing passwords,
- enabling and disabling user accounts, authenticating users and listing users and
roles.
- </para>
-
- <para>
- Before it may be used, the <literal>identityManager</literal> must
first be configured with one or more
- <literal>IdentityStore</literal>s. These components do the actual work
of interacting with the backend
- security provider, whether it be a database, LDAP server, or something else.
- </para>
-
- <mediaobject>
- <imageobject role="fo">
- <imagedata fileref="images/security-identitymanager.png"
align="center"/>
- </imageobject>
- <imageobject role="html">
- <imagedata fileref="images/security-identitymanager.png"
align="center"/>
- </imageobject>
- </mediaobject>
-
- <sect2>
- <title>Configuring IdentityManager</title>
-
- <para>
- The <literal>identityManager</literal> component allows for separate
identity stores to be configured
- for authentication and authorization operations. This means that it is possible
for users to
- be authenticated against one identity store, for example an LDAP directory, yet
have their roles
- loaded from another identity store, such as a relational database.
- </para>
-
- <para>
- Seam provides two <literal>IdentityStore</literal> implementations
out of the box;
- <literal>JpaIdentityStore</literal> uses a relational database to
store user and role information,
- and is the default identity store that is used if nothing is explicitly
configured in the
- <literal>identityManager</literal> component. The other
implementation that is provided is
- <literal>LdapIdentityStore</literal>, which uses an LDAP directory to
store users and roles.
- </para>
-
- <para>
- There are two configurable properties for the
<literal>identityManager</literal> component -
- <literal>identityStore</literal> and
<literal>roleIdentityStore</literal>. The value for these
- properties must be an EL expression referring to a Seam component implementing
the
- <literal>IdentityStore</literal> interface. As already mentioned,
- if left unconfigured then <literal>JpaIdentityStore</literal> will be
assumed by default. If
- only the <literal>identityStore</literal> property is configured,
then the same value will be used for
- <literal>roleIdentityStore</literal> also. For example, the
following entry in
- <literal>components.xml</literal> will configure
<literal>identityManager</literal> to use
- an <literal>LdapIdentityStore</literal> for both user-related and
role-related operations:
- </para>
-
- <programlisting role="XML"><![CDATA[
- <security:identity-manager identity-store="#{ldapIdentityStore}"/>
- ]]></programlisting>
-
- <para>
- The following example configures <literal>identityManager</literal>
to use an <literal>LdapIdentityStore</literal>
- for user-related operations, and <literal>JpaIdentityStore</literal>
for role-related operations:
- </para>
-
- <programlisting role="XML"><![CDATA[
- <security:identity-manager
- identity-store="#{ldapIdentityStore}"
- role-identity-store="#{jpaIdentityStore}"/>
- ]]></programlisting>
-
- <para>
- The following sections explain both of these identity store implementations in
greater detail.
- </para>
-
- </sect2>
-
- <sect2>
- <title>JpaIdentityStore</title>
-
- <para>
- This identity store allows for users and roles to be stored inside a relational
database. It is designed
- to be as unrestrictive as possible in regards to database schema design, allowing
a great deal of
- flexibility in the underlying table structure. This is achieved through the use
of a set of special
- annotations, allowing entity beans to be configured to store user and role
records.
- </para>
-
- <sect3>
- <title>Configuring JpaIdentityStore</title>
-
- <para>
- <literal>JpaIdentityStore</literal> requires that both the
<literal>user-class</literal> and
- <literal>role-class</literal> properties are configured. These
properties should refer to the
- entity classes that are to be used to store both user and role records,
respectively. The following
- example shows the configuration from
<literal>components.xml</literal> in the SeamSpace example:
- </para>
-
- <programlisting role="XML"><![CDATA[
- <security:jpa-identity-store
- user-class="org.jboss.seam.example.seamspace.MemberAccount"
- role-class="org.jboss.seam.example.seamspace.MemberRole"/>
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>Configuring the Entities</title>
-
- <para>
- As already mentioned, a set of special annotations are used to configure entity
beans for storing
- users and roles. The following table lists each of the annotations, and their
descriptions.
- </para>
-
- <table>
- <title>User Entity Annotations</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="2*" />
- <colspec colnum="2" colwidth="1*" />
- <colspec colnum="3" colwidth="3*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Annotation</para>
- </entry>
- <entry align="center">
- <para>Status</para>
- </entry>
- <entry align="center">
- <para>Description</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
- <row>
- <entry>
- <para>
- <literal>@UserPrincipal</literal>
- </para>
- </entry>
- <entry>
- <para>Required</para>
- </entry>
- <entry>
- <para>
- This annotation marks the field or method containing the user's
username.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@UserPassword</literal>
- </para>
- </entry>
- <entry>
- <para>Required</para>
- </entry>
- <entry>
- <para>
- This annotation marks the field or method containing the user's
password. It allows a <literal>hash</literal>
- algorithm to be specified for password hashing. Possible values for
<literal>hash</literal> are
- <literal>md5</literal>,
<literal>sha</literal> and <literal>none</literal>. E.g:
- </para>
-
- <programlisting
role="JAVA"><![CDATA[@UserPassword(hash = "md5")
-public String getPasswordHash() {
- return passwordHash;
-}]]></programlisting>
-
- <para>
- If an application requires a hash algorithm that isn't supported
natively by Seam, it
- is possible to extend the <literal>PasswordHash</literal>
component to implement other
- hashing algorithms.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@UserFirstName</literal>
- </para>
- </entry>
- <entry>
- <para>Optional</para>
- </entry>
- <entry>
- <para>
- This annotation marks the field or method containing the user's
first name.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@UserLastName</literal>
- </para>
- </entry>
- <entry>
- <para>Optional</para>
- </entry>
- <entry>
- <para>
- This annotation marks the field or method containing the user's
last name.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@UserEnabled</literal>
- </para>
- </entry>
- <entry>
- <para>Optional</para>
- </entry>
- <entry>
- <para>
- This annotation marks the field or method containing the enabled
status of the user. This should be a boolean
- property, and if not present then all user accounts are assumed to be
enabled.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@UserRoles</literal>
- </para>
- </entry>
- <entry>
- <para>Required</para>
- </entry>
- <entry>
- <para>
- This annotation marks the field or method containing the roles of the
user. This property will be described in
- more detail further down.
- </para>
- </entry>
- </row>
- </tbody>
- </tgroup>
- </table>
-
- <table>
- <title>Role Entity Annotations</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="2*" />
- <colspec colnum="2" colwidth="1*" />
- <colspec colnum="3" colwidth="3*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Annotation</para>
- </entry>
- <entry align="center">
- <para>Status</para>
- </entry>
- <entry align="center">
- <para>Description</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
- <row>
- <entry>
- <para>
- <literal>@RoleName</literal>
- </para>
- </entry>
- <entry>
- <para>Required</para>
- </entry>
- <entry>
- <para>
- This annotation marks the field or method containing the name of the
role.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@RoleGroups</literal>
- </para>
- </entry>
- <entry>
- <para>Optional</para>
- </entry>
- <entry>
- <para>
- This annotation marks the field or method containing the group
memberships of the role.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@RoleConditional</literal>
- </para>
- </entry>
- <entry>
- <para>Optional</para>
- </entry>
- <entry>
- <para>
- This annotation marks the field or method indicating whether the role
is conditional or not.
- Conditional roles are explained later in this chapter.
- </para>
- </entry>
- </row>
-
- </tbody>
- </tgroup>
- </table>
-
- </sect3>
-
- <sect3>
- <title>Entity Bean Examples</title>
-
- <para>
- As mentioned previously, <literal>JpaIdentityStore</literal> is
designed to be as flexible as possible when
- it comes to the database schema design of your user and role tables. This
section looks at a number of
- possible database schemas that can be used to store user and role records.
- </para>
-
- <sect4>
- <title>Minimal schema example</title>
-
- <para>
- In this bare minimal example, a simple user and role table are linked via a
- many-to-many relationship using a cross-reference table named
<literal>UserRoles</literal>.
- </para>
-
- <mediaobject>
- <imageobject role="fo">
- <imagedata fileref="images/security-entities-1.png"
align="center" scalefit="1"/>
- </imageobject>
- <imageobject role="html">
- <imagedata fileref="images/security-entities-1.png"
align="center"/>
- </imageobject>
- </mediaobject>
-
- <programlisting role="JAVA"><![CDATA[@Entity
-public class User {
- private Integer userId;
- private String username;
- private String passwordHash;
- private Set<Role> roles;
-
- @Id @GeneratedValue
- public Integer getUserId() { return userId; }
- public void setUserId(Integer userId) { this.userId = userId; }
-
- @UserPrincipal
- public String getUsername() { return username; }
- public void setUsername(String username) { this.username = username; }
-
- @UserPassword(hash = "md5")
- public String getPasswordHash() { return passwordHash; }
- public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
-
- @UserRoles
- @ManyToMany(targetEntity = Role.class)
- @JoinTable(name = "UserRoles",
- joinColumns = @JoinColumn(name = "UserId"),
- inverseJoinColumns = @JoinColumn(name = "RoleId"))
- public Set<Role> getRoles() { return roles; }
- public void setRoles(Set<Role> roles) { this.roles = roles; }
-}]]></programlisting>
-<programlisting><![CDATA[@Entity
-public class Role {
- private Integer roleId;
- private String rolename;
-
- @Id @Generated
- public Integer getRoleId() { return roleId; }
- public void setRoleId(Integer roleId) { this.roleId = roleId; }
-
- @RoleName
- public String getRolename() { return rolename; }
- public void setRolename(String rolename) { this.rolename = rolename; }
-}]]></programlisting>
-
- </sect4>
-
- <sect4>
- <title>Complex Schema Example</title>
-
- <para>
- This example builds on the above minimal example by including all of the
optional fields, and allowing
- group memberships for roles.
- </para>
-
- <mediaobject>
- <imageobject role="fo">
- <imagedata fileref="images/security-entities-2.png"
align="center" scalefit="1"/>
- </imageobject>
- <imageobject role="html">
- <imagedata fileref="images/security-entities-2.png"
align="center"/>
- </imageobject>
- </mediaobject>
-
- <programlisting role="JAVA"><![CDATA[@Entity
-public class User {
- private Integer userId;
- private String username;
- private String passwordHash;
- private Set<Role> roles;
- private String firstname;
- private String lastname;
- private boolean enabled;
-
- @Id @GeneratedValue
- public Integer getUserId() { return userId; }
- public void setUserId(Integer userId) { this.userId = userId; }
-
- @UserPrincipal
- public String getUsername() { return username; }
- public void setUsername(String username) { this.username = username; }
-
- @UserPassword(hash = "md5")
- public String getPasswordHash() { return passwordHash; }
- public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
-
- @UserFirstName
- public String getFirstname() { return firstname; }
- public void setFirstname(String firstname) { this.firstname = firstname; }
-
- @UserLastName
- public String getLastname() { return lastname; }
- public void setLastname(String lastname) { this.lastname = lastname; }
-
- @UserEnabled
- public boolean isEnabled() { return enabled; }
- public void setEnabled(boolean enabled) { this.enabled = enabled; }
-
- @UserRoles
- @ManyToMany(targetEntity = Role.class)
- @JoinTable(name = "UserRoles",
- joinColumns = @JoinColumn(name = "UserId"),
- inverseJoinColumns = @JoinColumn(name = "RoleId"))
- public Set<Role> getRoles() { return roles; }
- public void setRoles(Set<Role> roles) { this.roles = roles; }
-}]]></programlisting>
-<programlisting><![CDATA[@Entity
-public class Role {
- private Integer roleId;
- private String rolename;
- private boolean conditional;
-
- @Id @Generated
- public Integer getRoleId() { return roleId; }
- public void setRoleId(Integer roleId) { this.roleId = roleId; }
-
- @RoleName
- public String getRolename() { return rolename; }
- public void setRolename(String rolename) { this.rolename = rolename; }
-
- @RoleConditional
- public boolean isConditional() { return conditional; }
- public void setConditional(boolean conditional) { this.conditional = conditional; }
-
- @RoleGroups
- @ManyToMany(targetEntity = Role.class)
- @JoinTable(name = "RoleGroups",
- joinColumns = @JoinColumn(name = "RoleId"),
- inverseJoinColumns = @JoinColumn(name = "GroupId"))
- public Set<Role> getGroups() { return groups; }
- public void setGroups(Set<Role> groups) { this.groups = groups; }
-
-}]]></programlisting>
- </sect4>
-
- </sect3>
-
- <sect3>
- <title>JpaIdentityStore Events</title>
-
- <para>
- When using <literal>JpaIdentityStore</literal> as the identity
store implementation with <literal>IdentityManager</literal>,
- a few events are raised as a result of invoking certain
<literal>IdentityManager</literal> methods.
- </para>
-
- <sect4>
- <title>JpaIdentityStore.EVENT_PRE_PERSIST_USER</title>
-
- <para>
- This event is raised in response to calling
<literal>IdentityManager.createUser()</literal>. Just before the user
- entity is persisted to the database, this event will be raised passing the
entity instance as an event parameter.
- The entity will be an instance of the
<literal>user-class</literal> configured for
<literal>JpaIdentityStore</literal>.
- </para>
-
- <para>
- Writing an observer for this event may be useful for setting additional field
values on the entity, which aren't set
- as part of the standard <literal>createUser()</literal>
functionality.
- </para>
- </sect4>
-
- <sect4>
- <title>JpaIdentityStore.EVENT_USER_CREATED</title>
-
- <para>
- This event is also raised in response to calling
<literal>IdentityManager.createUser()</literal>. However, it is
- raised after the user entity has already been persisted to the database.
Like the <literal>EVENT_PRE_PERSIST_USER</literal>
- event, it also passes the entity instance as an event parameter. It may be
useful to observe this event if you also
- need to persist other entities that reference the user entity, for example
contact detail records or other user-specific
- data.
- </para>
- </sect4>
-
- <sect4>
- <title>JpaIdentityStore.EVENT_USER_AUTHENTICATED</title>
-
- <para>
- This event is raised when calling
<literal>IdentityManager.authenticate()</literal>. It passes the user entity
instance
- as the event parameter, and is useful for reading additional properties from
the user entity that is being authenticated.
- </para>
- </sect4>
- </sect3>
-
- </sect2>
-
- <sect2>
- <title>LdapIdentityStore</title>
-
- <para>
- This identity store implementation is designed for working with user records
stored in an LDAP directory. It is very
- highly configurable, allowing great flexibility in how both users and roles are
stored in the directory. The following
- sections describe the configuration options for this identity store, and provide
some configuration examples.
- </para>
-
- <sect3>
- <title>Configuring LdapIdentityStore</title>
-
- <para>
- The following table describes the available properties that can be configured
in <literal>components.xml</literal> for
- <literal>LdapIdentityStore</literal>.
- </para>
-
- <table>
- <title>LdapIdentityStore Configuration Properties</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="2*" />
- <colspec colnum="2" colwidth="1*" />
- <colspec colnum="3" colwidth="3*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Property</para>
- </entry>
- <entry align="center">
- <para>Default Value</para>
- </entry>
- <entry align="center">
- <para>Description</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
- <row>
- <entry>
- <para>
- <literal>server-address</literal>
- </para>
- </entry>
- <entry>
- <para><literal>localhost</literal></para>
- </entry>
- <entry>
- <para>
- The address of the LDAP server.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>server-port</literal>
- </para>
- </entry>
- <entry>
- <para><literal>389</literal></para>
- </entry>
- <entry>
- <para>
- The port number that the LDAP server is listening on.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>user-context-DN</literal>
- </para>
- </entry>
- <entry>
-
<para><literal>ou=Person,dc=acme,dc=com</literal></para>
- </entry>
- <entry>
- <para>
- The Distinguished Name (DN) of the context containing user records.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>user-DN-prefix</literal>
- </para>
- </entry>
- <entry>
- <para><literal>uid=</literal></para>
- </entry>
- <entry>
- <para>
- This value is prefixed to the front of the username to locate the
user's record.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>user-DN-suffix</literal>
- </para>
- </entry>
- <entry>
-
<para><literal>,ou=Person,dc=acme,dc=com</literal></para>
- </entry>
- <entry>
- <para>
- This value is appended to the end of the username to locate the
user's record.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>role-context-DN</literal>
- </para>
- </entry>
- <entry>
-
<para><literal>ou=Role,dc=acme,dc=com</literal></para>
- </entry>
- <entry>
- <para>
- The DN of the context containing role records.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>role-DN-prefix</literal>
- </para>
- </entry>
- <entry>
- <para><literal>cn=</literal></para>
- </entry>
- <entry>
- <para>
- This value is prefixed to the front of the role name to form the DN
for locating the
- role record.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>role-DN-suffix</literal>
- </para>
- </entry>
- <entry>
-
<para><literal>,ou=Roles,dc=acme,dc=com</literal></para>
- </entry>
- <entry>
- <para>
- This value is appended to the role name to form the DN for locating
the role record.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>bind-DN</literal>
- </para>
- </entry>
- <entry>
-
<para><literal>cn=Manager,dc=acme,dc=com</literal></para>
- </entry>
- <entry>
- <para>
- This is the context used to bind to the LDAP server.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>bind-credentials</literal>
- </para>
- </entry>
- <entry>
- <para><literal>secret</literal></para>
- </entry>
- <entry>
- <para>
- These are the credentials (the password) used to bind to the LDAP
server.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>user-role-attribute</literal>
- </para>
- </entry>
- <entry>
- <para><literal>roles</literal></para>
- </entry>
- <entry>
- <para>
- This is the name of the attribute of the user record that contains
the list of roles that the
- user is a member of.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>role-attribute-is-DN</literal>
- </para>
- </entry>
- <entry>
- <para><literal>true</literal></para>
- </entry>
- <entry>
- <para>
- This boolean property indicates whether the role attribute of the
user record is itself a
- distinguished name.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>user-name-attribute</literal>
- </para>
- </entry>
- <entry>
- <para><literal>uid</literal></para>
- </entry>
- <entry>
- <para>
- Indicates which attribute of the user record contains the username.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>user-password-attribute</literal>
- </para>
- </entry>
- <entry>
- <para><literal>userPassword</literal></para>
- </entry>
- <entry>
- <para>
- Indicates which attribute of the user record contains the user's
password.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>first-name-attribute</literal>
- </para>
- </entry>
- <entry>
- <para><literal>null</literal></para>
- </entry>
- <entry>
- <para>
- Indicates which attribute of the user record contains the user's
first name.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>last-name-attribute</literal>
- </para>
- </entry>
- <entry>
- <para><literal>sn</literal></para>
- </entry>
- <entry>
- <para>
- Indicates which attribute of the user record contains the user's
last name.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>full-name-attribute</literal>
- </para>
- </entry>
- <entry>
- <para><literal>cn</literal></para>
- </entry>
- <entry>
- <para>
- Indicates which attribute of the user record contains the user's
full (common) name.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>enabled-attribute</literal>
- </para>
- </entry>
- <entry>
- <para><literal>null</literal></para>
- </entry>
- <entry>
- <para>
- Indicates which attribute of the user record determines whether the
user is enabled.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>role-name-attribute</literal>
- </para>
- </entry>
- <entry>
- <para><literal>cn</literal></para>
- </entry>
- <entry>
- <para>
- Indicates which attribute of the role record contains the name of the
role.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>object-class-attribute</literal>
- </para>
- </entry>
- <entry>
- <para><literal>objectClass</literal></para>
- </entry>
- <entry>
- <para>
- Indicates which attribute determines the class of an object in the
directory.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>role-object-classes</literal>
- </para>
- </entry>
- <entry>
-
<para><literal>organizationalRole</literal></para>
- </entry>
- <entry>
- <para>
- An array of the object classes that new role records should be
created as.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>user-object-classes</literal>
- </para>
- </entry>
- <entry>
-
<para><literal>person,uidObject</literal></para>
- </entry>
- <entry>
- <para>
- An array of the object classes that new user records should be
created as.
- </para>
- </entry>
- </row>
-
- </tbody>
- </tgroup>
- </table>
- </sect3>
-
- <sect3>
- <title>LdapIdentityStore Configuration Example</title>
-
- <para>
- The following configuration example shows how
<literal>LdapIdentityStore</literal> may be configured for
- an LDAP directory running on fictional host
<literal>directory.mycompany.com</literal>. The users are stored
- within this directory under the context
<literal>ou=Person,dc=mycompany,dc=com</literal>, and are identified
- using the <literal>uid</literal> attribute (which corresponds to
their username). Roles are stored in their
- own context, <literal>ou=Roles,dc=mycompany,dc=com</literal> and
referenced from the user's entry via the
- <literal>roles</literal> attribute. Role entries are identified by
their common name (the <literal>cn</literal> attribute)
- , which corresponds to the role name. In this example, users may be disabled
by setting the value of their
- <literal>enabled</literal> attribute to false.
- </para>
-
- <programlisting role="XML"><![CDATA[
- <security:ldap-identity-store
- server-address="directory.mycompany.com"
- bind-DN="cn=Manager,dc=mycompany,dc=com"
- bind-credentials="secret"
- user-DN-prefix="uid="
- user-DN-suffix=",ou=Person,dc=mycompany,dc=com"
- role-DN-prefix="cn="
- role-DN-suffix=",ou=Roles,dc=mycompany,dc=com"
- user-context-DN="ou=Person,dc=mycompany,dc=com"
- role-context-DN="ou=Roles,dc=mycompany,dc=com"
- user-role-attribute="roles"
- role-name-attribute="cn"
- user-object-classes="person,uidObject"
- enabled-attribute="enabled"
- />]]></programlisting>
-
- </sect3>
-
- </sect2>
-
- <sect2>
- <title>Writing your own IdentityStore</title>
-
- <para>
- Writing your own identity store implementation allows you to authenticate and
perform identity management
- operations against security providers that aren't supported out of the box by
Seam. Only a single class is
- required to achieve this, and it must implement the
- <literal>org.jboss.seam.security.management.IdentityStore</literal>
interface.
- </para>
-
- <para>
- Please refer to the JavaDoc for <literal>IdentityStore</literal> for
a description of the methods that
- must be implemented.
- </para>
- </sect2>
-
-
- <sect2>
- <title>Authentication with Identity Management</title>
-
- <para>
- If you are using the Identity Management features in your Seam application, then
it is not required
- to provide an authenticator component (see previous Authentication section) to
enable authentication.
- Simply omit the <literal>authenticate-method</literal> from the
<literal>identity</literal> configuration
- in <literal>components.xml</literal>, and the
<literal>SeamLoginModule</literal> will by default
- use <literal>IdentityManager</literal> to authenticate your
application's users, without any special
- configuration required.
- </para>
- </sect2>
-
- <sect2>
- <title>Using IdentityManager</title>
-
- <para>
- The <literal>IdentityManager</literal> can be accessed either by
injecting it into your Seam
- component as follows:
- </para>
-
- <programlisting role="JAVA"><![CDATA[ @In IdentityManager
identityManager;]]></programlisting>
-
- <para>
- or by accessing it through its static <literal>instance()</literal>
method:
- </para>
-
- <programlisting role="JAVA"><![CDATA[ IdentityManager
identityManager = IdentityManager.instance();]]></programlisting>
-
- <para>
- The following table describes
<literal>IdentityManager</literal>'s API methods:
- </para>
-
- <table>
- <title>Identity Management API</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="1*" />
- <colspec colnum="2" colwidth="3*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Method</para>
- </entry>
- <entry align="center">
- <para>Returns</para>
- </entry>
- <entry align="center">
- <para>Description</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
-
- <row>
- <entry>
- <para>
- <literal>createUser(String name, String
password)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Creates a new user account, with the specified name and password.
Returns <literal>true</literal>
- if successful, or <literal>false</literal> if not.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>deleteUser(String name)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Deletes the user account with the specified name. Returns
<literal>true</literal>
- if successful, or <literal>false</literal> if not.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>createRole(String role)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Creates a new role, with the specified name. Returns
<literal>true</literal>
- if successful, or <literal>false</literal> if not.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>deleteRole(String name)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Deletes the role with the specified name. Returns
<literal>true</literal>
- if successful, or <literal>false</literal> if not.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>enableUser(String name)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Enables the user account with the specified name. Accounts that are
not enabled are
- not able to authenticate. Returns <literal>true</literal>
if successful, or
- <literal>false</literal> if not.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>disableUser(String name)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Disables the user account with the specified name. Returns
<literal>true</literal> if
- successful, or <literal>false</literal> if not.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>changePassword(String name, String
password)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Changes the password for the user account with the specified name.
Returns
- <literal>true</literal> if successful, or
<literal>false</literal> if not.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>isUserEnabled(String name)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Returns <literal>true</literal> if the specified user
account is enabled, or
- <literal>false</literal> if it isn't.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>grantRole(String name, String role)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Grants the specified role to the specified user or role. The role must
already exist for it to
- be granted. Returns <literal>true</literal> if the role is
successfully granted, or
- <literal>false</literal> if it is already granted to the
user.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>revokeRole(String name, String role)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Revokes the specified role from the specified user or role. Returns
<literal>true</literal>
- if the specified user is a member of the role and it is successfully
revoked, or
- <literal>false</literal> if the user is not a member of the
role.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>userExists(String name)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Returns <literal>true</literal> if the specified user
exists, or <literal>false</literal>
- if it doesn't.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>listUsers()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>List</literal>
- </para>
- </entry>
- <entry>
- <para>
- Returns a list of all user names, sorted in alpha-numeric order.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>listUsers(String filter)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>List</literal>
- </para>
- </entry>
- <entry>
- <para>
- Returns a list of all user names filtered by the specified filter
parameter, sorted in alpha-numeric order.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>listRoles()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>List</literal>
- </para>
- </entry>
- <entry>
- <para>
- Returns a list of all role names.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>getGrantedRoles(String name)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>List</literal>
- </para>
- </entry>
- <entry>
- <para>
- Returns a list of the names of all the roles explicitly granted to the
specified user name.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>getImpliedRoles(String name)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>List</literal>
- </para>
- </entry>
- <entry>
- <para>
- Returns a list of the names of all the roles implicitly granted to the
specified user name.
- Implicitly granted roles include those that are not directly granted to
a user, rather they are
- granted to the roles that the user is a member of. For example, is the
<literal>admin</literal>
- role is a member of the <literal>user</literal> role, and a
user is a member of the <literal>admin</literal>
- role, then the implied roles for the user are both the
<literal>admin</literal>, and <literal>user</literal>
- roles.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>authenticate(String name, String
password)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Authenticates the specified username and password using the configured
Identity Store. Returns
- <literal>true</literal> if successful or
<literal>false</literal> if authentication failed.
- Successful authentication implies nothing beyond the return value of
the method. It does not
- change the state of the <literal>Identity</literal>
component - to perform a proper Seam login the
- <literal>Identity.login()</literal> must be used instead.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>addRoleToGroup(String role, String
group)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Adds the specified role as a member of the specified group. Returns
true if the operation is successful.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>removeRoleFromGroup(String role, String
group)</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- Removes the specified role from the specified group. Returns true if
the operation is successful.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>listRoles()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>List</literal>
- </para>
- </entry>
- <entry>
- <para>
- Lists the names of all roles.
- </para>
- </entry>
- </row>
-
- </tbody>
- </tgroup>
- </table>
-
- <para>
- Using the Identity Management API requires that the calling user has the
appropriate authorization to invoke
- its methods. The following table describes the permission requirements for each
of the methods in
- <literal>IdentityManager</literal>. The permission targets listed
below are literal String values.
- </para>
-
- <table>
- <title>Identity Management Security Permissions</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="1*" />
- <colspec colnum="2" colwidth="3*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Method</para>
- </entry>
- <entry align="center">
- <para>Permission Target</para>
- </entry>
- <entry align="center">
- <para>Permission Action</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
- <row>
- <entry>
- <para>
- <literal>createUser()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.user</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>create</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>deleteUser()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.user</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>delete</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>createRole()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.role</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>create</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>deleteRole()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.role</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>delete</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>enableUser()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.user</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>update</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>disableUser()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.user</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>update</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>changePassword()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.user</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>update</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>isUserEnabled()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.user</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>read</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>grantRole()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.user</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>update</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>revokeRole()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.user</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>update</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>userExists()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.user</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>read</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>listUsers()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.user</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>read</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>listRoles()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.role</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>read</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>addRoleToGroup()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.role</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>update</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>removeRoleFromGroup()</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.role</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>update</literal>
- </para>
- </entry>
- </row>
- </tbody>
- </tgroup>
- </table>
-
- <para>
- The following code listing provides an example set of security rules that grants
access to all
- Identity Management-related methods to members of the
<literal>admin</literal> role:
- </para>
-
- <programlisting><![CDATA[rule ManageUsers
- no-loop
- activation-group "permissions"
-when
- check: PermissionCheck(name == "seam.user", granted == false)
- Role(name == "admin")
-then
- check.grant();
-end
-
-rule ManageRoles
- no-loop
- activation-group "permissions"
-when
- check: PermissionCheck(name == "seam.role", granted == false)
- Role(name == "admin")
-then
- check.grant();
-end
-]]></programlisting>
-
- </sect2>
-
- </sect1>
-
- <sect1>
- <title>Error Messages</title>
-
- <para>
- The security API produces a number of default faces messages for various
security-related events.
- The following table lists the message keys that can be used to override these
messages by specifying
- them in a <literal>message.properties</literal> resource file. To
suppress the message, just put the
- key with an empty value in the resource file.
- </para>
-
- <table>
- <title>Security Message Keys</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="2*" />
- <colspec colnum="2" colwidth="3*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Message Key</para>
- </entry>
- <entry align="center">
- <para>Description</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
-
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.loginSuccessful</literal>
- </para>
- </entry>
- <entry>
- <para>
- This message is produced when a user successfully logs in via the
security API.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.loginFailed</literal>
- </para>
- </entry>
- <entry>
- <para>
- This message is produced when the login process fails, either because the
user provided an
- incorrect username or password, or because authentication failed in some
other way.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.NotLoggedIn</literal>
- </para>
- </entry>
- <entry>
- <para>
- This message is produced when a user attempts to perform an action or
access a page that requires
- a security check, and the user is not currently authenticated.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.AlreadyLoggedIn</literal>
- </para>
- </entry>
- <entry>
- <para>
- This message is produced when a user that is already authenticated
attempts to log in again.
- </para>
- </entry>
- </row>
- </tbody>
- </tgroup>
- </table>
- </sect1>
-
- <sect1>
- <title>Authorization</title>
-
- <para>
- There are a number of authorization mechanisms provided by the Seam Security API
for securing access to
- components, component methods, and pages. This section describes each of these.
An important thing to
- note is that if you wish to use any of the advanced features (such as rule-based
permissions) then
- your <literal>components.xml</literal> may need to be configured to
support this - see the Configuration section
- above.
- </para>
-
- <sect2>
- <title>Core concepts</title>
-
- <para>
- Seam Security is built around the premise of users being granted roles and/or
permissions, allowing them to
- perform operations that may not otherwise be permissible for users without the
necessary security privileges.
- Each of the authorization mechanisms provided by the Seam Security API are built
upon this core concept of roles and
- permissions, with an extensible framework providing multiple ways to secure
application resources.
- </para>
-
- <sect3>
- <title>What is a role?</title>
-
- <para>
- A role is a <emphasis>group</emphasis>, or
<emphasis>type</emphasis>, of user that may have been granted certain
- privileges for performing one or more specific actions within an application.
They are simple constructs, consisting
- of just a name such as "admin", "user",
"customer", etc. They can be granted either to users (or in some cases to other
- roles), and are used to create logical groups of users for the convenient
assignment of specific application privileges.
- </para>
-
- <mediaobject>
- <imageobject role="fo">
- <imagedata fileref="images/security-roleclass.png"
align="center"/>
- </imageobject>
- <imageobject role="html">
- <imagedata fileref="images/security-roleclass.png"
align="center"/>
- </imageobject>
- </mediaobject>
- </sect3>
-
- <sect3>
- <title>What is a permission?</title>
-
- <para>
- A permission is a privilege (sometimes once-off) for performing a single,
specific action. It is entirely possible to
- build an application using nothing but permissions, however roles offer a
higher level of convenience when granting
- privileges to groups of users. They are slightly more complex in structure
than roles, essentially consisting of three
- "aspects"; a target, an action, and a recipient. The target of a
permission is the object (or an arbitrary name or class)
- for which a particular action is allowed to be performed by a specific
recipient (or user). For example, the user "Bob"
- may have permission to delete customer objects. In this case, the permission
target may be "customer", the permission
- action would be "delete" and the recipient would be "Bob".
- </para>
-
-
- <mediaobject>
- <imageobject role="fo">
- <imagedata fileref="images/security-permissionclass.png"
align="center"/>
- </imageobject>
- <imageobject role="html">
- <imagedata fileref="images/security-permissionclass.png"
align="center"/>
- </imageobject>
- </mediaobject>
-
- <para>
- Within this documentation, permissions are generally represented in the form
<literal>target:action</literal>
- (omitting the recipient, although in reality one is always required).
- </para>
- </sect3>
-
- </sect2>
-
- <sect2>
- <title>Securing components</title>
-
- <para>
- Let's start by examining the simplest form of authorization, component
security, starting with the
- <literal>@Restrict</literal> annotation.
- </para>
-
- <note>
- <title>@Restrict vs Typesafe security annotations</title>
-
- <para>
- While using the <literal>@Restrict</literal> annotation provides a
powerful and flexible method for security component methods
- due to its ability to support EL expressions, it is recommended that the
typesafe equivalent (described later) be
- used, at least for the compile-time safety it provides.
- </para>
- </note>
-
- <sect3>
- <title>The @Restrict annotation</title>
-
- <para>
- Seam components may be secured either at the method or the class level, using
the <literal>@Restrict</literal>
- annotation. If both a method and it's declaring class are annotated with
<literal>@Restrict</literal>,
- the method restriction will take precedence (and the class restriction will not
apply). If a method
- invocation fails a security check, then an exception will be thrown as per the
contract for
- <literal>Identity.checkRestriction()</literal> (see Inline
Restrictions). A <literal>@Restrict</literal>
- on just the component class itself is equivalent to adding
<literal>@Restrict</literal> to each of its
- methods.
- </para>
-
- <para>
- An empty <literal>@Restrict</literal> implies a permission check of
<literal>componentName:methodName</literal>.
- Take for example the following component method:
- </para>
-
- <programlisting
role="JAVA"><![CDATA[@Name("account")
-public class AccountAction {
- @Restrict public void delete() {
- ...
- }
-}]]></programlisting>
-
- <para>
- In this example, the implied permission required to call the
<literal>delete()</literal> method is
- <literal>account:delete</literal>. The equivalent of this would be
to write
-
<literal>@Restrict("#{s:hasPermission('account','delete')}")</literal>.
Now let's look at
- another example:
- </para>
-
- <programlisting role="JAVA"><![CDATA[@Restrict
@Name("account")
-public class AccountAction {
- public void insert() {
- ...
- }
- @Restrict("#{s:hasRole('admin')}")
- public void delete() {
- ...
- }
-}]]></programlisting>
-
- <para>
- This time, the component class itself is annotated with
<literal>@Restrict</literal>. This means that
- any methods without an overriding <literal>@Restrict</literal>
annotation require an implicit permission check.
- In the case of this example, the <literal>insert()</literal> method
requires a permission of
- <literal>account:insert</literal>, while the
<literal>delete()</literal> method requires that the user is a
- member of the <literal>admin</literal> role.
- </para>
-
- <para>
- Before we go any further, let's address the
<literal>#{s:hasRole()}</literal> expression seen in the above
- example. Both <literal>s:hasRole</literal> and
<literal>s:hasPermission</literal> are EL functions, which
- delegate to the correspondingly named methods of the
<literal>Identity</literal> class. These
- functions can be used within any EL expression throughout the entirety of the
security API.
- </para>
-
- <para>
- Being an EL expression, the value of the
<literal>@Restrict</literal> annotation may reference any objects that
- exist within a Seam context. This is extremely useful when performing
permission checks for a specific
- object instance. Look at this example:
- </para>
-
- <programlisting
role="JAVA"><![CDATA[@Name("account")
-public class AccountAction {
- @In Account selectedAccount;
- @Restrict("#{s:hasPermission(selectedAccount,'modify')}")
- public void modify() {
- selectedAccount.modify();
- }
-}]]></programlisting>
-
- <para>
- The interesting thing to note from this example is the reference to
<literal>selectedAccount</literal>
- seen within the <literal>hasPermission()</literal> function call.
The value of this variable will be
- looked up from within the Seam context, and passed to the
<literal>hasPermission()</literal> method
- in <literal>Identity</literal>, which in this case can then
determine if the user has the required
- permission for modifying the specified <literal>Account</literal>
object.
- </para>
- </sect3>
-
- <sect3>
- <title>Inline restrictions</title>
- <para>
- Sometimes it might be desirable to perform a security check in code, without
using the
- <literal>@Restrict</literal> annotation. In this situation, simply
use
- <literal>Identity.checkRestriction()</literal> to evaluate a
security expression, like this:
- </para>
-
- <programlisting role="JAVA"><![CDATA[public void
deleteCustomer() {
-
Identity.instance().checkRestriction("#{s:hasPermission(selectedCustomer,'delete')}");
-}]]></programlisting>
-
- <para>
- If the expression specified doesn't evaluate to
<literal>true</literal>, either
- </para>
-
- <itemizedlist>
- <listitem>
- <para>
- if the user is not logged in, a
<literal>NotLoggedInException</literal>
- exception is thrown or
- </para>
- </listitem>
- <listitem>
- <para>
- if the user is logged in, an
<literal>AuthorizationException</literal>
- exception is thrown.
- </para>
- </listitem>
- </itemizedlist>
-
- <para>
- It is also possible to call the <literal>hasRole()</literal> and
<literal>hasPermission()</literal>
- methods directly from Java code:
- </para>
-
- <programlisting role="JAVA"><![CDATA[if
(!Identity.instance().hasRole("admin"))
- throw new AuthorizationException("Must be admin to perform this action");
-
-if (!Identity.instance().hasPermission("customer", "create"))
- throw new AuthorizationException("You may not create new
customers");]]></programlisting>
-
- </sect3>
- </sect2>
-
- <sect2>
- <title>Security in the user interface</title>
-
- <para>
- One indication of a well designed user interface is that the user is not
presented with options for
- which they don't have the necessary privileges to use. Seam Security allows
conditional rendering of
- either 1) sections of a page or 2) individual controls, based upon the privileges
of the user, using
- the very same EL expressions that are used for component security.
- </para>
-
- <para>
- Let's take a look at some examples of interface security. First of all,
let's pretend that we have a
- login form that should only be rendered if the user is not already logged in.
Using the
- <literal>identity.isLoggedIn()</literal> property, we can write
this:
- </para>
-
- <programlisting role="XHTML"><![CDATA[<h:form
class="loginForm" rendered="#{not
identity.loggedIn}">]]></programlisting>
-
- <para>
- If the user isn't logged in, then the login form will be rendered - very
straight forward so far.
- Now let's pretend there is a menu on the page that contains some actions
which should only be accessible
- to users in the <literal>manager</literal> role. Here's one way
that these could be written:
- </para>
-
- <programlisting role="XHTML"><![CDATA[<h:outputLink
action="#{reports.listManagerReports}"
rendered="#{s:hasRole('manager')}">
- Manager Reports
-</h:outputLink>]]></programlisting>
-
- <para>
- This is also quite straight forward. If the user is not a member of the
<literal>manager</literal>
- role, then the outputLink will not be rendered. The
<literal>rendered</literal> attribute can
- generally be used on the control itself, or on a surrounding
<literal><s:div></literal> or
- <literal><s:span></literal> control.
- </para>
-
- <para>
- Now for something more complex. Let's say you have a
<literal>h:dataTable</literal> control on a
- page listing records for which you may or may not wish to render action links
depending on the
- user's privileges. The <literal>s:hasPermission</literal> EL
function allows us to pass in an
- object parameter which can be used to determine whether the user has the
requested permission
- for that object or not. Here's how a dataTable with secured links might
look:
- </para>
-
- <programlisting role="XHTML"><![CDATA[<h:dataTable
value="#{clients}" var="cl">
- <h:column>
- <f:facet name="header">Name</f:facet>
- #{cl.name}
- </h:column>
- <h:column>
- <f:facet name="header">City</f:facet>
- #{cl.city}
- </h:column>
- <h:column>
- <f:facet name="header">Action</f:facet>
- <s:link value="Modify Client"
action="#{clientAction.modify}"
- rendered="#{s:hasPermission(cl,'modify')}"/>
- <s:link value="Delete Client"
action="#{clientAction.delete}"
- rendered="#{s:hasPermission(cl,'delete')}"/>
- </h:column>
-</h:dataTable>]]></programlisting>
-
- </sect2>
-
- <sect2>
- <title>Securing pages</title>
- <para>
- Page security requires that the application is using a
<literal>pages.xml</literal> file, however is
- extremely simple to configure. Simply include a
<literal><restrict/></literal> element within
- the <literal>page</literal> elements that you wish to secure. If no
explicit restriction is specified
- by the <literal>restrict</literal> element, an implied permission of
<literal>/viewId.xhtml:render</literal>
- will be checked when the page is accessed via a non-faces (GET) request, and a
permission of
- <literal>/viewId.xhtml:restore</literal> will be required when any
JSF postback (form submission) originates
- from the page. Otherwise, the specified restriction will be evaluated as a
standard security expression.
- Here's a couple of examples:
- </para>
-
- <programlisting role="XML"><![CDATA[<page
view-id="/settings.xhtml">
- <restrict/>
-</page>]]></programlisting>
-
- <para>
- This page has an implied permission of
<literal>/settings.xhtml:render</literal> required for non-faces
- requests and an implied permission of
<literal>/settings.xhtml:restore</literal> for faces requests.
- </para>
-
- <programlisting role="XML"><![CDATA[<page
view-id="/reports.xhtml">
- <restrict>#{s:hasRole('admin')}</restrict>
-</page>]]></programlisting>
-
- <para>
- Both faces and non-faces requests to this page require that the user is a member
of the
- <literal>admin</literal> role.
- </para>
-
- </sect2>
-
- <sect2>
- <title>Securing Entities</title>
-
- <para>
- Seam security also makes it possible to apply security restrictions to read,
insert, update and
- delete actions for entities.
- </para>
-
- <para>
- To secure all actions for an entity class, add a
<literal>@Restrict</literal> annotation on the class
- itself:
- </para>
-
- <programlisting role="JAVA"><![CDATA[@Entity
-@Name("customer")
-@Restrict
-public class Customer {
- ...
-}]]></programlisting>
-
- <para>
- If no expression is specified in the <literal>@Restrict</literal>
annotation, the default security check
- that is performed is a permission check of
<literal>entity:action</literal>, where the permission target
- is the entity instance, and the <literal>action</literal> is either
<literal>read</literal>, <literal>insert</literal>,
- <literal>update</literal> or <literal>delete</literal>.
- </para>
-
- <para>
- It is also possible to only restrict certain actions, by placing a
<literal>@Restrict</literal> annotation
- on the relevent entity lifecycle method (annotated as follows):
- </para>
-
- <itemizedlist>
- <listitem>
- <para>
- <literal>@PostLoad</literal> - Called after an entity instance is
loaded from the database. Use this
- method to configure a <literal>read</literal> permission.
- </para>
- </listitem>
- <listitem>
- <para>
- <literal>@PrePersist</literal> - Called before a new instance of
the entity is inserted. Use this method
- to configure an <literal>insert</literal> permission.
- </para>
- </listitem>
- <listitem>
- <para>
- <literal>@PreUpdate</literal> - Called before an entity is
updated. Use this method
- to configure an <literal>update</literal> permission.
- </para>
- </listitem>
- <listitem>
- <para>
- <literal>@PreRemove</literal> - Called before an entity is
deleted. Use this method
- to configure a <literal>delete</literal> permission.
- </para>
- </listitem>
- </itemizedlist>
-
- <para>
- Here's an example of how an entity would be configured to perform a security
check for any <literal>insert</literal>
- operations. Please note that the method is not required to do anything, the only
important thing in regard to
- security is how it is annotated:
- </para>
-
- <programlisting role="JAVA"><![CDATA[
- @PrePersist @Restrict
- public void prePersist() {}
- ]]></programlisting>
-
- <note>
- <title>Using
<literal>/META-INF/orm.xml</literal></title>
-
-
- <para>
- You can also specify the call back method in
<literal>/META-INF/orm.xml</literal>:
- </para>
-
- <programlisting role="XML"><![CDATA[<?xml
version="1.0" encoding="UTF-8"?>
-<entity-mappings
xmlns="http://java.sun.com/xml/ns/persistence/orm"
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
- version="1.0">
-
- <entity class="Customer">
- <pre-persist method-name="prePersist" />
- </entity>
-
-</entity-mappings>]]></programlisting>
-
- <para>
- Of course, you still need to annotate the
<literal>prePersist()</literal>
- method on <literal>Customer</literal> with
<literal>@Restrict</literal>
- </para>
- </note>
-
- <para>
- And here's an example of an entity permission rule that checks if the
authenticated user is allowed to insert
- a new <literal>MemberBlog</literal> record (from the seamspace
example). The entity for which the security
- check is being made is automatically inserted into the working memory (in this
case <literal>MemberBlog</literal>):
- </para>
-
- <programlisting><![CDATA[rule InsertMemberBlog
- no-loop
- activation-group "permissions"
-when
- principal: Principal()
- memberBlog: MemberBlog(member : member ->
(member.getUsername().equals(principal.getName())))
- check: PermissionCheck(target == memberBlog, action == "insert", granted ==
false)
-then
- check.grant();
-end;]]></programlisting>
-
- <para>
- This rule will grant the permission
<literal>memberBlog:insert</literal> if the currently authenticated
- user (indicated by the <literal>Principal</literal> fact) has the
same name as the member for which the
- blog entry is being created. The "<literal>principal:
Principal()</literal>" structure that can be seen in the
- example code is a variable binding - it binds the instance of the
<literal>Principal</literal> object from the
- working memory (placed there during authentication) and assigns it to a variable
called <literal>principal</literal>.
- Variable bindings allow the value to be referred to in other places, such as the
following line which compares the
- member's username to the <literal>Principal</literal> name. For
more details, please refer to the JBoss Rules documentation.
- </para>
-
- <para>
- Finally, we need to install a listener class that integrates Seam security with
- your JPA provider.
- </para>
-
- <sect3>
- <title>Entity security with JPA</title>
-
- <para>
- Security checks for EJB3 entity beans are performed with an
<literal>EntityListener</literal>.
- You can install this listener by using the following
<literal>META-INF/orm.xml</literal> file:
- </para>
-
- <programlisting role="XML"><![CDATA[<?xml
version="1.0" encoding="UTF-8"?>
-<entity-mappings
xmlns="http://java.sun.com/xml/ns/persistence/orm"
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
- version="1.0">
-
- <persistence-unit-metadata>
- <persistence-unit-defaults>
- <entity-listeners>
- <entity-listener
class="org.jboss.seam.security.EntitySecurityListener"/>
- </entity-listeners>
- </persistence-unit-defaults>
- </persistence-unit-metadata>
-
-</entity-mappings>]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>Entity security with a Managed Hibernate Session</title>
-
- <para>
- If you are using a Hibernate <literal>SessionFactory</literal>
configured via Seam,
- and are using annotations, or <literal>orm.xml</literal>, then you
don't
- need to do anything special to use entity security.
- </para>
-
- </sect3>
-
- </sect2>
-
- <sect2>
- <title>Typesafe Permission Annotations</title>
-
- <para>
- Seam provides a number of annotations that may be used as an alternative to
<literal>@Restrict</literal>, which have
- the added advantage of providing compile-time safety as they don't support
arbitrary EL expressions in the same way
- that <literal>@Restrict</literal> does.
- </para>
-
- <para>
- Out of the box, Seam comes with annotations for standard CRUD-based permissions,
however it is a simple matter to
- add your own. The following annotations are provided in the
<literal>org.jboss.seam.annotations.security</literal> package:
- </para>
-
- <itemizedlist>
- <listitem>
- <para>@Insert</para>
- </listitem>
- <listitem>
- <para>@Read</para>
- </listitem>
- <listitem>
- <para>@Update</para>
- </listitem>
- <listitem>
- <para>@Delete</para>
- </listitem>
- </itemizedlist>
-
- <para>
- To use these annotations, simply place them on the method or parameter for which
you wish to perform a security check.
- If placed on a method, then they should specify a target class for which the
permission will be checked. Take the
- following example:
- </para>
-
- <programlisting><![CDATA[ @Insert(Customer.class)
- public void createCustomer() {
- ...
- }]]></programlisting>
-
- <para>
- In this example, a permission check will be performed for the user to ensure that
they have the rights to create
- new <literal>Customer</literal> objects. The target of the
permission check will be <literal>Customer.class</literal>
- (the actual <literal>java.lang.Class</literal> instance itself), and
the action is the lower case representation of the
- annotation name, which in this example is <literal>insert</literal>.
- </para>
-
- <para>
- It is also possible to annotate the parameters of a component method in the same
way. If this is done, then it is
- not required to specify a permission target (as the parameter value itself will
be the target of the permission check):
- </para>
-
- <programlisting><![CDATA[ public void updateCustomer(@Update Customer
customer) {
- ...
- }]]></programlisting>
-
- <para>
- To create your own security annotation, you simply need to annotate it with
<literal>@PermissionCheck</literal>, for example:
- </para>
-
- <programlisting role="JAVA"><![CDATA[@Target({METHOD,
PARAMETER})
-@Documented
-@Retention(RUNTIME)
-@Inherited
-@PermissionCheck
-public @interface Promote {
- Class value() default void.class;
-}]]></programlisting>
-
- <para>
- If you wish to override the default permisison action name (which is the lower
case version of the annotation name) with
- another value, you can specify it within the
<literal>@PermissionCheck</literal> annotation:
- </para>
-
-
<programlisting><![CDATA[@PermissionCheck("upgrade")]]></programlisting>
-
- </sect2>
-
- <sect2>
- <title>Typesafe Role Annotations</title>
-
- <para>
- In addition to supporting typesafe permission annotation, Seam Security also
provides typesafe role annotations that
- allow you to restrict access to component methods based on the role memberships
of the currently authenticated user.
- Seam provides one such annotation out of the box,
<literal>org.jboss.seam.annotations.security.Admin</literal>, used
- to restrict access to a method to users that are a member of the
<literal>admin</literal> role (so long as your
- own application supports such a role). To create your own role annotations,
simply meta-annotate them with
- <literal>org.jboss.seam.annotations.security.RoleCheck</literal>,
like in the following example:
- </para>
-
- <programlisting><![CDATA[@Target({METHOD})
-@Documented
-@Retention(RUNTIME)
-@Inherited
-@RoleCheck
-public @interface User {
-}]]></programlisting>
-
- <para>
- Any methods subsequently annotated with the <literal>@User</literal>
annotation as shown in the above example
- will be automatically intercepted and the user checked for the membership of the
corresponding role name
- (which is the lower case version of the annotation name, in this case
<literal>user</literal>).
- </para>
-
- </sect2>
-
- <sect2>
- <title>The Permission Authorization Model</title>
-
- <para>
- Seam Security provides an extensible framework for resolving application
permissions. The following class diagram
- shows an overview of the main components of the permission framework:
- </para>
-
- <mediaobject>
- <imageobject role="fo">
- <imagedata fileref="images/security-permission-classdiagram.png"
align="center"/>
- </imageobject>
- <imageobject role="html">
- <imagedata fileref="images/security-permission-classdiagram.png"
align="center"/>
- </imageobject>
- </mediaobject>
-
- <para>
- The relevant classes are explained in more detail in the following sections.
- </para>
-
- <sect3>
- <title>PermissionResolver</title>
-
- <para>
- This is actually an interface, which provides methods for resolving individual
object permissions. Seam provides
- the following built-in <literal>PermissionResolver</literal>
implementations, which are described in more detail later
- in the chapter:
- </para>
-
- <itemizedlist>
- <listitem>
- <para><literal>RuleBasedPermissionResolver</literal> - This
permission resolver uses Drools to resolve rule-based
- permission checks.</para>
- </listitem>
- <listitem>
- <para><literal>PersistentPermissionResolver</literal> -
This permission resolver stores object permissions in a
- persistent store, such as a relational database.</para>
- </listitem>
- </itemizedlist>
-
- <sect4>
- <title>Writing your own PermissionResolver</title>
-
- <para>
- It is very simple to implement your own permission resolver. The
<literal>PermissionResolver</literal>
- interface defines only two methods that must be implemented, as shown by the
following table. By deploying
- your own <literal>PermissionResolver</literal> implementation in
your Seam project, it will be automatically
- scanned during deployment and registered with the default
<literal>ResolverChain</literal>.
- </para>
-
- <table>
- <title>PermissionResolver interface</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="2*" />
- <colspec colnum="2" colwidth="3*" />
- <colspec colnum="3" colwidth="3*" />
- <colspec colnum="4" colwidth="4*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Return type</para>
- </entry>
- <entry align="center">
- <para>Method</para>
- </entry>
- <entry align="center">
- <para>Description</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
-
- <row>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>hasPermission(Object target, String
action)</literal>
- </para>
- </entry>
- <entry>
- <para>
- This method must resolve whether the currently authenticated user
(obtained via a call to
- <literal>Identity.getPrincipal()</literal>) has the
permission specified by the <literal>target</literal>
- and <literal>action</literal> parameters. It should
return <literal>true</literal> if the user has
- the permission, or <literal>false</literal> if they
don't.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>void</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>filterSetByAction(Set<Object> targets,
String action)</literal>
- </para>
- </entry>
- <entry>
- <para>
- This method should remove any objects from the specified set, that
would
- return <literal>true</literal> if passed to the
<literal>hasPermission()</literal> method with the
- same <literal>action</literal> parameter value.
- </para>
- </entry>
- </row>
-
- </tbody>
- </tgroup>
- </table>
-
- <note>
- <para>
- As they are cached in the user's session, any custom
<literal>PermissionResolver</literal>
- implementations must adhere to a couple of restrictions. Firstly, they may
not contain any
- state that is finer-grained than session scope (and the scope of the
component itself should
- either be application or session). Secondly, they must not use dependency
- injection as they may be accessed from multiple threads simultaneously. In
fact, for
- performance reasons it is recommended that they are annotated with
- <literal>@BypassInterceptors</literal> to bypass Seam's
interceptor stack altogether.
- </para>
- </note>
-
- </sect4>
- </sect3>
-
- <sect3>
- <title>ResolverChain</title>
-
- <para>
- A <literal>ResolverChain</literal> contains an ordered list of
<literal>PermissionResolver</literal>s, for the
- purpose of resolving object permissions for a particular object class or
permission target.
- </para>
-
- <para>
- The default <literal>ResolverChain</literal> consists of all
permission resolvers discovered during
- application deployment. The
<literal>org.jboss.seam.security.defaultResolverChainCreated</literal>
- event is raised (and the <literal>ResolverChain</literal> instance
passed as an event parameter)
- when the default <literal>ResolverChain</literal> is created. This
allows additional resolvers that
- for some reason were not discovered during deployment to be added, or for
resolvers that are in the
- chain to be re-ordered or removed.
- </para>
-
- <para>
- The following sequence diagram shows the interaction between the components of
the permission framework during a
- permission check (explanation follows). A permission check can originate from
a number of possible sources,
- for example - the security interceptor, the
<literal>s:hasPermission</literal> EL function, or via an API
- call to <literal>Identity.checkPermission</literal>:
- </para>
-
- <mediaobject>
- <imageobject role="fo">
- <imagedata fileref="images/security-permission-sequence.png"
align="center"/>
- </imageobject>
- <imageobject role="html">
- <imagedata fileref="images/security-permission-sequence.png"
align="center"/>
- </imageobject>
- </mediaobject>
-
- <itemizedlist>
- <listitem>
- <para>
- 1. A permission check is initiated somewhere (either in code or via an EL
- expression) resulting in a call to
<literal>Identity.hasPermission()</literal>.
- </para>
- </listitem>
- <listitem>
- <para>
- 1.1. <literal>Identity</literal> invokes
- <literal>PermissionMapper.resolvePermission()</literal>,
passing in the
- permission to be resolved.
- </para>
- </listitem>
- <listitem>
- <para>
- 1.1.1. <literal>PermissionMapper</literal> maintains a
<literal>Map</literal> of
- <literal>ResolverChain</literal> instances, keyed by class. It
uses this map
- to locate the correct <literal>ResolverChain</literal> for the
permission's
- target object. Once it has the correct
<literal>ResolverChain</literal>, it
- retrieves the list of <literal>PermissionResolver</literal>s it
contains via
- a call to <literal>ResolverChain.getResolvers()</literal>.
- </para>
- </listitem>
- <listitem>
- <para>
- 1.1.2. For each <literal>PermissionResolver</literal> in the
<literal>ResolverChain</literal>,
- the <literal>PermissionMapper</literal> invokes its
<literal>hasPermission()</literal> method,
- passing in the permission instance to be checked. If any of the
<literal>PermissionResolver</literal>s
- return <literal>true</literal>, then the permission check has
succeeded and the
- <literal>PermissionMapper</literal> also returns
<literal>true</literal> to <literal>Identity</literal>.
- If none of the <literal>PermissionResolver</literal>s return
true, then the permission check
- has failed.
- </para>
- </listitem>
- </itemizedlist>
- </sect3>
-
- </sect2>
-
- <sect2>
- <title>RuleBasedPermissionResolver</title>
-
- <para>
- One of the built-in permission resolvers provided by Seam,
<literal>RuleBasedPermissionResolver</literal>
- allows permissions to be evaluated based on a set of Drools (JBoss Rules)
security rules. A couple of the
- advantages of using a rule engine are 1) a centralized location for the business
logic that is used to
- evaluate user permissions, and 2) speed - Drools uses very efficient algorithms
for evaluating large
- numbers of complex rules involving multiple conditions.
- </para>
-
- <sect3>
- <title>Requirements</title>
-
- <para>
- If using the rule-based permission features provided by Seam Security, the
following jar files are required by Drools
- to be distributed with your project:
- </para>
-
- <itemizedlist>
- <listitem>
- <para>drools-api.jar</para>
- </listitem>
- <listitem>
- <para>drools-compiler.jar</para>
- </listitem>
- <listitem>
- <para>drools-core.jar</para>
- </listitem>
- <listitem>
- <para>drools-decisiontables.jar</para>
- </listitem>
- <listitem>
- <para>drools-templates.jar</para>
- </listitem>
- <listitem>
- <para>janino.jar</para>
- </listitem>
- <listitem>
- <para>antlr-runtime.jar</para>
- </listitem>
- <listitem>
- <para>mvel2.jar</para>
- </listitem>
- </itemizedlist>
-
- </sect3>
-
- <sect3>
- <title>Configuration</title>
-
- <para>
- The configuration for
<literal>RuleBasedPermissionResolver</literal> requires that a Drools rule
base is first
- configured in <literal>components.xml</literal>. By default, it
expects that the rule base is named
- <literal>securityRules</literal>, as per the following example:
- </para>
-
- <programlisting role="XML"><![CDATA[<components
xmlns="http://jboss.com/products/seam/components"
-
xmlns:core="http://jboss.com/products/seam/core"
-
xmlns:security="http://jboss.com/products/seam/security"
-
xmlns:drools="http://jboss.com/products/seam/drools"
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation=
- "http://jboss.com/products/seam/core
http://jboss.com/products/seam/core-2.2.xsd
-
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.2.xsd
-
http://jboss.com/products/seam/drools
http://jboss.com/products/seam/drools-2.2.xsd
-
http://jboss.com/products/seam/security
http://jboss.com/products/seam/security-2.2.xsd">
-
- <drools:rule-base name="securityRules">
- <drools:rule-files>
- <value>/META-INF/security.drl</value>
- </drools:rule-files>
- </drools:rule-base>
-
- </components>]]></programlisting>
-
- <para>
- The default rule base name can be overridden by specifying the
<literal>security-rules</literal>
- property for <literal>RuleBasedPermissionResolver</literal>:
- </para>
-
- <programlisting><![CDATA[
- <security:rule-based-permission-resolver
security-rules="#{prodSecurityRules}"/>]]></programlisting>
-
- <para>
- Once the <literal>RuleBase</literal> component is configured,
it's time to write the security rules.
- </para>
- </sect3>
-
- <sect3>
- <title>Writing Security Rules</title>
-
- <para>
- The first step to writing security rules is to create a new rule file in the
<literal>/META-INF</literal>
- directory of your application's jar file. Usually this file would be named
something like
- <literal>security.drl</literal>, however you can name it whatever
you like as long as it is configured
- correspondingly in <literal>components.xml</literal>.
- </para>
-
- <para>
- So what should the security rules file contain? At this stage it might be a
good idea to at least skim
- through the Drools documentation, however to get started here's an
extremely simple example:
- </para>
-
- <programlisting><![CDATA[package MyApplicationPermissions;
-
- import org.jboss.seam.security.permission.PermissionCheck;
- import org.jboss.seam.security.Role;
-
- rule CanUserDeleteCustomers
- when
- c: PermissionCheck(target == "customer", action == "delete")
- Role(name == "admin")
- then
- c.grant();
- end]]></programlisting>
-
- <para>
- Let's break this down step by step. The first thing we see is the package
declaration. A package in Drools
- is essentially a collection of rules. The package name can be anything you
want - it doesn't relate
- to anything else outside the scope of the rule base.
- </para>
-
- <para>
- The next thing we can notice is a couple of import statements for the
<literal>PermissionCheck</literal>
- and <literal>Role</literal> classes. These imports inform the rules
engine that we'll be referencing
- these classes within our rules.
- </para>
-
- <para>
- Finally we have the code for the rule. Each rule within a package should be
given a unique name (usually
- describing the purpose of the rule). In this case our rule is called
<literal>CanUserDeleteCustomers</literal>
- and will be used to check whether a user is allowed to delete a customer
record.
- </para>
-
- <para>
- Looking at the body of the rule definition we can notice two distinct sections.
Rules have what is known
- as a left hand side (LHS) and a right hand side (RHS). The LHS consists of the
conditional part of the
- rule, i.e. a list of conditions which must be satisfied for the rule to fire.
The LHS is represented by
- the <literal>when</literal> section. The RHS is the consequence,
or action section of the rule that will
- only be fired if all of the conditions in the LHS are met. The RHS is
represented by the
- <literal>then</literal> section. The end of the rule is denoted by
the <literal>end</literal> line.
- </para>
-
- <para>
- If we look at the LHS of the rule, we see two conditions listed there.
Let's examine the first condition:
- </para>
-
- <programlisting><![CDATA[c: PermissionCheck(target ==
"customer", action == "delete")]]></programlisting>
-
- <para>
- In plain english, this condition is stating that there must exist a
<literal>PermissionCheck</literal> object
- with a <literal>target</literal> property equal to
"customer", and an <literal>action</literal> property equal
- to "delete" within the working memory.
- </para>
-
- <para>
- So what is the working memory? Also known as a "stateful session" in
Drools terminology, the working memory
- is a session-scoped object that contains the contextual information that is
required by the rules engine to
- make a decision about a permission check. Each time the
<literal>hasPermission()</literal> method is called,
- a temporary <literal>PermissionCheck</literal> object, or
<emphasis>Fact</emphasis>, is inserted into the
- working memory. This <literal>PermissionCheck</literal>
corresponds exactly to the permission that is being
- checked, so for example if you call
<literal>hasPermission("account", "create")</literal> then
a
- <literal>PermissionCheck</literal> object with a
<literal>target</literal> equal to "account" and
- <literal>action</literal> equal to "create" will be
inserted into the working memory for the duration of the
- permission check.
- </para>
-
- <para>
- Besides the <literal>PermissionCheck</literal> facts, there is also
a <literal>org.jboss.seam.security.Role</literal>
- fact for each of the roles that the authenticated user is a member of. These
<literal>Role</literal> facts
- are synchronized with the user's authenticated roles at the beginning of
every permission check. As a consequence,
- any <literal>Role</literal> object that is inserted into the
working memory during the course of a permission
- check will be removed before the next permission check occurs, if the
authenticated user is not actually a member of
- that role. Besides the <literal>PermissionCheck</literal> and
<literal>Role</literal> facts, the working
- memory also contains the <literal>java.security.Principal</literal>
object that was created as a result of
- the authentication process.
- </para>
-
- <para>
- It is also possible to insert additional long-lived facts into the working
memory by calling
-
<literal>RuleBasedPermissionResolver.instance().getSecurityContext().insert()</literal>,
- passing the object as a parameter. The exception to this is
<literal>Role</literal> objects, which as
- already discussed are synchronized at the start of each permission check.
- </para>
-
- <para>
- Getting back to our simple example, we can also notice that the first line of
our LHS is prefixed with
- <literal>c:</literal>. This is a variable binding, and is used to
refer back to the object that is
- matched by the condition (in this case, the
<literal>PermissionCheck</literal>). Moving on to the
- second line of our LHS, we see this:
- </para>
-
- <programlisting><![CDATA[Role(name ==
"admin")]]></programlisting>
-
- <para>
- This condition simply states that there must be a
<literal>Role</literal> object with a
- <literal>name</literal> of "admin" within the working
memory. As already mentioned, user roles are inserted into
- the working memory at the beginning of each permission check. So, putting both
conditions together, this
- rule is essentially saying "I will fire if you are checking for the
<literal>customer:delete</literal>
- permission and the user is a member of the <literal>admin</literal>
role".
- </para>
-
- <para>
- So what is the consequence of the rule firing? Let's take a look at the
RHS of the rule:
- </para>
-
- <programlisting><![CDATA[c.grant()]]></programlisting>
-
- <para>
- The RHS consists of Java code, and in this case is invoking the
<literal>grant()</literal>
- method of the <literal>c</literal> object, which as already
mentioned is a variable binding
- for the <literal>PermissionCheck</literal> object. Besides the
<literal>name</literal> and
- <literal>action</literal> properties of the
<literal>PermissionCheck</literal> object, there
- is also a <literal>granted</literal> property which is initially
set to <literal>false</literal>.
- Calling <literal>grant()</literal> on a
<literal>PermissionCheck</literal> sets the
- <literal>granted</literal> property to
<literal>true</literal>, which means that the permission
- check was successful, allowing the user to carry out whatever action the
permission check was
- intended for.
- </para>
- </sect3>
-
- <sect3>
- <title>Non-String permission targets</title>
-
- <para>
- So far we have only seen permission checks for String-literal permission
targets. It is of course also
- possible to write security rules for permission targets of more complex types.
For example, let's say that you wish
- to write a security rule to allow your users to create blog comments. The
following rule demonstrates
- how this may be expressed, by requiring the target of the permission check to
be an instance of
- <literal>MemberBlog</literal>, and also requiring that the
currently authenticated user is a member of the
- <literal>user</literal> role:
- </para>
-
- <programlisting><![CDATA[rule CanCreateBlogComment
- no-loop
- activation-group "permissions"
-when
- blog: MemberBlog()
- check: PermissionCheck(target == blog, action == "create", granted == false)
- Role(name == "user")
-then
- check.grant();
-end
-]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>Wildcard permission checks</title>
-
- <para>
- It is possible to implement a wildcard permission check (which allows all
actions for a given permission
- target), by omitting the <literal>action</literal> constraint for
the <literal>PermissionCheck</literal> in
- your rule, like this:
- </para>
-
- <programlisting><![CDATA[rule CanDoAnythingToCustomersIfYouAreAnAdmin
-when
- c: PermissionCheck(target == "customer")
- Role(name == "admin")
-then
- c.grant();
-end;
- ]]></programlisting>
-
- <para>
- This rule allows users with the <literal>admin</literal> role to
perform <emphasis>any</emphasis> action for
- any <literal>customer</literal> permission check.
- </para>
-
- </sect3>
-
- </sect2>
-
- <sect2>
- <title>PersistentPermissionResolver</title>
-
- <para>
- Another built-in permission resolver provided by Seam,
<literal>PersistentPermissionResolver</literal>
- allows permissions to be loaded from persistent storage, such as a relational
database. This permission
- resolver provides ACL style instance-based security, allowing for specific object
permissions to be assigned
- to individual users and roles. It also allows for persistent, arbitrarily-named
permission targets (not
- necessarily object/class based) to be assigned in the same way.
- </para>
-
- <sect3>
- <title>Configuration</title>
-
- <para>
- Before it can be used,
<literal>PersistentPermissionResolver</literal> must be configured with a
- valid <literal>PermissionStore</literal> in
<literal>components.xml</literal>. If not configured,
- it will attempt to use the default permission store,
<literal>JpaIdentityStore</literal> (see section
- further down for details). To use a permission store other than the default,
configure the
- <literal>permission-store</literal> property as follows:
- </para>
-
- <programlisting><![CDATA[ <security:persistent-permission-resolver
permission-store="#{myCustomPermissionStore}"/>]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>Permission Stores</title>
-
- <para>
- A permission store is required for
<literal>PersistentPermissionResolver</literal> to connect to the
- backend storage where permissions are persisted. Seam provides one
<literal>PermissionStore</literal>
- implementation out of the box,
<literal>JpaPermissionStore</literal>, which is used to store
- permissions inside a relational database. It is possible to write your own
permission store
- by implementing the <literal>PermissionStore</literal> interface,
which defines the following
- methods:
- </para>
-
- <table>
- <title>PermissionStore interface</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="2*" />
- <colspec colnum="2" colwidth="3*" />
- <colspec colnum="3" colwidth="3*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Return type</para>
- </entry>
- <entry align="center">
- <para>Method</para>
- </entry>
- <entry align="center">
- <para>Description</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
-
- <row>
- <entry>
- <para>
- <literal>List<Permission></literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>listPermissions(Object target)</literal>
- </para>
- </entry>
- <entry>
- <para>
- This method should return a <literal>List</literal> of
<literal>Permission</literal> objects
- representing all the permissions granted for the specified target
object.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>List<Permission></literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>listPermissions(Object target, String
action)</literal>
- </para>
- </entry>
- <entry>
- <para>
- This method should return a <literal>List</literal> of
<literal>Permission</literal> objects
- representing all the permissions with the specified action, granted
for the specified target object.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>List<Permission></literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>listPermissions(Set<Object> targets,
String action)</literal>
- </para>
- </entry>
- <entry>
- <para>
- This method should return a <literal>List</literal> of
<literal>Permission</literal> objects
- representing all the permissions with the specified action, granted
for the specified set of
- target objects.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>grantPermission(Permission)</literal>
- </para>
- </entry>
- <entry>
- <para>
- This method should persist the specified
<literal>Permission</literal> object to the backend
- storage, returning true if successful.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>grantPermissions(List<Permission>
permissions)</literal>
- </para>
- </entry>
- <entry>
- <para>
- This method should persist all of the
<literal>Permission</literal> objects contained in the
- specified <literal>List</literal>, returning true if
successful.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>revokePermission(Permission
permission)</literal>
- </para>
- </entry>
- <entry>
- <para>
- This method should remove the specified
<literal>Permission</literal> object from persistent storage.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>revokePermissions(List<Permission>
permissions)</literal>
- </para>
- </entry>
- <entry>
- <para>
- This method should remove all of the
<literal>Permission</literal> objects in the specified list
- from persistent storage.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>List<String></literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>listAvailableActions(Object target)</literal>
- </para>
- </entry>
- <entry>
- <para>
- This method should return a list of all the available actions (as
Strings) for the class of the
- specified target object. It is used in conjunction with permission
management to build the user
- interface for granting specific class permissions (see section
further down).
- </para>
- </entry>
- </row>
- </tbody>
- </tgroup>
- </table>
-
- </sect3>
-
- <sect3>
- <title>JpaPermissionStore</title>
-
- <para>
- This is the default <literal>PermissionStore</literal>
implementation (and the only one provided by Seam), which
- uses a relational database to store permissions. Before it can be used it must
be configured with either one or
- two entity classes for storing user and role permissions. These entity classes
must be annotated with a special
- set of security annotations to configure which properties of the entity
correspond to various aspects of the
- permissions being stored.
- </para>
-
- <para>
- If you wish to use the same entity (i.e. a single database table) to store both
user and role permissions, then
- only the <literal>user-permission-class</literal> property is
required to be configured. If you wish to use
- separate tables for storing user and role permissions, then in addition to the
<literal>user-permission-class</literal>
- property you must also configure the
<literal>role-permission-class</literal> property.
- </para>
-
- <para>For example, to configure a single entity class to store both user
and role permissions:</para>
-
- <programlisting
role="XML"><![CDATA[<security:jpa-permission-store
user-permission-class="com.acme.model.AccountPermission"/>]]></programlisting>
-
- <para>To configure separate entity classes for storing user and role
permissions:</para>
-
- <programlisting
role="XML"><![CDATA[<security:jpa-permission-store
user-permission-class="com.acme.model.UserPermission"
-
role-permission-class="com.acme.model.RolePermission"/>]]></programlisting>
-
- <sect4>
- <title>Permission annotations</title>
-
- <para>
- As mentioned, the entity classes that contain the user and role permissions
must be configured with a
- special set of annotations, contained within the
<literal>org.jboss.seam.annotations.security.permission</literal> package.
- The following table lists each of these annotations along with a description
of how they are used:
- </para>
-
- <table>
- <title>Entity Permission annotations</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="2*" />
- <colspec colnum="2" colwidth="3*" />
- <colspec colnum="3" colwidth="3*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Annotation</para>
- </entry>
- <entry align="center">
- <para>Target</para>
- </entry>
- <entry align="center">
- <para>Description</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
-
- <row>
- <entry>
- <para>
- <literal>@PermissionTarget</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>FIELD,METHOD</literal>
- </para>
- </entry>
- <entry>
- <para>
- This annotation identifies the property of the entity that will
contain the permission target. The property
- should be of type <literal>java.lang.String</literal>.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@PermissionAction</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>FIELD,METHOD</literal>
- </para>
- </entry>
- <entry>
- <para>
- This annotation identifies the property of the entity that will
contain the permission action. The property
- should be of type <literal>java.lang.String</literal>.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@PermissionUser</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>FIELD,METHOD</literal>
- </para>
- </entry>
- <entry>
- <para>
- This annotation identifies the property of the entity that will
contain the recipient user for the permission. It should
- be of type <literal>java.lang.String</literal> and
contain the user's username.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@PermissionRole</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>FIELD,METHOD</literal>
- </para>
- </entry>
- <entry>
- <para>
- This annotation identifies the property of the entity that will
contain the recipient role for the permission. It should
- be of type <literal>java.lang.String</literal> and
contain the role name.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@PermissionDiscriminator</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>FIELD,METHOD</literal>
- </para>
- </entry>
- <entry>
- <para>
- This annotation should be used when the same entity/table is used
to store both user and role permissions. It identifies
- the property of the entity that is used to discriminate between
user and role permissions. By default, if the column
- value contains the string literal
<literal>user</literal>, then the record will be treated as a user permission.
If it
- contains the string literal <literal>role</literal>,
then it will be treated as a role permission. It is also possible
- to override these defaults by specifying the
<literal>userValue</literal> and <literal>roleValue</literal>
properties
- within the annotation. For example, to use
<literal>u</literal> and <literal>r</literal> instead of
<literal>user</literal>
- and <literal>role</literal>, the annotation would be
written like this:
- </para>
-
- <programlisting
role="JAVA"><![CDATA[@PermissionDiscriminator(userValue = "u",
roleValue = "r")]]></programlisting>
- </entry>
- </row>
-
- </tbody>
- </tgroup>
- </table>
-
- </sect4>
-
- <sect4>
- <title>Example Entity</title>
-
- <para>
- Here is an example of an entity class that is used to store both user and
role permissions. The following class can be found
- inside the SeamSpace example:
- </para>
-
- <programlisting role="JAVA"><![CDATA[
-@Entity
-public class AccountPermission implements Serializable {
- private Integer permissionId;
- private String recipient;
- private String target;
- private String action;
- private String discriminator;
-
- @Id @GeneratedValue
- public Integer getPermissionId() {
- return permissionId;
- }
-
- public void setPermissionId(Integer permissionId) {
- this.permissionId = permissionId;
- }
-
- @PermissionUser @PermissionRole
- public String getRecipient() {
- return recipient;
- }
-
- public void setRecipient(String recipient) {
- this.recipient = recipient;
- }
-
- @PermissionTarget
- public String getTarget() {
- return target;
- }
-
- public void setTarget(String target) {
- this.target = target;
- }
-
- @PermissionAction
- public String getAction() {
- return action;
- }
-
- public void setAction(String action) {
- this.action = action;
- }
-
- @PermissionDiscriminator
- public String getDiscriminator() {
- return discriminator;
- }
-
- public void setDiscriminator(String discriminator) {
- this.discriminator = discriminator;
- }
-}
- ]]></programlisting>
-
- <para>
- As can be seen in the above example, the
<literal>getDiscriminator()</literal> method has been annotated
- with the <literal>@PermissionDiscriminator</literal> annotation,
to allow <literal>JpaPermissionStore</literal> to
- determine which records represent user permissions and which represent role
permissions. In addition, it
- can also be seen that the <literal>getRecipient()</literal>
method is annotated with both
- <literal>@PermissionUser</literal> and
<literal>@PermissionRole</literal> annotations. This is perfectly valid,
- and simply means that the <literal>recipient</literal> property
of the entity will either contain the name
- of the user or the name of the role, depending on the value of the
<literal>discriminator</literal> property.
- </para>
-
- </sect4>
-
- <sect4>
- <title>Class-specific Permission Configuration</title>
-
- <para>
- A further set of class-specific annotations can be used to configure a
specific set of allowable permissions
- for a target class. These permissions can be found in the
<literal>org.jboss.seam.annotation.security.permission</literal>
- package:
- </para>
-
- <table>
- <title>Class Permission Annotations</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="2*" />
- <colspec colnum="2" colwidth="3*" />
- <colspec colnum="3" colwidth="3*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Annotation</para>
- </entry>
- <entry align="center">
- <para>Target</para>
- </entry>
- <entry align="center">
- <para>Description</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
-
- <row>
- <entry>
- <para>
- <literal>@Permissions</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>TYPE</literal>
- </para>
- </entry>
- <entry>
- <para>
- A container annotation, this annotation may contain an array of
<literal>@Permission</literal> annotations.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>@Permission</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>TYPE</literal>
- </para>
- </entry>
- <entry>
- <para>
- This annotation defines a single allowable permission action for
the target class. Its <literal>action</literal>
- property must be specified, and an optional
<literal>mask</literal> property may also be specified if permission
- actions are to be persisted as bitmasked values (see next
section).
- </para>
- </entry>
- </row>
-
- </tbody>
- </tgroup>
- </table>
-
- <para>
- Here's an example of the above annotations in action. The following
class can also be found in the SeamSpace example:
- </para>
-
- <programlisting role="JAVA"><![CDATA[@Permissions({
- @Permission(action = "view"),
- @Permission(action = "comment")
-})
-@Entity
-public class MemberImage implements Serializable {]]></programlisting>
-
- <para>
- This example demonstrates how two allowable permission actions,
<literal>view</literal> and <literal>comment</literal>
- can be declared for the entity class
<literal>MemberImage</literal>.
- </para>
-
- </sect4>
-
- <sect4>
- <title>Permission masks</title>
-
- <para>
- By default, multiple permissions for the same target object and recipient
will be persisted as a single database record,
- with the <literal>action</literal> property/column containing a
comma-separated list of the granted actions. To reduce
- the amount of physical storage required to persist a large number of
permissions, it is possible to use a bitmasked
- integer value (instead of a comma-separated list) to store the list of
permission actions.
- </para>
-
- <para>
- For example, if recipient "Bob" is granted both the
<literal>view</literal> and <literal>comment</literal>
permissions
- for a particular <literal>MemberImage</literal> (an entity bean)
instance, then by default the <literal>action</literal> property of the
- permission entity will contain
"<literal>view,comment</literal>", representing the two granted
permission actions.
- Alternatively, if using bitmasked values for the permission actions, as
defined like so:
- </para>
-
- <programlisting role="JAVA"><![CDATA[@Permissions({
- @Permission(action = "view", mask = 1),
- @Permission(action = "comment", mask = 2)
-})
-@Entity
-public class MemberImage implements Serializable {]]></programlisting>
-
- <para>
- The <literal>action</literal> property will instead simply
contain "3" (with both the 1 bit and 2 bit switched on). Obviously
- for a large number of allowable actions for any particular target class, the
storage required for the permission records
- is greatly reduced by using bitmasked actions.
- </para>
-
- <para>
- Obviously, it is very important that the <literal>mask</literal>
values specified are powers of 2.
- </para>
- </sect4>
-
- <sect4>
- <title>Identifier Policy</title>
-
- <para>
- When storing or looking up permissions,
<literal>JpaPermissionStore</literal> must be able to uniquely identify
specific
- object instances to effectively operate on its permissions. To achieve this,
an <emphasis>identifier strategy</emphasis>
- may be assigned to each target class for the generation of unique identifier
values. Each identifier strategy
- implementation knows how to generate unique identifiers for a particular type
of class, and it is a simple matter to
- create new identifier strategies.
- </para>
-
- <para>
- The <literal>IdentifierStrategy</literal> interface is very
simple, declaring only two methods:
- </para>
-
- <programlisting role="JAVA"><![CDATA[public interface
IdentifierStrategy {
- boolean canIdentify(Class targetClass);
- String getIdentifier(Object target);
-}]]></programlisting>
-
- <para>
- The first method, <literal>canIdentify()</literal> simply returns
<literal>true</literal> if the identifier strategy
- is capable of generating a unique identifier for the specified target class.
The second method,
- <literal>getIdentifier()</literal> returns the unique identifier
value for the specified target object.
- </para>
-
- <para>
- Seam provides two <literal>IdentifierStrategy</literal>
implementations, <literal>ClassIdentifierStrategy</literal>
- and <literal>EntityIdentifierStrategy</literal> (see next
sections for details).
- </para>
-
- <para>
- To explicitly configure a specific identifier strategy to use for a
particular class, it should be annotated with
-
<literal>org.jboss.seam.annotations.security.permission.Identifier</literal>,
and the value should be set to
- a concrete implementation of the
<literal>IdentifierStrategy</literal> interface. An optional
<literal>name</literal>
- property can also be specified, the effect of which is dependent upon the
actual <literal>IdentifierStrategy</literal>
- implementation used.
- </para>
- </sect4>
-
- <sect4>
- <title>ClassIdentifierStrategy</title>
-
- <para>
- This identifier strategy is used to generate unique identifiers for classes,
and will use the value of the
- <literal>name</literal> (if specified) in the
<literal>@Identifier</literal> annotation. If there is no
- <literal>name</literal> property provided, then it will attempt
to use the component name of the class
- (if the class is a Seam component), or as a last resort it will create an
identifier based on the name
- of the class (excluding the package name). For example, the identifier for
the following class will
- be "<literal>customer</literal>":
- </para>
-
- <programlisting role="JAVA"><![CDATA[@Identifier(name =
"customer")
-public class Customer {]]></programlisting>
-
- <para>
- The identifier for the following class will be
"<literal>customerAction</literal>":
- </para>
-
- <programlisting
role="JAVA"><![CDATA[@Name("customerAction")
-public class CustomerAction { ]]></programlisting>
-
- <para>
- Finally, the identifier for the following class will be
"<literal>Customer</literal>":
- </para>
-
- <programlisting><![CDATA[public class Customer {
]]></programlisting>
-
- </sect4>
-
- <sect4>
- <title>EntityIdentifierStrategy</title>
-
- <para>
- This identifier strategy is used to generate unique identifiers for entity
beans. It does so by
- concatenating the entity name (or otherwise configured name) with a string
representation of the
- primary key value of the entity. The rules for generating the name section
of the identifier are
- similar to <literal>ClassIdentifierStrategy</literal>. The
primary key value (i.e. the
- <emphasis>id</emphasis> of the entity) is obtained using the
<literal>PersistenceProvider</literal>
- component, which is able to correctly determine the value regardless of which
persistence implementation
- is used within the Seam application. For entities not annotated with
<literal>@Entity</literal>, it is
- necessary to explicitly configure the identifier strategy on the entity class
itself, for example:
- </para>
-
- <programlisting role="JAVA"><![CDATA[@Identifier(value =
EntityIdentifierStrategy.class)
-public class Customer { ]]></programlisting>
-
- <para>
- For an example of the type of identifier values generated, assume we have the
following entity class:
- </para>
-
- <programlisting role="JAVA"><![CDATA[@Entity
-public class Customer {
- private Integer id;
- private String firstName;
- private String lastName;
-
- @Id
- public Integer getId() { return id; }
- public void setId(Integer id) { this.id = id; }
-
- public String getFirstName() { return firstName; }
- public void setFirstName(String firstName) { this.firstName = firstName; }
-
- public String getLastName() { return lastName; }
- public void setLastName(String lastName) { this.lastName = lastName; }
-}]]></programlisting>
-
- <para>
- For a <literal>Customer</literal> instance with an
<literal>id</literal> value of <literal>1</literal>,
- the value of the identifier would be
"<literal>Customer:1</literal>". If the entity class is annotated
- with an explicit identifier name, like so:
- </para>
-
- <programlisting role="JAVA"><![CDATA[@Entity
-@Identifier(name = "cust")
-public class Customer { ]]></programlisting>
-
- <para>
- Then a <literal>Customer</literal> with an
<literal>id</literal> value of <literal>123</literal>
- would have an identifier value of
"<literal>cust:123</literal>".
- </para>
- </sect4>
-
- </sect3>
-
- </sect2>
-
- </sect1>
-
- <sect1>
- <title>Permission Management</title>
-
- <para>
- In much the same way that Seam Security provides an Identity Management API for the
management of users and roles,
- it also provides a Permissions Management API for the management of persistent user
permissions, via the
- <literal>PermissionManager</literal> component.
- </para>
-
- <sect2>
- <title>PermissionManager</title>
-
- <para>
- The <literal>PermissionManager</literal> component is an
application-scoped Seam component that provides a number of
- methods for managing permissions. Before it can be used, it must be configured
with a permission store (although by
- default it will attempt to use <literal>JpaPermissionStore</literal>
if it is available). To explicitly configure a
- custom permission store, specify the
<literal>permission-store</literal> property in components.xml:
- </para>
-
- <programlisting role="XML"><![CDATA[
-<security:permission-manager permission-store="#{ldapPermissionStore}"/>
- ]]></programlisting>
-
- <para>
- The following table describes each of the available methods provided by
<literal>PermissionManager</literal>:
- </para>
-
- <table>
- <title>PermissionManager API methods</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="2*" />
- <colspec colnum="2" colwidth="3*" />
- <colspec colnum="3" colwidth="3*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Return type</para>
- </entry>
- <entry align="center">
- <para>Method</para>
- </entry>
- <entry align="center">
- <para>Description</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
-
- <row>
- <entry>
- <para>
- <literal>List<Permission></literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>listPermissions(Object target, String
action)</literal>
- </para>
- </entry>
- <entry>
- <para>
- Returns a list of <literal>Permission</literal> objects
representing all of the permissions that
- have been granted for the specified target and action.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>List<Permission></literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>listPermissions(Object target)</literal>
- </para>
- </entry>
- <entry>
- <para>
- Returns a list of <literal>Permission</literal> objects
representing all of the permissions that
- have been granted for the specified target and action.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>grantPermission(Permission permission)</literal>
- </para>
- </entry>
- <entry>
- <para>
- Persists (grants) the specified
<literal>Permission</literal> to the backend permission store.
- Returns true if the operation was successful.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>grantPermissions(List<Permission>
permissions)</literal>
- </para>
- </entry>
- <entry>
- <para>
- Persists (grants) the specified list of
<literal>Permission</literal>s to the backend permission store.
- Returns true if the operation was successful.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>revokePermission(Permission permission)</literal>
- </para>
- </entry>
- <entry>
- <para>
- Removes (revokes) the specified
<literal>Permission</literal> from the backend permission store.
- Returns true if the operation was successful.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>boolean</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>revokePermissions(List<Permission>
permissions)</literal>
- </para>
- </entry>
- <entry>
- <para>
- Removes (revokes) the specified list of
<literal>Permission</literal>s from the backend permission store.
- Returns true if the operation was successful.
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>List<String></literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>listAvailableActions(Object target)</literal>
- </para>
- </entry>
- <entry>
- <para>
- Returns a list of the available actions for the specified target
object. The actions that this
- method returns are dependent on the
<literal>@Permission</literal> annotations configured on the
- target object's class.
- </para>
- </entry>
- </row>
-
- </tbody>
- </tgroup>
- </table>
-
- </sect2>
-
- <sect2>
- <title>Permission checks for PermissionManager operations</title>
-
- <para>
- Invoking the methods of <literal>PermissionManager</literal> requires
that the currently-authenticated user
- has the appropriate authorization to perform that management operation. The
following table lists the required
- permissions that the current user must have.
- </para>
-
- <table>
- <title>Permission Management Security Permissions</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="1*" />
- <colspec colnum="2" colwidth="3*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Method</para>
- </entry>
- <entry align="center">
- <para>Permission Target</para>
- </entry>
- <entry align="center">
- <para>Permission Action</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
- <row>
- <entry>
- <para>
- <literal>listPermissions()</literal>
- </para>
- </entry>
- <entry>
- <para>
- The specified <literal>target</literal>
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.read-permissions</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>grantPermission()</literal>
- </para>
- </entry>
- <entry>
- <para>
- The target of the specified <literal>Permission</literal>,
or each of the targets
- for the specified list of <literal>Permission</literal>s
(depending on which method is
- called).
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.grant-permission</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>grantPermission()</literal>
- </para>
- </entry>
- <entry>
- <para>
- The target of the specified <literal>Permission</literal>.
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.grant-permission</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>grantPermissions()</literal>
- </para>
- </entry>
- <entry>
- <para>
- Each of the targets of the specified list of
<literal>Permission</literal>s.
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.grant-permission</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>revokePermission()</literal>
- </para>
- </entry>
- <entry>
- <para>
- The target of the specified <literal>Permission</literal>.
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.revoke-permission</literal>
- </para>
- </entry>
- </row>
-
- <row>
- <entry>
- <para>
- <literal>revokePermissions()</literal>
- </para>
- </entry>
- <entry>
- <para>
- Each of the targets of the specified list of
<literal>Permission</literal>s.
- </para>
- </entry>
- <entry>
- <para>
- <literal>seam.revoke-permission</literal>
- </para>
- </entry>
- </row>
-
- </tbody>
- </tgroup>
- </table>
- </sect2>
-
- </sect1>
-
- <sect1>
- <title>SSL Security</title>
-
- <para>
- Seam includes basic support for serving sensitive pages via the HTTPS protocol.
This is easily
- configured by specifying a <literal>scheme</literal> for the page in
<literal>pages.xml</literal>.
- The following example shows how the view
<literal>/login.xhtml</literal> is configured to use
- HTTPS:
- </para>
-
- <programlisting role="XML"><![CDATA[<page
view-id="/login.xhtml"
scheme="https"/>]]></programlisting>
-
- <para>
- This configuration is automatically extended to both
<literal>s:link</literal> and
- <literal>s:button</literal> JSF controls, which (when specifying the
<literal>view</literal>)
- will also render the link using the correct protocol. Based on the previous
example, the following
- link will use the HTTPS protocol because
<literal>/login.xhtml</literal> is configured to use it:
- </para>
-
- <programlisting role="XHTML"><![CDATA[<s:link
view="/login.xhtml" value="Login"/>]]></programlisting>
-
- <para>
- Browsing directly to a view when using the
<emphasis>incorrect</emphasis> protocol will cause a
- redirect to the same view using the <emphasis>correct</emphasis>
protocol. For example, browsing
- to a page that has <literal>scheme="https"</literal> using
HTTP will cause a redirect to the same
- page using HTTPS.
- </para>
-
- <para>
- It is also possible to configure a <emphasis>default scheme</emphasis>
for all pages. This is useful
- if you wish to use HTTPS for a only few pages. If no default scheme is specified
then the normal
- behavior is to continue use the current scheme. So once the user accessed a page
that required
- HTTPS, then HTTPS would continue to be used after the user navigated away to other
non-HTTPS pages.
- (While this is good for security, it is not so great for performance!). To define
HTTP as the
- default <literal>scheme</literal>, add this line to
<literal>pages.xml</literal>:
- </para>
-
- <programlisting role="XML"><![CDATA[<page view-id="*"
scheme="http" />]]></programlisting>
-
- <para>
- Of course, if <emphasis>none</emphasis> of the pages in your
application use HTTPS then it is not
- required to specify a default scheme.
- </para>
-
- <para>
- You may configure Seam to automatically invalidate the current HTTP session each
time the scheme
- changes. Just add this line to <literal>components.xml</literal>:
- </para>
-
- <programlisting role="XML"><![CDATA[<web:session
invalidate-on-scheme-change="true"/>]]></programlisting>
-
- <para>
- This option helps make your system less vulnerable to sniffing of the session id or
leakage of
- sensitive data from pages using HTTPS to other pages using HTTP.
- </para>
-
- <sect2>
- <title>Overriding the default ports</title>
-
- <para>
- If you wish to configure the HTTP and HTTPS ports manually, they may be
configured in
- <literal>pages.xml</literal> by specifying the
<literal>http-port</literal> and
- <literal>https-port</literal> attributes on the
<literal>pages</literal> element:
- </para>
-
- <programlisting role="XML"><![CDATA[
-<pages
xmlns="http://jboss.com/products/seam/pages"
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-
xsi:schemaLocation="http://jboss.com/products/seam/pages
http://jboss.com/products/seam/pages-2.2.xsd"
- no-conversation-view-id="/home.xhtml"
- login-view-id="/login.xhtml"
- http-port="8080"
- https-port="8443">
- ]]></programlisting>
- </sect2>
-
- </sect1>
-
- <sect1>
- <title>CAPTCHA</title>
-
- <para>
- Though strictly not part of the security API, Seam provides a built-in CAPTCHA
(<emphasis>C</emphasis>ompletely
- <emphasis>A</emphasis>utomated <emphasis>P</emphasis>ublic
<emphasis>T</emphasis>uring test to tell
- <emphasis>C</emphasis>omputers and
<emphasis>H</emphasis>umans <emphasis>A</emphasis>part) algorithm
to
- prevent automated processes from interacting with your application.
- </para>
-
- <sect2>
- <title>Configuring the CAPTCHA Servlet</title>
- <para>
- To get up and running, it is necessary to configure the Seam Resource Servlet,
which will provide the Captcha
- challenge images to your pages. This requires the following entry in
<literal>web.xml</literal>:
- </para>
-
- <programlisting role="XML"><![CDATA[<servlet>
- <servlet-name>Seam Resource Servlet</servlet-name>
-
<servlet-class>org.jboss.seam.servlet.SeamResourceServlet</servlet-class>
-</servlet>
-
-<servlet-mapping>
- <servlet-name>Seam Resource Servlet</servlet-name>
- <url-pattern>/seam/resource/*</url-pattern>
-</servlet-mapping>]]></programlisting>
-
- </sect2>
-
- <sect2>
- <title>Adding a CAPTCHA to a form</title>
-
- <para>
- Adding a CAPTCHA challenge to a form is extremely easy. Here's an example:
- </para>
-
- <programlisting role="XHTML"><![CDATA[<h:graphicImage
value="/seam/resource/captcha"/>
-<h:inputText id="verifyCaptcha" value="#{captcha.response}"
required="true">
- <s:validate />
-</h:inputText>
-<h:message for="verifyCaptcha"/>]]></programlisting>
-
- <para>
- That's all there is to it. The <literal>graphicImage</literal>
control displays the CAPTCHA challenge,
- and the <literal>inputText</literal> receives the user's
response. The response is automatically
- validated against the CAPTCHA when the form is submitted.
- </para>
-
- </sect2>
-
- <sect2>
- <title>Customising the CAPTCHA algorithm</title>
-
- <para>
- You may customize the CAPTCHA algorithm by overriding the built-in component:
- </para>
-
- <programlisting
role="JAVA"><![CDATA[(a)Name("org.jboss.seam.captcha.captcha")
-@Scope(SESSION)
-public class HitchhikersCaptcha extends Captcha
-{
- @Override @Create
- public void init()
- {
- setChallenge("What is the answer to life, the universe and
everything?");
- setCorrectResponse("42");
- }
-
- @Override
- public BufferedImage renderChallenge()
- {
- BufferedImage img = super.renderChallenge();
- img.getGraphics().drawOval(5, 3, 60, 14); //add an obscuring decoration
- return img;
- }
-}]]></programlisting>
-
- </sect2>
-
- </sect1>
-
- <sect1>
- <title>Security Events</title>
-
- <para>
- The following table describes a number of events (see <xref
linkend="events"/>) raised by Seam Security
- in response to certain security-related events.
- </para>
-
- <table>
- <title>Security Events</title>
-
- <tgroup cols="2">
- <colspec colnum="1" colwidth="3*" />
- <colspec colnum="2" colwidth="2*" />
-
- <thead>
- <row>
- <entry align="center">
- <para>Event Key</para>
- </entry>
- <entry align="center">
- <para>Description</para>
- </entry>
- </row>
- </thead>
-
- <tbody>
-
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.security.loginSuccessful</literal>
- </para>
- </entry>
- <entry>
- <para>
- Raised when a login attempt is successful.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.security.loginFailed</literal>
- </para>
- </entry>
- <entry>
- <para>
- Raised when a login attempt fails.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.security.alreadyLoggedIn</literal>
- </para>
- </entry>
- <entry>
- <para>
- Raised when a user that is already authenticated attempts to log in
again.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.security.notLoggedIn</literal>
- </para>
- </entry>
- <entry>
- <para>
- Raised when a security check fails when the user is not logged in.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.security.notAuthorized</literal>
- </para>
- </entry>
- <entry>
- <para>
- Raised when a security check fails when the user is logged in however
doesn't have sufficient privileges.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.security.preAuthenticate</literal>
- </para>
- </entry>
- <entry>
- <para>
- Raised just prior to user authentication.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.security.postAuthenticate</literal>
- </para>
- </entry>
- <entry>
- <para>
- Raised just after user authentication.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.security.loggedOut</literal>
- </para>
- </entry>
- <entry>
- <para>
- Raised after the user has logged out.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
-
<literal>org.jboss.seam.security.credentialsUpdated</literal>
- </para>
- </entry>
- <entry>
- <para>
- Raised when the user's credentials have been changed.
- </para>
- </entry>
- </row>
- <row>
- <entry>
- <para>
- <literal>org.jboss.seam.security.rememberMe</literal>
- </para>
- </entry>
- <entry>
- <para>
- Raised when the Identity's rememberMe property is changed.
- </para>
- </entry>
- </row>
-
- </tbody>
- </tgroup>
- </table>
-
- </sect1>
-
- <sect1>
- <title>Run As</title>
-
- <para>
- Sometimes it may be necessary to perform certain operations with elevated
privileges, such
- as creating a new user account as an unauthenticated user. Seam Security
supports such a
- mechanism via the <literal>RunAsOperation</literal> class. This
class allows either the
- <literal>Principal</literal> or
<literal>Subject</literal>, or the user's roles to be
- overridden for a single set of operations.
- </para>
-
- <para>
- The following code example demonstrates how
<literal>RunAsOperation</literal> is used, by
- calling its <literal>addRole()</literal> method to provide a set of
roles to masquerade
- as for the duration of the operation. The <literal>execute()</literal>
method contains the
- code that will be executed with the elevated privileges.
- </para>
-
- <programlisting role="JAVA"><![CDATA[ new RunAsOperation() {
- public void execute() {
- executePrivilegedOperation();
- }
- }.addRole("admin")
- .run();]]></programlisting>
-
- <para>
- In a similar way, the <literal>getPrincipal()</literal> or
<literal>getSubject()</literal>
- methods can also be overriden to specify the
<literal>Principal</literal> and
- <literal>Subject</literal> instances to use for the duration of the
operation.
- Finally, the <literal>run()</literal> method is used to carry out the
- <literal>RunAsOperation</literal>.
- </para>
-
- </sect1>
-
- <sect1>
- <title>Extending the Identity component</title>
-
- <para>
- Sometimes it might be necessary to extend the Identity component if your
application has
- special security requirements. The following example (contrived, as credentials
would normally
- be handled by the <literal>Credentials</literal> component instead)
shows an extended Identity
- component with an additional <literal>companyCode</literal> field. The
install precendence of <literal>APPLICATION</literal>
- ensures that this extended Identity gets installed in preference to the built-in
Identity.
- </para>
-
- <programlisting
role="JAVA"><![CDATA[(a)Name("org.jboss.seam.security.identity")
-@Scope(SESSION)
-@Install(precedence = APPLICATION)
-@BypassInterceptors
-@Startup
-public class CustomIdentity extends Identity
-{
- private static final LogProvider log = Logging.getLogProvider(CustomIdentity.class);
-
- private String companyCode;
-
- public String getCompanyCode()
- {
- return companyCode;
- }
-
- public void setCompanyCode(String companyCode)
- {
- this.companyCode = companyCode;
- }
-
- @Override
- public String login()
- {
- log.info("###### CUSTOM LOGIN CALLED ######");
- return super.login();
- }
-}]]></programlisting>
-
- <warning>
- <para>
- Note that an <literal>Identity</literal> component must be marked
<literal>@Startup</literal>, so that it is
- available immediately after the <literal>SESSION</literal> context
begins. Failing to do this may render
- certain Seam functionality inoperable in your application.
- </para>
- </warning>
-
-
- </sect1>
-
-
- <sect1>
- <title>OpenID</title>
-
- <para>
- OpenID is a community standard for external web-based authentication. The
basic
- idea is that any web application can supplement (or replace) its local
handling of
- authentication by delegating responsibility to an external OpenID server of
the user's
- chosing. This benefits the user, who no longer has to remember a name and
password for
- every web application he uses, and the developer, who is relieved of some of
the burden of
- maintaining a complex authentication system.
- </para>
-
- <para>When using OpenID, the user selects an OpenID provider, and the
provider assigns the user
- an OpenID. The id will take the form of a URL, for example
<literal>http://maximoburrito.myopenid.com</literal> however,
- it's acceptable to leave off the <literal>http://</literal>
part of the identifier when logging into a site. The web application
- (known as a relying party in OpenID-speak) determines which OpenID server to
contact and redirects the user to the remote
- site for authentication. Upon successful authentication the user is given
- the (cryptographically secure) token proving his identity and is redirected
back to the original web application.The
- local web application can then be sure the user accessing the application
controls the OpenID he presented.
- </para>
-
- <para>
- It's important to realize at this
- point that authentication does not imply authorization. The web application
still needs to make a determination of how to
- use that information. The web application could treat the user as instantly
logged in and give full access to the system or
- it could try and map the presented OpenID to a local user account, prompting
the user to register if he hasn't already.
- The choice of how to handle the OpenID is left as a design decision for the
local application.
- </para>
-
-
- <sect2>
- <title>Configuring OpenID</title>
- <para>
- Seam uses the openid4java package and requires four additional JARs to
make use of the Seam integration. These
- are: <literal>htmlparser.jar</literal>,
<literal>openid4java.jar</literal>,
<literal>openxri-client.jar</literal>
- and <literal>openxri-syntax.jar</literal>.
- </para>
-
- <para>
- OpenID processing requires the use of the
<literal>OpenIdPhaseListener</literal>, which should be added to your
- <literal>faces-config.xml</literal> file. The phase listener
processes the callback from the OpenID provider, allowing
- re-entry into the local application.
- </para>
-
- <programlisting role="XML"><lifecycle>
-
<phase-listener>org.jboss.seam.security.openid.OpenIdPhaseListener</phase-listener>
-</lifecycle></programlisting>
-
-
- <para>
- With this configuration, OpenID support is available to your
application.
- The OpenID support component,
<literal>org.jboss.seam.security.openid.openid</literal>, is installed
automatically if the openid4java
- classes are on the classpath.
- </para>
- </sect2>
-
- <sect2>
- <title>Presenting an OpenIdDLogin form</title>
-
- <para>
- To initiate an OpenID login, you can present a simply form to the user
asking for the user's OpenID. The <literal>#{openid.id}</literal>
- value
- accepts the user's OpenID and the
<literal>#{openid.login}</literal> action initiates an authentication
request.
- </para>
- <programlisting role="XML"><h:form>
- <h:inputText value="#{openid.id}" />
- <h:commandButton action="#{openid.login}"
value="OpenID Login"/>
-</h:form></programlisting>
-
- <para>
- When the user submits the login form, he will be redirected to his OpenID
provider. The user will eventually
- return to your application through the Seam pseudo-view
<literal>/openid.xhtml</literal>, which is
- provided by the <literal>OpenIdPhaseListener</literal>. Your
application can handle the OpenID response by means
- of a <literal>pages.xml</literal> navigation from that view,
just as if the user had never left your application.
- </para>
- </sect2>
-
- <sect2>
- <title>Logging in immediately</title>
-
- <para> The simplest strategy is to simply login the user immediately. The
following navigation rule shows how to handle this using
- the <literal>#{openid.loginImmediately()}</literal> action.
- </para>
-
- <programlisting role="XML"><page
view-id="/openid.xhtml">
- <navigation evaluate="#{openid.loginImmediately()}">
- <rule if-outcome="true">
- <redirect view-id="/main.xhtml">
- <message>OpenID login
successful...</message>
- </redirect>
- </rule>
- <rule if-outcome="false">
- <redirect view-id="/main.xhtml">
- <message>OpenID login rejected...</message>
- </redirect>
- </rule>
- </navigation>
-</page></programlisting>
-
- <para>Thie <literal>loginImmediately()</literal> action checks
to see if the OpenID is valid. If it is valid, it adds an
- OpenIDPrincipal to the identity component, marks the user as logged in (i.e.
<literal>#{identity.loggedIn}</literal> will be true)
- and returns true. If the OpenID was not validated, the method returns false,
and the user re-enters the application un-authenticated.
- If the user's OpenID is valid, it will be accessible using the expression
<literal>#{openid.validatedId}</literal> and
- <literal>#{openid.valid}</literal> will be true.
- </para>
-
-
- </sect2>
-
- <sect2>
- <title>Deferring login</title>
-
- <para>
- You may not want the user to be immediately logged in to your
application. In that case, your navigation
- should check the <literal>#{openid.valid}</literal> property
and redirect the user to a local registration or processing
- page. Actions you might take would be asking for more information and
creating a local user account or presenting a captcha
- to avoid programmatic registrations. When you are done processing, if
you want to log the user in, you can call
- the <literal>loginImmediately</literal> method, either
through EL as shown previously or by directly interaction with the
- <literal>org.jboss.seam.security.openid.OpenId</literal>
component. Of course, nothing prevents you from writing custom
- code to interact with the Seam identity component on your own for even
more customized behaviour.
- </para>
-
- </sect2>
-
-
- <sect2>
- <title>Logging out</title>
-
- <para>
- Logging out (forgetting an OpenID association) is done by calling
<literal>#{openid.logout}</literal>. If you
- are not using Seam security, you can call this method directly. If you
are using Seam security, you should
- continue to use <literal>#{identity.logout}</literal> and
install an event handler to capture the logout event, calling
- the OpenID logout method.
-
- </para>
- <programlisting role="XML"><event
type="org.jboss.seam.security.loggedOut">
- <action execute="#{openid.logout}" />
-</event> </programlisting>
-
- <para>It's important that you do not leave this out or the user
will not be able to login again in the same session.</para>
- </sect2>
-
-
-
-
- </sect1>
-
-
</chapter>
Modified: modules/security/trunk/examples/idmconsole/pom.xml
===================================================================
--- modules/security/trunk/examples/idmconsole/pom.xml 2010-07-23 08:37:34 UTC (rev
13483)
+++ modules/security/trunk/examples/idmconsole/pom.xml 2010-07-23 09:17:40 UTC (rev
13484)
@@ -3,9 +3,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.jboss.seam.security</groupId>
- <artifactId>idmconsole</artifactId>
+ <artifactId>seam-security-idmconsole-example</artifactId>
<packaging>war</packaging>
- <name>idmconsole</name>
+ <name>Seam Security Identity Management Console Example</name>
<version>3.0.0-SNAPSHOT</version>
<parent>
Modified: modules/security/trunk/pom.xml
===================================================================
--- modules/security/trunk/pom.xml 2010-07-23 08:37:34 UTC (rev 13483)
+++ modules/security/trunk/pom.xml 2010-07-23 09:17:40 UTC (rev 13484)
@@ -81,7 +81,8 @@
<modules>
<module>dist</module>
<module>docs</module>
- <module>examples/seamspace</module>
+ <!--module>examples/seamspace</module-->
+ <module>examples/idmconsole</module>
</modules>
</profile>
</profiles>