Chapter 13. Security

The Seam Security API is an optional Seam feature that provides authentication and authorization features for securing both domain and page resources within your Seam project.

13.1. Overview

Seam Security provides two different modes of operation:

  • simplified mode - this mode supports authentication services and simple role-based security checks.

  • advanced mode - this mode supports all the same features as the simplified mode, plus it offers rule-based security checks using JBoss Rules.

13.1.1. Which mode is right for my application?

That all depends on the requirements of your application. If you have minimal security requirements, for example if you only wish to restrict certain pages and actions to users who are logged in, or who belong to a certain role, then the simplified mode will probably be sufficient. The advantages of this is a more simplified configuration, significantly less libraries to include, and a smaller memory footprint.

If on the other hand, your application requires security checks based on contextual state or complex business rules, then you will require the features provided by the advanced mode.

13.2. Requirements

If using the advanced mode features of Seam Security, the following jar files are required to be configured as modules in application.xml. If you are using Seam Security in simplified mode, these are not required:

  • drools-compiler-4.0.0.MR2.jar

  • drools-core-4.0.0.MR2.jar

  • janino-2.5.7.jar

  • antlr-runtime-3.0.jar

  • mvel14-1.2beta16.jar

For web-based security, jboss-seam-ui.jar must also be included in the application's war file.

13.3. Disabling Security

In some situations it may be necessary to disable Seam Security, for example during unit tests. This can be done by calling the static method Identity.setSecurityEnabled(false) to disable security checks. Doing this prevents any security checks being performed for the following:

  • Entity Security

  • Hibernate Security Interceptor

  • Seam Security Interceptor

  • Page restrictions

13.4. Authentication

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.

13.4.1. Configuration

The simplified authentication method uses a built-in JAAS login module, SeamLoginModule, 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. Configuring this simplified form of authentication requires the identity component to be configured in components.xml:

<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.0.xsd
                 http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.0.xsd">                
        
    <security:identity authenticate-method="#{authenticator.authenticate}"/>
    
</components>

If you wish to use the advanced security features such as rule-based permission checks, all you need to do is include the Drools (JBoss Rules) jars in your classpath, and add some additional configuration, described later.

The EL expression #{authenticator.authenticate} is a method binding indicating that the authenticate method of the authenticator component will be used to authenticate the user.

13.4.2. Writing an authentication method

The authenticate-method property specified for identity in components.xml specifies which method will be used by SeamLoginModule to authenticate users. This method takes no parameters, and is expected to return a boolean indicating whether authentication is successful or not. The user's username and password can be obtained from Identity.instance().getUsername() and Identity.instance().getPassword(), respectively. Any roles that the user is a member of should be assigned using Identity.instance().addRole(). Here's a complete example of an authentication method inside a JavaBean component:

@Name("authenticator")
public class Authenticator {
   @In EntityManager entityManager;
   
   public boolean authenticate() {
      try
      {
         User user = (User) entityManager.createQuery(
            "from User where username = :username and password = :password")
            .setParameter("username", Identity.instance().getUsername())
            .setParameter("password", Identity.instance().getPassword())
            .getSingleResult();

         if (user.getRoles() != null)
         {
            for (UserRole mr : user.getRoles())
               Identity.instance().addRole(mr.getName());
         }

         return true;
      }
      catch (NoResultException ex)
      {
         return false;
      }
      
   }
   
}

In the above example, both User and UserRole are application-specific entity beans. The roles parameter is populated with the roles that the user is a member of, which should be added to the Set as literal string values, e.g. "admin", "user". In this case, if the user record is not found and a NoResultException thrown, the authentication method returns false to indicate the authentication failed.

13.4.2.1. Identity.addRole()

The Identity.addRole() method behaves differently depending on whether the current session is authenticated or not. If the session is not authenticated, then addRole() should only 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 Identity.hasRole() 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.

13.4.3. Writing a login form

The Identity component provides both username and password 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 the identity.login() method will authenticate the user using the provided credentials. Here's an example of a simple login form:

