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:
- Fact: user has admin role
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:
- Fact: user has
userrole - Fact: there is a blog entry
- Fact: the blog entry's owner is the same as the user
Going through the matching, it fires the second rule but not the first. Permission is granted.
We remove one fact:
- Fact: user has
userrole - Fact: there is a blog entry
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:
x, a instance membera, a variable local to the method which is introduced as a method arguments, a variable local to the method
At Point B, we have all the above, with two more:
i, the variable created in theforloopr, a variable within theforloop's statement
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:
- Install the necessary JARs in the EAR and edit
components.xmlto use a security rules file - Annotate methods and classes with
@Restrictannotations - Define the security rules in a JBoss Rules
.drlfile
Installing the JARs and setting up components.xml
As described in the documentation, add the following JAR files to to the EAR lib directory:
- drools-compiler.jar
- drools-core.jar
- janino.jar
- antlr-runtime.jar
- mvel14.jar
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:
| Property | Value | Note |
|---|---|---|
action | deleteEntry | the method name |
name | blogEditor | Seam 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:
| Fact | Property name | Property 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:
- The
PermissionCheckobject - Zero or more
Roleobjects
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:
| Name | Value |
|---|---|
name | processTransaction |
action | start |
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.
For JBoss Seam consulting or training, call us at 310 356 7869 to discuss your needs.