Authorization is the process of giving someone permission to do or see something. It is used to determine what the authenticated user is allowed to do in the application and what data he is allowed to see.

Organizing Authorization

There are different ways of organizing authorization. Widely used approaches are role-based authorization or access control lists.

  • In role-based Authorization users are assigned to roles. Rights to do or get something are granted to these roles.
  • In the concept of "Access Control Lists" an access control list is assigned to every object that has to be accessed. An access control list is a list of access control entries. Every of this entries holds a reference to a user or a role it is valid for, as well as the access rights that the referenced user or role has on the object.

Access Control: Authorization in Action

Access Control is the process of allowing and forbidding access to resources based on the authorization of the current security context. There are different levels of access control.

  • Presentation left access control - Access control is implemented in the presentation tier of the application. Only data that the user is allowed to see is displayed and only buttons for actions that the user is allowed to do are displayed and enabled. Although it may be necessary to provide different views to users with different roles, handling access control only at the presentation layer can be a big problem, when it involves loading and transferring masses of data to the presentation layer, where it gets filtered out, because the current user is not allowed to see it.
  • Service level access control - Access control is implemented in the service tier. Often this is realized using method-based access control. For every method in the service tier it is defined which users and/or roles are allowed to call it. Although this kind of security is widely supported by current security solutions like JavaEE Security and Spring Security it is often not the correct location of handling access control because access control usually depends more on the data used than on the actions executed. This leads to code that checks the current user and/or its roles to decide which data access tier method to invoke or this kind of checks are done by the code invoking the service methods. However manual handling of authorization informations leads to spreading the security all over the code. This makes changes to security requirements hard to handle.
  • Class level access control - Using class level access control on the entity beans of an application leads to a more fine-graded access control mechanism. If you are able to define which entity types may be used by which user or role (and you can do this in EJB 3 with the @RolesAllowed annotation) you have a clean way of defining which user may see and/or change which data without manual handling of authorization information. However this level is not detailed enough in many cases.
  • Instance level access control - With instance level access control for every user and/or role you can define permissions on an entity basis. Access Control Lists are widely used for this approach, but JPA Security provides a more flexible way to accomplish this, like we see later.
  • Property level access control - A step further from instance level access control is property level access control, where you can define access restrictions on a property base. JPA Security currently only supports property level access control for properties of type @Embeddable by providing instance level access control for embeddables.

Access Control in JPA Security

In JPA Security the access to entities and embeddables is defined by access rules. There is one set of access rules per security unit. This set of access rules applies to every JPA query and every entity and embeddable you get out of an EntityManager of the persistence unit that corresponds to that security unit.

Example: Access Rule

The following rule restricts the read access to accounts, where the owner is equal to the current user. In other words: every user can read only its own accounts.

        GRANT READ ACCESS TO Account account WHERE account.owner = CURRENT_PRINCIPAL
      

In the previous example the CURRENT_PRINCIPAL is provided by the currently active security context. When the access rule is evaluated, the CURRENT_PRINCIPAL is received from the security context and the rule is evaluated against this principal

Access Rules in JPA Security

JPA Security allows the definition of rules that grant create, read, update and/or delete access. Currently there is no explicit way to deny access. Although this is not needed, since every entity or embeddable to which no access is granted may not be accessed. An exception from this behavior are classes for which no access rule exists at all. Objects of such class may be accessed without any restriction.

The syntax of access rules

The general syntax of an access rule of JPA Security looks like GRANT [CREATE] [READ] [UPDATE] [DELETE] ACCESS TO entity_name alias[where_clause], where entity_name must be an entity or embeddable of the persistence unit (according to the JPA Specification, defaults to the class name if not otherwise specified) and the alias is an alias for that entity or embeddable that may be used in the where clause.

The syntax of the where_clause is derived from the syntax of WHERE clauses of JPQL, the query language of JPA. Within the clause any alias may be used that is defined by your current security context. The build-in security contexts define two aliases, which are CURRENT_PRINCIPAL and CURRENT_ROLES. The CURRENT_PRINCIPAL alias will be evaluated to the currently authenticated principal during runtime and the CURRENT_ROLES alias will be expanded to a list of roles that the current principal belongs to. No input parameters may be used in the WHERE clause of access rules. If you need more aliases to be defined (i.e. CURRENT_TENANT), you will have to implement your own security context like described later.

Providing access rules