<div>
    <h:outputLabel for="name" value="Username"/>
    <h:inputText id="name" value="#{identity.username}"/>
</div>

<div>
    <h:outputLabel for="password" value="Password"/>
    <h:inputSecret id="password" value="#{identity.password}"/>
</div>

<div>
    <h:commandButton value="Login" action="#{identity.login}"/>
</div>

Similarly, logging out the user is done by calling #{identity.logout}. Calling this action will clear the security state of the currently authenticated user.

13.4.4. Simplified Configuration - Summary

So to sum up, there are the three easy steps to configure authentication:

  • Configure an authentication method in components.xml.

  • Write an authentication method.

  • Write a login form so that the user can authenticate.

13.4.5. Handling Security Exceptions

To prevent users from receiving the default error page in response to a security error, it's recommended that pages.xml is configured to redirect security errors to a more "pretty" page. The two main types of exceptions thrown by the security API are:

  • NotLoggedInException - This exception is thrown if the user attempts to access a restricted action or page when they are not logged in.

  • AuthorizationException - 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.

In the case of a NotLoggedInException, it is recommended that the user is redirected to either a login or registration page so that they can log in. For an AuthorizationException, it may be useful to redirect the user to an error page. Here's an example of a pages.xml file that redirects both of these security exceptions:

<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>

Most web applications require even more sophisticated handling of login redirection, so Seam includes some special functionality for handling this problem.

13.4.6. Login Redirection

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:

<pages login-view-id="/login.xhtml">

    <page view-id="/members/*" login-required="true"/>
    
    ...
    
</pages>

(This is less of a blunt instrument than the exception handler shown above, but should probably be used in conjunction with it.)

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 components.xml, 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.

<event type="org.jboss.seam.notLoggedIn">
    <action execute="#{redirect.captureCurrentView}"/>
</event>
    
<event type="org.jboss.seam.postAuthenticate">
    <action execute="#{redirect.returnToCapturedView}"/>
</event>

Note that login redirection is implemented as a conversation-scoped mechanism, so don't end the conversation in your authenticate() method.

13.4.7. HTTP Authentication

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 authentication-filter component must be enabled in components.xml:

  <web:authentication-filter url-pattern="*.seam" auth-type="basic"/>
      

To enable the filter for basic authentication, set auth-type to basic, or for digest authentication, set it to digest. If using digest authentication, the key and realm must also be set:

  <web:authentication-filter url-pattern="*.seam" auth-type="digest" key="AA3JK34aSDlkj" realm="My App"/>
      

The key can be any String value. The realm is the name of the authentication realm that is presented to the user when they authenticate.

13.4.7.1. Writing a Digest Authenticator

If using digest authentication, your authenticator class should extend the abstract class org.jboss.seam.security.digest.DigestAuthenticator, and use the validatePassword() method to validate the user's plain text password against the digest request. Here is an example:

   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;
      }      
   }        
        

13.4.8. Advanced Authentication Features

This section explores some of the advanced features provided by the security API for addressing more complex security requirements.

13.4.8.1. Using your container's JAAS configuration

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 jaasConfigName property in components.xml. For example, if you are using JBoss AS and wish to use the other policy (which uses the UsersRolesLoginModule login module provided by JBoss AS), then the entry in components.xml would look like this:

<security:identity authenticate-method="#{authenticator.authenticate}" 
                      jaas-config-name="other"/>

13.5. Error Messages

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 message.properties resource file. To suppress the message, just put the key with an empty value in the resource file.

Table 13.1. Security Message Keys

org.jboss.seam.loginSuccessful

This message is produced when a user successfully logs in via the security API.

org.jboss.seam.loginFailed

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.

org.jboss.seam.NotLoggedIn

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.

13.6. Authorization

There are a number of authorization features 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 components.xml must be configured to support this - see the Configuration section above.

13.6.1. Core concepts

