Security for websites and web applications

September 12th, 2008

Note: this article covers Seam 2.0 security. Seam 2.1 has major changes in its security system. We will be covering that in a later article.

Securing a site is easy, right? In PHP, for example, you create a function like this:

function isLoggedIn() { return isset($_SESSION['user']); }

Then, at the beginning of your pages, you put in a snip like this:

if(! isLoggedIn()) {
   header("Location:/login.php"); return;
}

We have various options in the JSP world. A common way is using an authentication filter like this:

public final class AuthFilter extends Filter {
    public void doFilter(ServletRequest request, ServletResponse response,  
        FilterChain chain) throws IOException, ServletException {  
            final HttpSession session = httpRequest.getSession(false); 
            if(session.getAttribute("user") == null) {
                filterConfig.getServletContext().getRequestDispatcher("/login.jsp").forward(request, response);  
                return;
            }
            // ok, the user is logged in
            // so chain
            chain.doFilter(request,response); 
        }
    }
}

Then you map that filter onto the resources that need to be protected using web.xml. That protects access to pages which should require a login, and that's all there is to it, right?

Note that within JSP there are many more options. For example, it's possible to put a security contraint on a collection of web resources, making the filter unnecessary. Within PHP, the various frameworks have security options. I'm showing the simple old standbys here, as points of comparison; modern PHP and JSP applications use techniques which are conceptually similar, but more modern.

Not all users are created equal

Restricting pages to logged-in users is the first step. The next step is differentiating groups of users based on access.

Usually this need arises to differentiate admin users and non-admin users. An admin property is added to the User object. Now, in a Servlet or a PHP file, we have some addtional checks:

final User user = (User) session.getAttribute("user");
if(! user.isAdmin()) { // do something to block this user from performing the action...
}

Of course, users aren't just admin and non-admin. There are customers, staff, supervisors, etc. We can add roles, with another method on the User class:

public String hasRole(String r) { ... } 

And now the check is: user.hasRole("admin").

The problem is we are now starting to clutter up our business logic, our entities, and even our persistence code, with role checks. Our business logic becomes tightly intertwined with the role checking system and the session system. It doesn't look so dreadful when it's a few lines in this example, but when you start having hundreds or thousands of classes of business logic and entities, it becomes a mess. And then sometimes you need to add more roles. And the user interface needs to change what it displays depending on roles. For example, an admin will have more menu options available.

Note that the Java Servlet system has some capabilities for making this easy. Servlets can be marked with a security-role entry, enforcing that the user has a certain role. That's a good step, but a Servlet is a coarse-grained level of access control.

What started out as a simple user authentication check has become a complex tangle of roles all through the business classes, or else we have a mismatch of needing fine-grained role restrictions and having coarse-grained Servlet-level restrictions.

Scattering simple role checks all over business objects, entities, persistence, and UI code is bad enough. It gets much worse when we need checks that are more complex than a simple role check. For instance, in a social networking site, any user with the role user can view profiles, but only the user who owns the profile, or a user with admin role, can modify it. In this case, the capabilities of the user role vary depending on the entities it is trying to work on.

Rules could get even more complex. For example, a rule might say that someone with a teller role can approve transactions up to $1,000.00. Someone with a supervisor role could have a higher limit. Both can approve the same transactions, but we need to check the value of the transaction relative to the role. And what if the next day the thresholds change due to a new policy decision? If the permissions logic is scattered all over the application, it might be hard to do that.

JBoss Seam security provides a solution to all the problems listed above.

Seam security, simple usage: checking for roles

Seam security has two modes of use: simple and advanced. Please see the reference manual to get started on the simple security usage. The most essential use of the simple mode of Seam security is the use s:hasRole and @Restrict. For example, on a Seam component, we might put a @Restrict annotation:

@Restrict("#{s:hasRole('admin')}") public void deleteAccount() { ... } 

Of course it's Expression Language so we can use it in the UI as well:

<h:commandButton rendered="#{s:hasRole('admin')}" 
  value="Delete account" action="#{accountManager.deleteAccount}"/>

This is nice because we have security on two layers: the button doesn't even show up in the UI if the user doesn't have the right role, and the Seam component will throw an exception if it accessed without the proper role. If the UI designers make a mistake, security is not compromised, and it makes it easy to re-use the business components in new UIs.

This simple use does not require installing JBoss Rules or writing a rules file, so it's the quickest way to add role checking to your application.

But we still have the problem that the rules are hard-coded as compiled-in annotations, and also these are nothing more than role checks, without any other logic. What if we later break up the admin role into separate sub-roles, like accountAdmin and staffAdmin? We need to contact the developers who created these business objects, get them to go through their code and change annotations and recompile.