With JPA Security there are two predefined ways to provide access rules: via XML configuration or via Annotations. In a later chapter we will see how to implement your own way of providing access rules.

Access rules via XML

One predefined way to provide access rules in JPA Security is via a file called security.xml, which is located in the META-INF directory of your application. Below is an example of the structure of such file:

<security xmlns="http://jpasecurity.sf.net/xml/ns/security"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://jpasecurity.sf.net/xml/ns/security
                              http://jpasecurity.sf.net/xml/ns/security/security_1_0.xsd"
          version="1.0">

  <persistence-unit name="...">
    <access-rule>...</access-rule>
    ...
  </persistence-unit>
</security>
        

Access rules via Annotations

The other predefined way to provide access rules in JPA Security is via Annotations. You may annotate your entity classes with one of the following two annotations: javax.annotation.security.RolesAllowed and net.sf.jpasecurity.security.rules.Permit.

Note that the semantics of the @RolesAllowed annotation slightly differs between the EJB Specification and JPA Security: If you annotate a class with the @RolesAllowed annotation this means for EJB any access to any method of an instance of that class will cause a SecurityException, if the current user is not in one of the roles allowed. JPA Security goes a step further: The current user will not retrieve this object from database if he is not in one of the roles allowed. JPA Security does not support the @RolesAllowed annotation at method-level.

The @Permit annotation has two optional parameters:

  • With the parameter access you can specify which kind of access shall be granted by this rule. The value is an array of the AccessType enum. Possible values of an AccessType are CREATE, READ, UPDATe and DELETE.
  • With the parameter rule you can specify a rule to restict the access for entities of the annotated class. The syntax of this rule is derived from the where-clause of JPQL (see previous section), where the special keyword this serves as an alias for the annotated entity or embeddable that may be used in the rule.

Example:

@Permit(access = AccessType.READ, rule = "owner = CURRENT_PRINCIPAL")
public class Account {
    ...
}
        

Applying of access rules

Read-access rules are applied to every entity or embeddable that is accessed via a JPA-Security-enabled EntityManager or via object-navigation through objects obtained from such EntityManager. When the object is accessed via JPQL, the access rules are applied directly to the JPQL-query, allowing the filtering to take place within the database. When the object is accessed via object-navigation, JPA Security tries to avoid database-calls and evaluates the rules in memory. When in-memory-evaluation is not applicable and the entity-manager is still open, a query is performed to evaluate the query. In the next section you can read, in which cases in-memory-evaluation is applicable and where not. When in-memory-evaluation is not applicable and the entity-manager is already closed, a SecurityException will be thrown.

Update-access rules are applied on flush() or commit(). Again the default-behaviour is in-memory-evaluation then, falling back to a query like described above.

Create-access rules and delete-access rules are applied when the appropriate action is performed with the entity-manager (either direct or by cascading). In-memory-evaluation applies like described above.

For all cases JPA Security is clever enough to apply the appropriate access rules for sub- and superclasses, too.

In-Memory-Evaluation

Every access rule that does not contain any sub-select can be evaluated in memory. For queries that contain sub-selects it depends on the kind of the sub-select and the content of the (first-level) entity-manager-cache of JPA Security. Sub-selects where all aliases from within the sub-select can directly replaced with an alias from outside can be evaluated in memory. Thus the following access rule can be evaluated in memory:

          
GRAND ACCESS TO TestBean bean WHERE EXISTS (SELECT b FROM TestBean b WHERE b = bean AND b.accessControlList = CURRENT_PRINCIPAL)
          
        

Since the alias from within the sub-select cannot directly replaced by the alias from outside, the following query cannot be evaluated in memory:

          
GRAND ACCESS TO TestBean bean WHERE EXISTS (SELECT entry FROM AccessControlListEntry entry WHERE bean.accessControlList = entry.accessControlList AND entry IN (CURRENT_ROLES)
          
        

Although there can be no guarantee in general that the last query can be evaluated in memory, in-memory-evaluation can still be achieved by ensuring that the entities that are needed to evaluate the sub-select are contained in the (first-level) entity-manager-cache. For example the last rule can be evaluated in memory if there exists an AccessContolListEntry in the cache that meets the specified where-clause. Remember, that on persist the check will be done before the persist-operation is cascaded. So when you want to persist a TestBean that contains an accessControlList with entries that match the where-clause, you have to persist the entries before you persist the TestBean to ensure the entries are in the cache.