Each of the authorization mechanisms provided by the Seam Security API are built upon the concept of a user being granted roles and/or permissions. A role is a group, or type, of user that may have been granted certain privileges for performing one or more specific actions within an application. A permission on the other hand 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.

Roles are simple, consisting of only a name such as "admin", "user", "customer", etc. Permissions consist of both a name and an action, and are represented within this documentation in the form name:action, for example customer:delete, or customer:insert.

13.6.2. Securing components

Let's start by examining the simplest form of authorization, component security, starting with the @Restrict annotation.

13.6.2.1. The @Restrict annotation

Seam components may be secured either at the method or the class level, using the @Restrict annotation. If both a method and it's declaring class are annotated with @Restrict, 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 Identity.checkRestriction() (see Inline Restrictions). A @Restrict on just the component class itself is equivalent to adding @Restrict to each of its methods.

An empty @Restrict implies a permission check of componentName:methodName. Take for example the following component method:

@Name("account")
public class AccountAction {
    @Restrict public void delete() {
      ...
    }
}

In this example, the implied permission required to call the delete() method is account:delete. The equivalent of this would be to write @Restrict("#{s:hasPermission('account','delete',null)}"). Now let's look at another example:

@Restrict @Name("account")
public class AccountAction {
    public void insert() {
      ...
    }
    @Restrict("#{s:hasRole('admin')}") 
    public void delete() {
      ...
    }
}

This time, the component class itself is annotated with @Restrict. This means that any methods without an overriding @Restrict annotation require an implicit permission check. In the case of this example, the insert() method requires a permission of account:insert, while the delete() method requires that the user is a member of the admin role.

Before we go any further, let's address the #{s:hasRole()} expression seen in the above example. Both s:hasRole and s:hasPermission are EL functions, which delegate to the correspondingly named methods of the Identity class. These functions can be used within any EL expression throughout the entirety of the security API.

Being an EL expression, the value of the @Restrict 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:

@Name("account")
public class AccountAction {
    @In Account selectedAccount;
    @Restrict("#{s:hasPermission('account','modify',selectedAccount)}")
    public void modify() {
        selectedAccount.modify();
    }
}

The interesting thing to note from this example is the reference to selectedAccount seen within the hasPermission() function call. The value of this variable will be looked up from within the Seam context, and passed to the hasPermission() method in Identity, which in this case can then determine if the user has the required permission for modifying the specified Account object.

13.6.2.2. Inline restrictions

Sometimes it might be desirable to perform a security check in code, without using the @Restrict annotation. In this situation, simply use Identity.checkRestriction() to evaluate a security expression, like this:

public void deleteCustomer() {
    Identity.instance().checkRestriction("#{s:hasPermission('customer','delete',selectedCustomer)}");
}

If the expression specified doesn't evaluate to true, either

  • if the user is not logged in, a NotLoggedInException exception is thrown or

  • if the user is logged in, an AuthorizationException exception is thrown.

It is also possible to call the hasRole() and hasPermission() methods directly from Java code:

if (!Identity.instance().hasRole("admin"))
     throw new AuthorizationException("Must be admin to perform this action");

if (!Identity.instance().hasPermission("customer", "create", null))
     throw new AuthorizationException("You may not create new customers");

13.6.3. Security in the user interface

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.

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 identity.isLoggedIn() property, we can write this:

<h:form class="loginForm" rendered="#{not identity.loggedIn}">

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 manager role. Here's one way that these could be written:

<h:outputLink action="#{reports.listManagerReports}" rendered="#{s:hasRole('manager')}">
    Manager Reports
</h:outputLink>

This is also quite straight forward. If the user is not a member of the manager role, then the outputLink will not be rendered. The rendered attribute can generally be used on the control itself, or on a surrounding <s:div> or <s:span> control.

Now for something more complex. Let's say you have a h:dataTable 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 s:hasPermission 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:

<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('client','modify',cl)"/>
        <s:link value="Delete Client" action="#{clientAction.delete}"
                rendered="#{s:hasPermission('client','delete',cl)"/>
    </h:column>