If the restrictions were all in an editable text file, separate from the compiled classes, we would be able to edit them in response to changing business needs Even better if the restrictions text file could use an expert systems type of language. Java's logic contructs are ill-suited for expressing complex security restrictions. Nested if statements quickly become unreadable, and nested boolean logic with a lot of parenthesis and and and or operators become very hard for humans to understand.

Seam lets us to this with the JBoss Rules engine integrated with Seam Security. There is some added complexity in setting it up, and learning the new rule file language is also another cost of entry, but the end result is an excellent way to specify fine-grained rule-based security for your application.

You should not use the Rules-based security if your needs are only to control logins or manage a small number of simple roles. If your needs go beyond that, go ahead and use Rules-based security. Here we're going to show getting started with it.

What we really want is to put annotations on methods or classes that say, "this needs to be secured", without defining the access rules. Then, all the access rules are coded in a separate text file. This is exactly what the JBoss security advanced usage lets us do.

A brief introduction to JBoss Rules

Before we can dive into using JBoss Rules with Seam security, we need to explain what JBoss Rules (formerly Drools) is, what's the difference between a rules engine and the familiar procedural programming paradigm, and what are the key concepts within rules. This is not a comprehensive guide to using JBoss Rules. It's just the essentials to understand the concepts and get started.

We're all familiar with procedural programming in Java. We use flow control statements to determine the behavior of a piece of code:

if(amount > 1000) {
    // do something related to this amount
}

That works fine for simple expressions, but as the logic becomes more complex, these expressions can become absurd:

