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.
There are different ways of organizing authorization. Widely used approaches are role-based authorization or access control lists.
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.
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.
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
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 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.
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.
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>
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:
Example:
@Permit(access = AccessType.READ, rule = "owner = CURRENT_PRINCIPAL") public class Account { ... }
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.
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.