</h:dataTable>

13.6.4. Securing pages

Page security requires that the application is using a pages.xml file, however is extremely simple to configure. Simply include a <restrict/> element within the page elements that you wish to secure. If no explicit restriction is specified by the restrict element, an implied permission of /viewId.xhtml:render will be checked when the page is accessed via a non-faces (GET) request, and a permission of /viewId.xhtml:restore 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:

<page view-id="/settings.xhtml">
    <restrict/>
</page>

This page has an implied permission of /settings.xhtml:render required for non-faces requests and an implied permission of /settings.xhtml:restore for faces requests.

<page view-id="/reports.xhtml">    
    <restrict>#{s:hasRole('admin')}</restrict>
</page>

Both faces and non-faces requests to this page require that the user is a member of the admin role.

13.6.5. Securing Entities

Seam security also makes it possible to apply security restrictions to read, insert, update and delete actions for entities.

To secure all actions for an entity class, add a @Restrict annotation on the class itself:

@Entity
@Name("customer")
@Restrict
public class Customer {
  ...
}

If no expression is specified in the @Restrict annotation, the default security check that is performed is a permission check of entityName:action, where entityName is the Seam component name of the entity (or the fully-qualified class name if no @Name is specified), and the action is either read, insert, update or delete.

It is also possible to only restrict certain actions, by placing a @Restrict annotation on the relevent entity lifecycle method (annotated as follows):

  • @PostLoad - Called after an entity instance is loaded from the database. Use this method to configure a read permission.

  • @PrePersist - Called before a new instance of the entity is inserted. Use this method to configure an insert permission.

  • @PreUpdate - Called before an entity is updated. Use this method to configure an update permission.

  • @PreRemove - Called before an entity is deleted. Use this method to configure a delete permission.

Here's an example of how an entity would be configured to perform a security check for any insert 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:

  @PrePersist @Restrict
  public void prePersist() {}      
   

And here's an example of an entity permission rule that checks if the authenticated user is allowed to insert a new MemberBlog 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 MemberBlog):

rule InsertMemberBlog
  no-loop
  activation-group "permissions"  
when
  check: PermissionCheck(name == "memberBlog", action == "insert", granted == false)
  Principal(principalName : name)
  MemberBlog(member : member -> (member.getUsername().equals(principalName)))
then
  check.grant();
end;

This rule will grant the permission memberBlog:insert if the currently authenticated user (indicated by the Principal fact) has the same name as the member for which the blog entry is being created. The "principalName : name" structure that can be seen in the Principal fact (and other places) is a variable binding - it binds the name property of the Principal to a variable called principalName. 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 Principal name. For more details, please refer to the JBoss Rules documentation.

Finally, we need to install a listener class that integrates Seam security with your JPA provider.

13.6.5.1. Entity security with JPA

Security checks for EJB3 entity beans are performed with an EntityListener. You can install this listener by using the following META-INF/orm.xml file:

<?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>

13.6.5.2. Entity security with Hibernate

If you are using a Hibernate SessionFactory configured via Seam, you don't need to do anything special to use entity security.

13.7. Writing Security Rules

Up to this point there has been a lot of mention of permissions, but no information about how permissions are actually defined or granted. This section completes the picture, by explaining how permission checks are processed, and how to implement permission checks for a Seam application.

13.7.1. Permissions Overview

So how does the security API know whether a user has the customer:modify permission for a specific customer? Seam Security provides quite a novel method for determining user permissions, based on JBoss Rules. A couple of the advantages of using a rule engine are 1) a centralized location for the business logic that is behind each user permission, and 2) speed - JBoss Rules uses very efficient algorithms for evaluating large numbers of complex rules involving multiple conditions.

13.7.2. Configuring a rules file

Seam Security expects to find a RuleBase component called securityRules which it uses to evaluate permission checks. This is configured in components.xml as follows:

<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.0.xsd 
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.0.xsd
                 http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.0.xsd"
                 http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.0.xsd">                 
        
   <drools:rule-base name="securityRules">
       <drools:rule-files>
           <value>/META-INF/security.drl</value>
       </drools:rule-files>
   </drools:rule-base>    
   
</components>

Once the RuleBase component is configured, it's time to write the security rules.

13.7.3. Creating a security rules file

For this step you need to create a file called security.drl in the /META-INF directory of your application's jar file. In actual fact this file can be called anything you want, and exist in any location as long as it is configured appropriately in components.xml.

So what should the security rules file contain? At this stage it might be a good idea to at least skim through the JBoss Rules documentation, however to get started here's an extremely simple example:

package MyApplicationPermissions;

import org.jboss.seam.security.PermissionCheck;
import org.jboss.seam.security.Role;

rule CanUserDeleteCustomers
when
  c: PermissionCheck(name == "customer", action == "delete")
  Role(name == "admin")
then
  c.grant();
end;

Let's break this down. The first thing we see is the package declaration. A package in JBoss Rules 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.

The next thing we can notice is a couple of import statements for the PermissionCheck and Role classes. These imports inform the rules engine that we'll be referencing these classes within our rules.

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 CanUserDeleteCustomers and will be used to check whether a user is allowed to delete a customer record.

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 when 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 then section. The end of the rule is denoted by the end; line.

If we look at the LHS of the rule, we see two conditions listed there. Let's examine the first condition:

c: PermissionCheck(name == "customer", action == "delete")

In plain english, this condition is stating that there must exist a PermissionCheck object with a name property equal to "customer", and an action property equal to "delete" within the working memory. What is the working memory? It 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 hasPermission() method is called, a temporary PermissionCheck object, or Fact, is inserted into the working memory. This PermissionCheck corresponds exactly to the permission that is being checked, so for example if you call hasPermission("account", "create", null) then a PermissionCheck object with a name equal to "account" and action equal to "create" will be inserted into the working memory for the duration of the permission check.

So what else is in the working memory? Besides the short-lived temporary facts inserted during a permission check, there are some longer-lived objects in the working memory that stay there for the entire duration of a user being authenticated. These include any java.security.Principal objects that are created as part of the authentication process, plus a org.jboss.seam.security.Role object for each of the roles that the user is a member of. It is also possible to insert additional long-lived facts into the working memory by calling ((RuleBasedIdentity) RuleBasedIdentity.instance()).getSecurityContext().insert(), passing the object as a parameter.

Getting back to our simple example, we can also notice that the first line of our LHS is prefixed with c:. This is a variable binding, and is used to refer back to the object that is matched by the condition. Moving onto the second line of our LHS, we see this:

Role(name == "admin")

This condition simply states that there must be a Role object with a name of "admin" within the working memory. As mentioned, user roles are inserted into the working memory as long-lived facts. So, putting both conditions together, this rule is essentially saying "I will fire if you are checking for the customer:delete permission and the user is a member of the admin role".

So what is the consequence of the rule firing? Let's take a look at the RHS of the rule:

c.grant()

The RHS consists of Java code, and in this case is invoking the grant() method of the c object, which as already mentioned is a variable binding for the PermissionCheck object. Besides the name and action properties of the PermissionCheck object, there is also a granted property which is initially set to false. Calling grant() on a PermissionCheck sets the granted property to true, which means that the permission check was successful, allowing the user to carry out whatever action the permission check was intended for.

13.7.3.1. Wildcard permission checks

It is possible to implement a wildcard permission check (which allows all actions for a given permission name), by omitting the action constraint for the PermissionCheck in your rule, like this:

rule CanDoAnythingToCustomersIfYouAreAnAdmin
when
  c: PermissionCheck(name == "customer")
  Role(name == "admin")
then
  c.grant();
end;        
        

This rule allows users with the admin role to perform any action for any customer permission check.

13.8. SSL Security