if((user.isAdmin() || (user.isStaff() && transation.getAmount() < 1000) ||
        (transaction.getAccount().getUser() == user) {
    if(transaction.getAmount() > 1 && transaction.getAmount() < 500) {
        if(account.isRestricted()) {
            // do something
        } else {
            // do something else
        }
    } else {
        // do something else
    }
}
            

We've all seen, and we've probably all written, code like that at one time or another. Can you really look at that and know that there isn't a mistake somewhere? It's challenging to figure out if that code really enforces the permissions (business security rules) it is supposed to enforce. And then imagine if these rules are scattered throughout your business objects, entities, and UI. And non-technical users will need to change them regularly.

We need a rule language that avoids these nested-if / nested logic constructions. JBoss Rules is that language.

A rules engine (such as JBoss Rules) replaces these nested-if constructions with a series of rules called production rules or simply productions. These rules take the following form:

when
    <conditions>
then
    <actions>

These production rules are listed in a particular syntax in the .drl rules file. The rules engine starts with a working memory which contains a collection of facts and applies the rules to the facts.

JBoss Rules is a forward-chaining engine, meaning that it starts with the facts in working memory and then triggers rules which fit these facts.

In pseudo-code, a rule set might look like this:

AdminAllowAllRule:
when
    user has an admin role
then
    grant permission;
    
UserAccessRule:
when
    user has user role
    blog entry belongs to the user
then
    grant permission;

Let's create a set of facts and apply them:

This matches the left-hand-side (the when clause) of the AdminAllowAllRule. The Rules engine forward-chains and matches the rule, and therefore executes the right-hand-side (the then clause). This does not match the UserAccessRule, so that RHS is not executed. It doesn't matter; the grant has already happened.

Let's change the facts in working memory:

Going through the matching, it fires the second rule but not the first. Permission is granted.

We remove one fact:

This does not fire either of the two rules in our rule base, so permission is not granted.

How would you write this in plain old nested-if style?

if((user.isAdmin() || (blogEntry.getOwner() == user) {
    permission.grant();
} else {
    // do not grant
}

It's not overwhelmingly complex, but you can see, if we added a few more rules to the rule base, rule base would not become any more complex, whereas the nested-if construction is already at the limit of what can be quickly understood by our thought processes.

In every case, we start with a collection of facts and apply them to the set of rules. The rules, obviously, come from the rules file. The facts are created by Seam and are placed into the working memory. It's important to be clear that the facts in working memory are not the same as the set of Seam components within some given scope.

Seam security lets us use such rule-based decision making to define security contraints.

Working memory

Rules are evaluated within a universe of facts called working memory. Think of this collection as a scope. Consider this simple Java class:

public final class Foo {
    int x;
    
    public void doSomething(int a) {
        String s;
        // This is Point A
        for(int i = 0; i < 10; i++) {
            String r = "We're at: " + i;
            // This is Point B
        }
    }
}

At Point A, the following are in scope:

At Point B, we have all the above, with two more:

Within a rules evaluation, facts correspond to variables in the Java, and the working memory corresponds to a scope in Java.

Expressing security constraints in JBoss Rules

There are three steps to using Seam security with JBoss Rules:

  1. Install the necessary JARs in the EAR and edit components.xml to use a security rules file
  2. Annotate methods and classes with @Restrict annotations
  3. Define the security rules in a JBoss Rules .drl file

Installing the JARs and setting up components.xml

As described in the documentation, add the following JAR files to to the EAR lib directory:

Edit the components.xml file to load Drools (the previous name for JBoss Rules). Make sure the xmlns:drools="http://jboss.com/products/seam/drools" namespace is used.

Add a section to link in the rules file. We'll explain each part in detail:

    <drools:rule-base name="securityRules">
       <drools:rule-files>
           <value>/META-INF/security-rules.drl</value>
       </drools:rule-files>
    </drools:rule-base>

The name="securityRules" attribute is required. The rule base must be linked to the name securityRules so Seam security will find it.

The rules file is set to /META-INF/security-rules.drl. A key question comes up: which META-INF is this? There's a META-INF in both the EJB JAR and the WAR files of a typical Seam app. In this case, it is within the EJB JAR. This makes sense. The rules exist to secure the EJBs, and also the Seam components and the web resources. That's also why we at Chiral Software put our components.xml files in the EJB JAR META-INF directory, instead of in the more typical WEB-INF location. Both components.xml and security-rules.drl exist to control components which outside of the WAR.

Now that Rules engine is installed and configured, we can start using it.

Add @Restrict annotations to trigger rule checks

The simplest way that a rule check is triggered is with a no-arguments @Restrict annotation on a single method. We'll be working on a very simple blogging application. It has an entity class, called Blog, and a POJO called BlogEditor which is used to manage these blog entries. We'll only show the parts of this class necessary to illustrate Seam security.

@Name("blogEditor") public class BlogEditor {

     @Restrict public void deleteEntry() {
          entityManager.remove(blogEntry);
     }

}

Seam creates the facts in working memory

What happens when Seam hits a method with a no-arguments @Restrict annotation? It creates certain facts in working memory and then evaluates the security rule base file. The rule base is the /META-INF/security-rules.drl file defined earlier. One fact is always present in working memory: a PermissionCheck. If the user is logged in, and if the user has been granted some roles, there is also one Role fact for each role that has been assigned to the user. These are the methods of PermissionCheck:

PermissionCheck Method Summary
String getAction()
String getName()
void grant()
boolean isGranted()
void revoke()

We're mainly interested in the name and action properties and the grant() action. In our example above, the line:

@Restrict public void deleteEntry()

causes a PermissionCheck fact with the following properties to be created and asserted in working memory:

PropertyValueNote
actiondeleteEntrythe method name
nameblogEditorSeam component name

A set (possibly empty) of Role objects is also added to working memory.

Role Method Summary
String getName()

If there is no logged in user, or if no roles have been assigned to the user, there will be no Role objects. If identity.addRole(string); has been used during login (in the authentication method) then those roles will be present as Role facts.

In this case, let's say the user has been granted admin and staff roles. Now, this table shows the complete set of facts in working memory when the @Restrict public void deleteEntry() method is used:

FactProperty nameProperty value
PermissionCheck action deleteEntry
name blogEditor
Role name admin
Role name staff

Writing rules: declarations

Rules files start with a package declaration:

package ChiralSoftwarePermissions;

The package name is arbitrary and doesn't refer to anything external to the rules file.

At a minimum, Seam security provides the following facts:

Before these can be accessed, the classes must be imported into the rules file:

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

There may be other facts also in working memory. We'll show later the ways that they can be introduced. If there are other facts, also import them:

import example.BlogEntry;

Basic structure of a rule

A simple rule has three elements:

rule rule-name
when
    left-hand-side (conditions)
then
    right-hand-side (actions)
end;

The rule name is arbitrary, and exists only for the convience of the person editing the file.

The left-hand side provides a list of conditions which must be met for the rule to fire. There may be zero or more conditions. If there is more than one condition, the conditions are conjunctive, meaning there is an implicit logical and between the conditions.

The right-hand side specifies the actions which result when the rule is fired.

The when clause: the conditions

Let's write a typical left-hand side:

when
   # This is a comment
   $c: PermissionCheck(name == "blogEditor")
   Role(name == "admin")

Breaking it down: when is the keyword which introduces the left-hand side. Comments may begin with the # character or Java-style with //, or may be multi-line /* */ style.

The next line is actually two tests, and one variable assignment. Start with the test:

PermissionCheck(name == "blogEditor")

This checks for a fact with the PermissionCheck class, with the name property being equal to blogEditor. This construction corresponds deeply with an SQL or EJB QL SELECT statement. To see how, realize that the table in the FROM clause of a SELECT statement corresponds to the class name of a Java class. In other words, the BlogEntry class corresponds to the BLOGENTRY SQL table, with fields in BlogEntry corresponding to columns in BLOGENTRY. You can view:

PermissionCheck(name == "blogEditor")

as if it were the EJB QL:

SELECT permissionCheck from PermissionCheck permissionCheck 
   where permissionCheck.name = 'blogEditor';

or the following standard SQL:

SELECT * FROM PERMISSIONCHECK
   WHERE name = 'blogEditor';
Corresponding concepts
SQL table relation row column WHERE clause
Java class reachable objects (instances) object (class instance) bean property n/a
Rules class pattern working memory fact property value when clause

Just like in an SQL SELECT clause, the Rules condition may match more than one fact in working memory. When the SELECT statement finds multiple emits a set of matched objects (rows). When there are multiple facts in working memory which trigger a condition, the rule fires once for each match. It would seem that once a permission has been granted, it would no longer be necessary to keep evaluating rules, but in fact, evaluation continues, because the then clause may have side-effects other than just granting permission. Remember, JBoss Rules is primarily a system for creating expert systems. Seam security is just one particular use for the Rules system. If you would like to have the clause evaluate if any one fact matches, see the documentation for the exists keyword.

We continue analyzing the condition:

$c: PermissionCheck(name == "blogEditor")

This triggers for PermissionCheck fact with the given property, assigns that fact (object) to the variable c. The dollar sign is optional, but makes it easier to read in some cases. We need to assign the PermissionCheck to a variable so we can refer to it in the then clause.

The next line looks for a Role fact with a name property equal to admin. There is an implicit logical and between these two rules.

That covers the left-hand side.

The then clause: the actions

The right-hand side is simpler. It's just a script of actions which occur when the rule matches. The best way to think of this is it is like a function that is called on every row of the output of a SELECT statement.

For the purposes of Seam security, there's usually only one action we care about:

$c.grant();

where $c is the PermissionCheck object that was found and assigned to the variable in the when clause.

But we can put in many other things in the then clause. This clause is basically ordinary Java code, so we can write:

System.out.println("The PermissionCheck's properties are: " +
    "name=" + $c.getName() + ", action=" + $c.getAction());

This is very useful for looking into what a rule is doing. Of course it should be removed after testing.

This is a complete, simple rules file which checks for a role and grants permission:

package ChiralSoftwarePermissions;

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

when
   # This is a comment
   $c: PermissionCheck(name == "blogEditor")
   Role(name == "admin")
then
    System.out.println("The PermissionCheck's properties are: " +
        "name=" + $c.getName() + ", action=" + $c.getAction());
   $c.grant();
            

More facts

The basic facts of the PermissionCheck and zero or more Roles are enough to express many complex access rules. Also, see the Rules documention for more information about the operators available. You can use regular expressions, math operations, and many others. But we often need some more facts to make the decision. For example, if we're dealing with a payment transaction, we may want the PaymentTransaction entity so we can see the dollar value of the transaction. We need to see some other ways to make facts available within the working memory.

Seam security provides three more methods for introducing facts. Before these can be used, the relevant classes must be imported, just like Role and PermissionCheck were imported:

import example.BlogEntry;

Use the third argument of hasPermission

The first way to introduce additional facts is to use the third argument of hasPermission within the @Restrict annotation:

@Restrict("#{s:hasPermission('processTransaction','start',transaction)}

In this case, the @Restrict creates a PermissionCheck object with the following properties:

NameValue
nameprocessTransaction
actionstart

The third argument finds the transaction Seam component, and introduces it as a fact within working memory. It's that simple. Find it by using a class selector:

import example.Transaction;

....
when
       Transaction(value > 1000)

The only limitation here is you're only adding one fact. Often it would be nice to have several facts available.

Put the Restrict on an entity

When the @Restrict annotation is on an entity, the entity is available as a fact.

Introduce the fact manually

A fact can be manually inserted into working memory using this construction:

((RuleBasedIdentity) RuleBasedIdentity.instance()).
     getSecurityContext().
     insert(object);

It's handy to do this in the authentication method to insert the logged-in user into the working memory.

Conclusion

I hope after reading this article, you'll try out Rules-based security. I'm sure that once you do, the older methods of managing access will never feel adequate again. Rules-based security allows the creation of an expert system to make access decisions. Expert systems model the way we think about security rules more naturally than nested-if statements, and expert system production rules are easier to understand. Equally important, the rules are kept in a separate file, and can be edited by people who are not Java developers.

I also hope that this has given you an insight into the use of JBoss Rules to create expert systems. Security is a natural application of an expert system, but there are many other areas where Rules can be used to create decision-making systems that would be very challenging to hand-code in Java or other procedural languages. You may find that with JBoss Rules you can tackle decision-making problems that would have been unmanageable otherwise. We have shown the most basic usage of Rules here. It is a fully-featured language with every construct you could need to build powerful decision-making systems. In fact, it has been used by banks and other institutions to provide a flexible way to express their extremely complicated rule bases. If you have a project that requires migrating a complex business rule system which is embedded in a large legacy code base (perhaps in COBOL), JBoss Rules should be at the top of the list of options to consider.