Seam includes basic support for serving sensitive pages via the HTTPS protocol. This is easily configured by specifying a scheme for the page in pages.xml. The following example shows how the view /login.xhtml is configured to use HTTPS:

<page view-id="/login.xhtml" scheme="https">

This configuration is automatically extended to both s:link and s:button JSF controls, which (when specifying the view) will also render the link using the correct protocol. Based on the previous example, the following link will use the HTTPS protocol because /login.xhtml is configured to use it:

<s:link view="/login.xhtml" value="Login"/>

Browsing directly to a view when using the incorrect protocol will cause a redirect to the same view using the correct protocol. For example, browsing to a page that has scheme="https" using HTTP will cause a redirect to the same page using HTTPS.

It is also possible to configure a default scheme 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 scheme, add this line to pages.xml:

<page view-id="*" scheme="http" />

Of course, if none of the pages in your application use HTTPS then it is not required to specify a default scheme.

You may configure Seam to automatically invalidate the current HTTP session each time the scheme changes. Just add this line to components.xml:

<core:servlet-session invalidate-on-scheme-change="true"/>

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.

13.9. Implementing a Captcha Test

Though strictly not part of the security API, it might be useful in certain circumstances (such as new user registrations, posting to a public blog or forum) to implement a Captcha (Completely Automated Public Turing test to tell Computers and Humans Apart) to prevent automated bots from interacting with your application. Seam provides seamless integration with JCaptcha, an excellent library for generating Captcha challenges. If you wish to use the captcha feature in your application you need to include the jcaptcha-* jar file from the Seam lib directory in your project, and register it in application.xml as a java module.

13.9.1. Configuring the Captcha Servlet

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 web.xml:

<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>

13.9.2. Adding a Captcha to a page

Adding a captcha challenge to a page is extremely easy. Seam provides a page-scoped component, captcha, which provides everything that is required, including built-in captcha validation. Here's an example:

<div>
    <h:graphicImage value="/seam/resource/captcha?#{captcha.id}"/>
</div>
  
<div>
    <h:outputLabel for="verifyCaptcha">Enter the above letters</h:outputLabel>
    <h:inputText id="verifyCaptcha" value="#{captcha.response}" required="true">
      <s:validate />
    </h:inputText>
    <div class="validationError"><h:message for="verifyCaptcha"/></div>
</div>

<div>
    <h:commandButton action="#{register.next}" value="Register"/>
</div>

That's all there is to it. The graphicImage control displays the Captcha challenge, and the inputText receives the user's response. The response is automatically validated against the Captcha when the form is submitted.

13.9.3. Customising the Captcha image

The captcha image itself may be customised by providing an alternative ImageCaptchaService to the default (DefaultManageableImageCaptchaService). To configure a different ImageCaptchaService, add the following entry to components.xml:

  <component name="org.jboss.seam.captcha.captchaImage" service="#{customCaptcha.service}"/>

The service attribute specifies the ImageCaptchaService instance to use to generate captcha images. For more information on configuring an ImageCaptchaService please refer to the JCaptcha documentation. Here is an example of a custom image generator (which can be found in the seamspace example):

@Name("customCaptcha")
public class CustomCaptcha
{
   public ImageCaptchaService getService()
   {
      BasicGimpyEngine customCaptcha = new BasicGimpyEngine();
      GimpyFactory factory = new GimpyFactory(
            new RandomWordGenerator("ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789"), 
            new ComposedWordToImage(new RandomFontGenerator(new Integer(15), 
                  new Integer(15)), new UniColorBackgroundGenerator(new Integer(150), 
                        new Integer(30)), new RandomTextPaster(new Integer(4), 
                              new Integer(7), Color.BLACK)));
      GimpyFactory[] factories = {factory};
      customCaptcha.setFactories(factories);
      
      return new DefaultManageableImageCaptchaService(
                   new FastHashMapCaptchaStore(),
                   customCaptcha,
                   180,
                   120000,
                   75000);      
   }
}