Implementing container authorization in Java EE with JACC
JACC, which stands for Java Authorization Contract for Containers and for some reason also for Java Authorization Service Provider Contract for Containers is a specification that according to the official Java EE documentation "defines a contract between a Java EE application server and an authorization policy provider" and which "defines java.security.Permission classes that satisfy the Java EE authorization model.".
Public opinion
While JASPIC had only been added to Java EE as late as in Java EE 6, JACC has been part of Java EE since the dark old days of J2EE 1.4. Developers should thus have had plenty of time to get accustomed to JACC, but unfortunately this doesn't quite seem to be the case. While preparing for this article I talked to a few rather advanced Java EE developers (as in, those who literally wrote the book, worked or have worked on implementing various aspects of Java EE, etc). A few of their responses:
- "I really have no idea whatsoever what JACC is supposed to do"
- (After telling I'm working on a JACC article) "Wow! You really have guts!"
- "[the situation surrounding JACC] really made me cry"
- " [...] the PolicyConfiguration's (atrocious) contract [...] "
- "More ammunition for my case that this particular Java EE contract is not worth the paper it's printed on."
What does JACC do?
The negativity aside, the prevailing emotion seems to be that it's just not clear what JACC brings to the table. While JASPIC is not widely used either people do tend to easily get what JASPIC primarily does; provide a standardized API for authentication modules, where those authentication modules are things that check credentials against LDAP servers, a database, a local file, etc and where those credentials are asked via mechanisms like an HTML form or HTTP BASIC etc.
But what does a "contract between an AS and an authorization policy provider" actually mean? In other words, what does JACC do?
In a very practical sense JACC offers the followings things:
- Contextual objects: A number of convenient objects bound to thread local storage that can be obtained from "anywhere" like the current http request and the current Subject.
- Authorization queries: A way for application code to ask the container authorization related things like: "What roles does the current user have?" and "Will this user have access to this URL?"
- A hook into the authorization process: A way to register your own class that will:
- receive all authorization constraints that have been put in web.xml, ejb-jar.xml and corresponding annotations
- be consulted when the container makes an authentication decision, e.g. to determine if a user/caller has access to a URL
Contextual objects
Just as the Servlet spec makes a number of (contextual) objects available as request parameters, and JSF does something like this via EL implicit objects, so does JACC makes a number of objects available.
While those objects seem to be primarily intended to be consumed by the special JACC policy providers (see below), they are in fact specified to work in the context of a "dispatched call" (the invocation of an actual Servlet + Filters or an EJB + Interceptors). As such they typically do work when called from user code inside e.g. a Servlet, but the spec could be a bit stricter here and specifically mandate this.
Contrary to Servlet's request/session/application attributes, you don't store instances of objects directly into the JACC policy context. Instead, just like can optionally be done for JNDI you store for each key a factory that knows how to produce an object corresponding to that key. E.g. in order to put a value "bar" in the context using the key "foo" you'd use the following code:
final String key = "foo"; PolicyContext.registerHandler(key, new PolicyContextHandler() { @Override public Object getContext(String key, Object data) throws PolicyContextException { return "bar"; } @Override public boolean supports(String key) throws PolicyContextException { return key.equals(key); } @Override public String[] getKeys() throws PolicyContextException { String[] keys = { key }; return keys; } }, true ); // result will be "bar" String result = PolicyContext.getContext("foo");For general usage it's bit unwieldy that the factory (called handler) must known its own key(s). Most containers first get the factory by looking it up in a table using the key it was registered with and then ask the factory if it indeed supports that key. Since a factory has to be registered individually for each key it supports anyway it's debatable whether the responsibility for ensuring the correct keys are used shouldn't be with the one doing the registration. That way the factory interface could have been made a bit simpler.
By default the following keys are defined:
- javax.security.auth.Subject.container - Returns the current Subject or null if user not authenticated.
- javax.servlet.http.HttpServletRequest - Returns the current request when requested from a Servlet.
- javax.xml.soap.SOAPMessage - Returns the SOAP message when requested from an EJB when JAX-WS is used.
- javax.ejb.EnterpriseBean - Returns the EJB 1.0 "EnterpriseBean" interface. It's best forgotten that this exists.
- javax.ejb.arguments - Returns the method arguments of the last call to an EJB that's on the stack.
- java.security.Policy.supportsReuse - Indicates that a container (server) supports caching of an authorization outcome.
Getting the Subject is especially crucial here, since it's pretty much the only way in Java EE to get a hold of this, and we need this for our authorization queries (see below).
For general usage the other objects have the most value for actual policy providers, as in e.g. JSF there are already ways to get the current request from TLS. In practice however semi-proprietary JAAS login modules also not rarely use the PolicyContext to get access to the http request. It should be noted that it's not entirely clear what the "current" request is, especially when servlet filters are used that do request wrapping. Doing an authorization query first (see below) may force the container to set the current request to the one that's really current.
Authorization queries
JACC provides an API and a means to ask the container several authorization related things. Just like JASPIC, JACC provides an API that's rather abstract and which seems to be infinitely flexible. Unfortunately this also means it's infinitely difficult for users to find out how to perform certain common tasks. So, while JACC enables us to ask the aforementioned things like "What roles does the current user have?" and "Will this user have access to this URL?", there aren't any convenience methods such as "List<String> getAllUserRoles();" or "boolean hasAccess(String url);". Below we'll show how these things are being doing in JACC.
Get all users roles
We'll start with obtaining the Subject instance corresponding to the current user. For simplicity we assume the user is indeed logged-in here.Subject subject = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");After that we'll get the so-called permission collection from the container that corresponds to this Subject:
PermissionCollection permissionCollection = Policy.getPolicy().getPermissions( new ProtectionDomain( new CodeSource(null, (Certificate[]) null), null, null, subject.getPrincipals().toArray(new Principal[subject.getPrincipals().size()]) ) );Most types shown here originate from the original Java SE security system (pre-JAAS even). Their usage is relatively rare in Java EE so we'll give a quick primer here. A more thorough explanation can be found in books like e.g. this one.
CodeSource
A code source indicates from which URL a class was loaded and with which certificates (if any) it was signed. This class was used in the original security model to protect your system from classes that were loaded from websites on the Internet; i.e. when Applets were the main focus area of Java. The roles associated with a user in Java EE do not depend in any way on the location from which the class asking this was loaded (they are so-called Principal-based permissions), so we provide a null for both the URL and the certificates here.
ProtectionDomain
A protection domain is primarily a grouping of a code source, and a set of static permissions that apply for the given Principals. In the original Java SE security model this type was introduced to be associated with a collection of classes, where each class part of the domain holds a reference to this type. In this case we're not using the protection domain in that way, but merely use it as input to ask which permissions the current Subject has. As such, the code source, static permissions and class loader (third parameter) are totally irrelevant. The only thing that matters is the Subject, and specifically its principals.The reason why we pass in a code source with all its fields set to null instead of just a null directly is that the getPermissions() method of well known Policy implementations like the PolicyFile from the Oracle JDK will call methods on it without checking if the entire thing isn't null. The bulky code to transform the Principals into an array is an unfortunate mismatch between the design of the Subject class (which uses a Set) and the ProtectionDomain (which uses a native array). It's extra unfortunate since the ProtectionDomain's constructor specifies it will copy the array again, so two copies of our original Set will be made here.
Policy
A policy is the main mechanism in Java SE that originally encapsulated the mapping between code sources and a global set of permissions that were given to that code source. Only much later in Java 1.4 was the ability added to get the permissions for a protection domain. Its JavaDoc has the somewhat disheartening warning that applications should not actually call the method. In case of JACC we can probably ignore this warning (application server implementations call this method too).
PermissionCollection
At first glance a PermissionCollection is exactly what its name implies (no pun intended): a collection of permissions. So why does the JDK have a special type for this and doesn't it just use a Collection<Permission> or a List<Permission>? Maybe part of the answer is that PermissionCollection was created before the Collection Framework in Java was introduced.But this may be only part of the answer. A PermissionCollection and its most important subclass Permissions make a distinction between homogenous and heterogenous Permissions. Adding an arbitrary Permission to a Permissions class is supposed to not just add it randomly in sequence, but to add it internally to a special "bucket" . This bucket is another PermissionCollection that stores permissions of the same type. It's typically implemented as a Class to PermissionCollection Map. This somewhat complex mechanism is used to optimize checking for a permission; iterating over every individual permission would not be ideal. We at least should be able to go right away to the right type of permission. E.g. when checking for permission to access a file, it's useless to ask every socket permission whether we have access to that.
After this we call the implies() method on the collection:
permissionCollection.implies(new WebRoleRefPermission("", "nothing"));This is small trick, hack if you will, to get rid of a special type of permission that might be in the collection; the UnresolvedPermission. This is a special type of permission that may be used when permissions are read from a file. Such file then typically contains the fully qualified name of a class that represents a specific permission. If this class hasn't been loaded yet or has been loaded by another class loader than the one from which the file is read, a UnresolvedPermission will be created that just contains this fully qualified class name as a String. The implies() method checks if the given permission is implied by the collection and therefor forces the actual loading of at least the WebRoleRefPermission class. This class is the standard permission type that corresponds to the non-standard representation of the group/roles inside the collection of principals that we're after.
Finally we iterate over the permission collection and collect all role names from the WebRoleRefPermission:
Set<String> roles = new HashSet<>(); for (Permission permission : list(permissionCollection.elements())) { if (permission instanceof WebRoleRefPermission) { String role = permission.getActions(); if (!roles.contains(role) && request.isUserInRole(role)) { roles.add(role); } } }A thing to note here is that there's no such thing as a WebRolePermission, but only a WebRoleRefPermission. In the Servlet spec a role ref is the thing that you use when inside a specific Servlet a role name is used that's different from the role names in the rest of your application. In theory this could be handy for secured Servlets from a library that you include in your application. Role refs are fully optional and when you don't use them you can simply use the application wide role names directly.
In JACC however there are only role refs. When a role ref is not explicitly defined then they are simply defaulted to the application role names. Since a role ref is per servlet, the number of WebRoleRefPermission instances that will be created is *at least* the number of roles in the application plus one (for the '**' role), times the number of servlets in the application (typically plus three for the default and JSP servlet, and an extra one for the so-called unmapped context). So given an application with two roles "foo" and "bar" and two Servlets named "servlet1" and "servlet2", the WebRoleRefPermission instances that will be created is as follows:
- servlet1 - foo
- servlet1 - bar
- servlet1 - **
- servlet2 - foo
- servlet2 - bar
- servlet2 - **
- default - foo
- default - bar
- default - **
- jsp - foo
- jsp - bar
- jsp - **
- "" - foo
- "" - bar
- "" - **
Set<String> roles = new HashSet<>(); for (Permission permission : list(permissionCollection.elements())) { if (permission instanceof WebRoleRefPermission && permission.getName().isEmpty()) { roles.add(permission.getActions()); } }
Typically JACC providers will create the total list of WebRoleRefPermission instances when an application is deployed and then return a sub-selection based on the Principals that we (indirectly) passed in our call to Policy#getPermissions. This however requires that all roles are statically and upfront declared. But a JASPIC auth module can dynamically return any amount of roles to the container and via HttpServletRequest#isUserInRole() an application can dynamically query for any such role without anything needing to be declared. Unfortunately such dynamic role usage typically doesn't work when JACC is used (the Java EE specification also forbids this, but on servers like JBoss it works anyway).
All in all the above shown query needs a lot of code and a lot of useless types and parameters for which nulls have to be passed. This could have been a lot simpler by any of the following means:
- Availability of a "List<String> getAllUserRoles();" method
- Standardization of the group/role Principals inside a subject (see JAAS in Java EE is not the universal standard you may think it is)
- A convenience method to get permissions based on just a Subject or Principal collection, e.g. PermissionCollection getPermissions(Subject subject); or PermissionCollection getPermissions(Collection<Principals> principals);
This same technique with slightly different code is also explained here: Using JACC to determine a caller's roles
Has access
Asking whether a user has permission to access a given resource (e.g. a Servlet) is luckily a bit smaller:Subject subject = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container"); boolean hasAccess = Policy.getPolicy().implies( new ProtectionDomain( new CodeSource(null, (Certificate[]) null), null, null, subject.getPrincipals().toArray(new Principal[subject.getPrincipals().size()]) ), new WebResourcePermission("/protected/Servlet", "GET")) ;We first get the Subject and create a ProtectionDomain in the same way as we did before. This time around we don't need to get the permission collection, but can make use of a small shortcut in the API. Calling implies on the Policy instance effectively invokes it on the permission collection that this instance maintains. Besides being ever so slightly shorter in code, it's presumably more efficient as well.
The second parameter that we pass in is the actual query; via a WebResourcePermission instance we can ask whether the resource "/protected/Servlet" can be accessed via a GET request. Both parameters support patterns and wildcards (see the JavaDoc). It's important to note that a WebResourcePermission only checks permission for the resource name and the HTTP method. There's a third aspect for checking access to a resource and that boils down to the URI scheme that's used (http vs https) and which corresponds to the <transport-guarantee> in a <user-data-constraint> element in web.xml. In JACC this information is NOT included in the WebResourcePermission, but a separate permission has been invented for that; the WebUserDataPermission, which can be individually queried.
Although this query requires less code than the previous one, it's probably still more verbose than many users would want to cope with. Creating the ProtectionDomain remains a cumbersome affair and getting the Subject via a String that needs to be remembered and requiring a cast unfortunately all add to the feeling of JACC being an arcane API. Here too, things could be made a lot simpler by:
- Availability of boolean hasAccess(String resource);" and boolean hasAccess(String resource, String metbod);" methods
- A convenience method to do a permission check based on the current user, e.g. boolean hasUserPermission(Permission permission);
- A convenience method to do a permission check based on just a Subject or Principal collection, e.g. boolean hasPermission(Subject subject, Permission permission); or boolean hasPermission(Collection
principals, Permission permission);
A hook into the authorization process
The most central feature of JACC is that it allows a class to be registered that hooks into the authorization process. "A class" is actually not entirely correct as typically 3 distinct classes are needed; a factory (there's of course always a factory), a "configuration" and the actual class (the "policy") that's called by the container (together the provider classes).
The factory and the policy have to be set individually via system properties (e.g. -D on the commandline). The configuration doesn't have to be set this way since it's created by the factory.
The following table gives an overview of these:
Class | System property | Description | Origin |
---|---|---|---|
javax.security.jacc.PolicyConfigurationFactory | javax.security.jacc.PolicyConfigurationFactory.provider | Creates and stores instances of PolicyConfiguration | JACC |
javax.security.jacc.PolicyConfiguration | - | Receives Permission instances corresponding to authorization constraints and can configure a Policy instance | JACC |
java.security.Policy | javax.security.jacc.policy.provider | Called by the container for authorization decisions | Java SE |
Complexity
While JACC is not the only specification that requires the registration of a factory (e.g. JSF has a similar requirement for setting a custom external context) its perhaps debatable whether the requirement to implement 3 classes and set 2 system properties isn't yet another reason why JACC is perceived as arcane. Indeed, when looking at a typical implementation of the PolicyConfigurationFactory it doesn't seem it does anything that the container wouldn't be able to do itself.
Likewise, the need to have a separate "configuration" and policy object isn't entirely clear either. Although just an extra class, such seemingly simple requirement does greatly add to the (perceived) complexity. This is extra problematic in the case of JACC since the "configuration" has an extremely heavyweight interface with an associated requirement to implement a state machine that controls the life cycle in which its methods can be called. Reading it one can only wonder why the container doesn't implement this required state machine and just lets the user implement the bare essentials. And if this all isn't enough the spec also hints at users having to make sure their "configuration" implementation is thread safe, again a task which one would think a container would be able to do.
Of course we have to realize that JACC originates from J2EE 1.4, where a simple "hello, world" EJB 2 required implementing many useless (for most users) container interfaces, the use of custom tools and verbose registrations in XML deployment descriptors, and where a custom component in JSF required the implementation and registration of many such moving parts as well. Eventually the EJB bean was simplified to just a single POJO with a single annotation, and while the JSF custom component didn't became a POJO it did became a single class with a single annotation as well.
JACC had some maintenance revisions, but never got a really major revision for ease of use. It thus still strongly reflects the J2EE 1.4 era thinking where everything had to be ultimately flexible, abstract and under the control of the user. Seemingly admirable goals, but unfortunately in practice leading to a level of complexity very few users are willing to cope with.
Registration
The fact that the mentioned provider classes can only be registered via system properties is problematic as well. In a way it's maybe better than having no standardized registration method at all and leaving it completely up to the discretion of an application server vendor, but having it as the only option means the JACC provider classes can only be registered globally for the entire server. In particular it thus means that all applications on a server have to share the same JACC provider even though authorization is not rarely quite specific to an individual application.
At any length not having a way to register the required classes from within an application archive seriously impedes testing, makes tutorials and example applications harder and doesn't really work well with cloud deployments either. Interestingly JASPIC which was released much later dished the system properties method again and left a server wide registration method unspecified, but did specify a way to register its artifacts from within an application archive.
Default implementation
Arguably one of the biggest issues with JACC is that the spec doesn't mandate a default implementation of a JACC provider to be present. This has two very serious consequences. First of all users wanting to use JACC for authorization queries can not just do this, since there might not be a default JACC provider available for their server, and if there is it might not be activated.
Maybe even worse is that users wanting to provide extended authorization behavior have to implement the entire default behavior from scratch. This is bad. Really bad. Even JSF 1.0 which definitely had its own share of issues allowed users to provide extensions by overriding only what they wanted to be different from whatever a default implementation provided. JACC is complex enough as it is. Not providing a default implementation is making the complexity barrier literally go through the roof.
Role mapping
While we have seen some pretty big issues with JACC already, by far the biggest issue and a complete hindrance to any form of portability of JACC policy providers is the fact that there's a huge gaping hole in the specification; the crucial concept of "role mapping" is not specified. The spec mentions it, but then says it's the provider's responsibility.
The problem is two fold; many containers (but not all) have a mechanism in place where the role names that are returned by a (JASPIC) authentication module (typically called "groups" at this point) are mapped to the role names that are used in the application. This can be a one to one mapping, e.g. "admin" is mapped to "administrator", but can also be a many to many mapping. For example when "admin and "super" both map to "super-users", but "admin" is also mapped to "administrator". JACC has no standard API available to access this mapping, yet it can't work without this knowledge if the application server indeed uses it.
In practice JACC providers thus have to be paired with a factory to obtain a custom role mapper. This role mapper has to be implemented again for every application server that uses role mapping and every JACC provider. Especially for the somewhat lesser known application servers and/or closed source ones it can be rather obscure to find out how to implement a role mapper for it. And the task is different for every other JACC policy provider as well, and some JACC policy providers may not have a factory or interface for it.
Even if we ignore role mapping all together (e.g. assume role "admin" returned by an auth module is the same "admin" used by the application, which is not at all uncommon), there is another crucial task that's typically attributed to the role mapper which we're missing; the ability to identify which Principals are in fact roles. As we've seen above in the section that explained the authorization queries we're passing in the Principals of a Subject. But as we've explained in a previous article this collection contains all sorts of principals and there's no standard way to identify which of those represent roles.
So the somewhat shockingly conclusion is that given the current Java EE specification it's not possible to write a portable and actual working JACC policy provider. At some point we just need the roles, but there's no way to get to them via any Java EE API, SPI, convention or otherwise. In sample code below we've implemented a crude hack where we hardcoded the way to get to the roles for a small group of known servers. This is of course far from ideal.
Sample code
The code below shows how to implement a simple as can be JACC module that implements the default behavior for Servlet and EJB containers. This thus doesn't show how to provide specialized behavior, but should give some idea what's needed. The required state machine was left out as well as the article was already getting way too long without it. The code is thus only intended to give a general impression of what JACC requires to be implemented. It's not an actual working and compliant JACC provider, and so definitely not recommended to be used for any actual application.
The factory
We start with creating the factory. This is the class that we primarily register with the container. It stores instances of the configuration class TestPolicyConfiguration in a static concurrent map. The code is more or less thread-safe with respect to creating a configuration. In case of a race we'll create an instance for nothing, but since it's not a heavy instance there won't be much harm done. For simplicity's sake we won't be paying much if any attention to thread-safety after this. The getPolicyConfiguration() will be called by the container whenever it needs the instance corresponding to the "contextID", which is in its simplest form the (web) application for which we are doing authorization.import static javax.security.jacc.PolicyContext.getContextID; // other imports omitted for brevity public class TestPolicyConfigurationFactory extends PolicyConfigurationFactory { private static final ConcurrentMap<String, TestPolicyConfiguration> configurations = new ConcurrentHashMap<>(); @Override public PolicyConfiguration getPolicyConfiguration(String contextID, boolean remove) throws PolicyContextException { if (!configurations.containsKey(contextID)) { configurations.putIfAbsent(contextID, new TestPolicyConfiguration(contextID)); } if (remove) { configurations.clear(); } return configurations.get(contextID); } @Override public boolean inService(String contextID) throws PolicyContextException { return getPolicyConfiguration(contextID, false).inService(); } public static TestPolicyConfiguration getCurrentPolicyConfiguration() { return configurations.get(getContextID()); } }
The configuration
The configuration class has a ton of methods to implement and additionally according to its JavaDocs a state machine has to be implemented as well.The methods that need to be implemented can be placed into a few groups. We'll first show the implementation of each group of methods and then after that show the entire class.
The first is simply about the identity of the configuration. It contains the method getContextID that returns the ID for the module for which the configuration is created. Although there's no guidance where this ID has to come from, a logical way seems to pass it from the factory via the constructor:
private final String contextID; public TestPolicyConfiguration(String contextID) { this.contextID = contextID; } @Override public String getContextID() throws PolicyContextException { return contextID; }The second group concerns the methods that receive from the container all permissions applicable to the application module, namely the excluded, unchecked and per role permissions. No less than 6 methods are demanded to be implemented. 3 of them each take a single permission of the aforementioned types, while the other 3 take a collection (PermissionCollection) of these permissions. Having these two sub-groups of methods seems rather unnecessary. Maybe there was a deeper reason once, but of the several existing implementations I studied all just iterated over the collection and added each individual permission separately to an internal collection.
At any length, we just collect each permission that we receive in the most straightforward way possible.
private Permissions excludedPermissions = new Permissions(); private Permissions uncheckedPermissions = new Permissions(); private Map<String, Permissions> perRolePermissions = new HashMap<String, Permissions>(); // Group 2a: collect single permission @Override public void addToExcludedPolicy(Permission permission) throws PolicyContextException { excludedPermissions.add(permission); } @Override public void addToUncheckedPolicy(Permission permission) throws PolicyContextException { uncheckedPermissions.add(permission); } @Override public void addToRole(String roleName, Permission permission) throws PolicyContextException { Permissions permissions = perRolePermissions.get(roleName); if (permissions == null) { permissions = new Permissions(); perRolePermissions.put(roleName, permissions); } permissions.add(permission); } // Group 2b: collect multiple permissions @Override public void addToExcludedPolicy(PermissionCollection permissions) throws PolicyContextException { for (Permission permission : list(permissions.elements())) { addToExcludedPolicy(permission); } } @Override public void addToUncheckedPolicy(PermissionCollection permissions) throws PolicyContextException { for (Permission permission : list(permissions.elements())) { addToUncheckedPolicy(permission); } } @Override public void addToRole(String roleName, PermissionCollection permissions) throws PolicyContextException { for (Permission permission : list(permissions.elements())) { addToRole(roleName, permission); } }
The third group are life-cycle methods, partly part of the above mentioned state machine. Much of the complexity here is caused by the fact that multi-module applications have to share some authorization data but also still need to have their own, and that some modules have to be available before others. The commit methods signals that the last permission has been given to the configuration class. Depending on the exact strategy used, the configuration class could now e.g. start building up some specialized data structure, write the collected permissions to a standard policy file on disk, etc.
In our case we have nothing special to do. For this simple example we only implement the most basic requirement of the aforementioned state machine and that's making sure the inService method returns true when we're done. Containers may check for this via the previously shown factory, and will otherwise not hand over the permissions to our configuration class or keep doing that forever.
@Override public void linkConfiguration(PolicyConfiguration link) throws PolicyContextException { } boolean inservice; @Override public void commit() throws PolicyContextException { inservice = true; } @Override public boolean inService() throws PolicyContextException { return inservice; }
The fourth group concern methods that ask for the deletion of previously given permissions. Here too we see some redundant overlap; there's 1 method that deletes all permissions and 1 method for each type. These methods are supposedly needed because programmatic registration of Servlets can change the authorization data during container startup and every time(?) that this happens all previously collected permissions would have to be deleted. Apparently the container can't delay the moment of handing permissions to the configuration class since during the time when it's legal to register Servlets a call can be made to another module (e.g. a ServletContextListener could call an EJB in a separate EJB module). Still, it's questionable whether it's really needed to have a kind of back-tracking permission collector in place and whether part of this burden should really be placed on the user implementing an authorization extension.
In our case the implementations are pretty straightforward. The Permissions type doesn't have a clear() method so we'll replace it by a new instance. For the Map we can call a clear() method, but there's a special protocol to be executed concerning the "*" role; if the collection explicitly contains a role name "*" remove it, otherwise clear the entire collection.
@Override public void delete() throws PolicyContextException { removeExcludedPolicy(); removeUncheckedPolicy(); perRolePermissions.clear(); } @Override public void removeExcludedPolicy() throws PolicyContextException { excludedPermissions = new Permissions(); } @Override public void removeRole(String roleName) throws PolicyContextException { if (perRolePermissions.containsKey(roleName)) { perRolePermissions.remove(roleName); } else if ("*".equals(roleName)) { perRolePermissions.clear(); } }
Finally we added 3 getters ourselves to give access to the 3 collections of permissions that we have been building up. We'll use this below to give the Policy instance access to the permissions.
public Permissions getExcludedPermissions() { return excludedPermissions; } public Permissions getUncheckedPermissions() { return uncheckedPermissions; } public Map<String, Permissions> getPerRolePermissions() { return perRolePermissions; }
Although initially unwieldy looking, the PolicyConfiguration in our case becomes just a data structure to add, get and remove three types of different but related groups of objects. All together it becomes this:
import static java.util.Collections.list; // other imports omitted for brevity public class TestPolicyConfiguration implements PolicyConfiguration { private final String contextID; private Permissions excludedPermissions = new Permissions(); private Permissions uncheckedPermissions = new Permissions(); private Map<String, Permissions> perRolePermissions = new HashMap<String, Permissions>(); // Group 1: identity public TestPolicyConfiguration(String contextID) { this.contextID = contextID; } @Override public String getContextID() throws PolicyContextException { return contextID; } // Group 2: collect permissions from container // Group 2a: collect single permission @Override public void addToExcludedPolicy(Permission permission) throws PolicyContextException { excludedPermissions.add(permission); } @Override public void addToUncheckedPolicy(Permission permission) throws PolicyContextException { uncheckedPermissions.add(permission); } @Override public void addToRole(String roleName, Permission permission) throws PolicyContextException { Permissions permissions = perRolePermissions.get(roleName); if (permissions == null) { permissions = new Permissions(); perRolePermissions.put(roleName, permissions); } permissions.add(permission); } // Group 2b: collect multiple permissions @Override public void addToExcludedPolicy(PermissionCollection permissions) throws PolicyContextException { for (Permission permission : list(permissions.elements())) { addToExcludedPolicy(permission); } } @Override public void addToUncheckedPolicy(PermissionCollection permissions) throws PolicyContextException { for (Permission permission : list(permissions.elements())) { addToUncheckedPolicy(permission); } } @Override public void addToRole(String roleName, PermissionCollection permissions) throws PolicyContextException { for (Permission permission : list(permissions.elements())) { addToRole(roleName, permission); } } // Group 3: life-cycle methods @Override public void linkConfiguration(PolicyConfiguration link) throws PolicyContextException { } boolean inservice; @Override public void commit() throws PolicyContextException { inservice = true; } @Override public boolean inService() throws PolicyContextException { return inservice; } // Group 4: removing all or specific collection types again @Override public void delete() throws PolicyContextException { removeExcludedPolicy(); removeUncheckedPolicy(); perRolePermissions.clear(); } @Override public void removeExcludedPolicy() throws PolicyContextException { excludedPermissions = new Permissions(); } @Override public void removeRole(String roleName) throws PolicyContextException { if (perRolePermissions.containsKey(roleName)) { perRolePermissions.remove(roleName); } else if ("*".equals(roleName)) { perRolePermissions.clear(); } } @Override public void removeUncheckedPolicy() throws PolicyContextException { uncheckedPermissions = new Permissions(); } // Group 5: extra methods public Permissions getExcludedPermissions() { return excludedPermissions; } public Permissions getUncheckedPermissions() { return uncheckedPermissions; } public Map<String, Permissions> getPerRolePermissions() { return perRolePermissions; } }
The policy
To implement the policy we inherit from the Java SE policy class and override the implies and getPermissions methods. Where the PolicyConfiguration is the "dumb" data structure that just contains the collections of different permissions, the Policy implements the rules that operate on this data.The policy instance is per the JDK rules a single instance for the entire JVM, so central to its implementation in Java EE is that it first has to obtain the correct data corresponding to the "current" Java EE application or module within such application. The key to obtaining this data is the Context ID that is set in thread local storage by the container prior to calling the policy. The factory that we showed earlier stored the configuration instance under this key in a concurrent map and we use the extra method that we added to the factory here to conveniently retrieve it again.
The policy implementation also contains the hacky method that we referred to earlier for extracting the roles from a collection of principals. It just iterates over the collection and matches the principals against the known group names for each server. Of course this is a brittle and incomplete technique. Newer versions of servers can change the class names and we have not covered all servers; e.g. WebSphere is missing since via the custom method to obtain a Subject we saw earlier that the roles were stored in the credentials (we may need to study this further).
package test; import static java.util.Collections.list; import static test.TestPolicyConfigurationFactory.getCurrentPolicyConfiguration; // other imports omitted for brevity public class TestPolicy extends Policy { private Policy previousPolicy = Policy.getPolicy(); @Override public boolean implies(ProtectionDomain domain, Permission permission) { TestPolicyConfiguration policyConfiguration = getCurrentPolicyConfiguration(); if (isExcluded(policyConfiguration.getExcludedPermissions(), permission)) { // Excluded permissions cannot be accessed by anyone return false; } if (isUnchecked(policyConfiguration.getUncheckedPermissions(), permission)) { // Unchecked permissions are free to be accessed by everyone return true; } if (hasAccessViaRole(policyConfiguration.getPerRolePermissions(), getRoles(domain.getPrincipals()), permission)) { // Access is granted via role. Note that if this returns false it doesn't mean the permission is not // granted. A role can only grant, not take away permissions. return true; } if (previousPolicy != null) { return previousPolicy.implies(domain, permission); } return false; } @Override public PermissionCollection getPermissions(ProtectionDomain domain) { Permissions permissions = new Permissions(); TestPolicyConfiguration policyConfiguration = getCurrentPolicyConfiguration(); Permissions excludedPermissions = policyConfiguration.getExcludedPermissions(); // First get all permissions from the previous (original) policy if (previousPolicy != null) { collectPermissions(previousPolicy.getPermissions(domain), permissions, excludedPermissions); } // If there are any static permissions, add those next if (domain.getPermissions() != null) { collectPermissions(domain.getPermissions(), permissions, excludedPermissions); } // Thirdly, get all unchecked permissions collectPermissions(policyConfiguration.getUncheckedPermissions(), permissions, excludedPermissions); // Finally get the permissions for each role *that the current user has* Map<String, Permissions> perRolePermissions = policyConfiguration.getPerRolePermissions(); for (String role : getRoles(domain.getPrincipals())) { if (perRolePermissions.containsKey(role)) { collectPermissions(perRolePermissions.get(role), permissions, excludedPermissions); } } return permissions; } @Override public PermissionCollection getPermissions(CodeSource codesource) { Permissions permissions = new Permissions(); TestPolicyConfiguration policyConfiguration = getCurrentPolicyConfiguration(); Permissions excludedPermissions = policyConfiguration.getExcludedPermissions(); // First get all permissions from the previous (original) policy if (previousPolicy != null) { collectPermissions(previousPolicy.getPermissions(codesource), permissions, excludedPermissions); } // Secondly get the static permissions. Note that there are only two sources possible here, without // knowing the roles of the current user we can't check the per role permissions. collectPermissions(policyConfiguration.getUncheckedPermissions(), permissions, excludedPermissions); return permissions; } private boolean isExcluded(Permissions excludedPermissions, Permission permission) { if (excludedPermissions.implies(permission)) { return true; } for (Permission excludedPermission : list(excludedPermissions.elements())) { if (permission.implies(excludedPermission)) { return true; } } return false; } private boolean isUnchecked(Permissions uncheckedPermissions, Permission permission) { return uncheckedPermissions.implies(permission); } private boolean hasAccessViaRole(Map<String, Permissions> perRolePermissions, List<String> roles, Permission permission) { for (String role : roles) { if (perRolePermissions.containsKey(role) && perRolePermissions.get(role).implies(permission)) { return true; } } return false; } /** * Copies permissions from a source into a target skipping any permission that's excluded. * * @param sourcePermissions * @param targetPermissions * @param excludedPermissions */ private void collectPermissions(PermissionCollection sourcePermissions, PermissionCollection targetPermissions, Permissions excludedPermissions) { boolean hasExcludedPermissions = excludedPermissions.elements().hasMoreElements(); for (Permission permission : list(sourcePermissions.elements())) { if (!hasExcludedPermissions || !isExcluded(excludedPermissions, permission)) { targetPermissions.add(permission); } } } /** * Extracts the roles from the vendor specific principals. SAD that this is needed :( * @param principals * @return */ private List<String> getRoles(Principal[] principals) { List<String> roles = new ArrayList<>(); for (Principal principal : principals) { switch (principal.getClass().getName()) { case "org.glassfish.security.common.Group": // GlassFish case "org.apache.geronimo.security.realm.providers.GeronimoGroupPrincipal": // Geronimo case "weblogic.security.principal.WLSGroupImpl": // WebLogic case "jeus.security.resource.GroupPrincipalImpl": // JEUS roles.add(principal.getName()); break; case "org.jboss.security.SimpleGroup": // JBoss if (principal.getName().equals("Roles") && principal instanceof Group) { Group rolesGroup = (Group) principal; for (Principal groupPrincipal : list(rolesGroup.members())) { roles.add(groupPrincipal.getName()); } // Should only be one group holding the roles, so can exit the loop // early return roles; } } } return roles; } }
Summary of JACC's problems
Unfortunately we've seen that JACC has a few problems. It has a somewhat arcane and verbose API which easily puts (new) users off. We've also seen that it puts too much responsibilities on the user, especially when it comes to customizing the authorization system.
A fatal flaw in JACC is that it didn't specify how to access roles from a collection of Principals. Since authorization is primarily role based in Java EE this makes it downright impossible to create portable JACC policy providers and much harder than necessary to create vendor specific ones.
Another fatal flaw of JACC is that there's not necessarily a default implementation of JACC active at runtime. This means that general Java EE applications cannot just use JACC; they would have to instruct their users to make sure JACC is activated for their server. Since not all servers ship with a simple default implementation this would not even work for all servers.
On the one hand it's impressive that JACC has managed to integrate so well with Java SE security. On the other hand it's debatable whether this has been really useful in practice. Java EE containers can now consult the Java SE Policy for authorization, but when a security manager is installed Java SE code will consult the very same one.
Java SE permissions are mostly about what code coming from some code location (e.g. a specific library directory on the severer) is allowed to do, while Java EE permissions are about the things an external user logged-in to the server is allowed to do. With the current setup the JACC policy replaces the low level Java SE one and thus all Java SE security checks will go through the JACC policy as well. This may be tens of checks per second or even more. All that a JACC policy can really do is delegate these to the default Java SE policy.
Conclusion
The concept of having a central repository holding all authorization rules is by itself a powerful addition to the Java EE security system. It allows one to query the repository and thus programmatically check if a resource will be accessible. This is particularly useful for URL based resources; if a user would not have access we can omit rendering a link to that resource, or perhaps render it with a different color, with a lock icon, etc.
Although not explicitly demonstrated in this article it shouldn't require too much imagination to see that the default implementation that we showed can easily do something else instead and thus tailor the authorization system to the specific needs of an application.
As shown it might not be that difficult to reduce the verbosity of the JACC API by introducing a couple of convenience methods for common functionality. Specifying which principals corresponds to roles would be the most straightforward solution for the roles problem, but a container provided role mapper instance that returns a list of group/role principals given a collection of strings representing application roles and the other way around might be a workable solution as well.
While tedious for users to implement, container vendors should not have much difficulties implementing a default JACC policy provider and activating it by default. Actually mandating providers to USE JACC themselves for their Servlet and EJB authorization decisions may be another story though. A couple of vendors (specifically Oracle themselves for WebLogic) claim performance and flexibility issues with JACC and actually more or less encourage users not to use it. In case of WebLogic this advice probably stems from the BEA days, but it's still in the current WebLogic documentation. With Oracle being the steward of JACC it's remarkable that they effectively suggest their own users not to use it, although GlassFish which is also from Oracle is one of the few or perhaps only server that in fact uses JACC internally itself.
As it stands JACC is not used a lot and not universally loved, but a few relatively small changes may be all that's needed to make it much more accessible and easy to use.
Arjan Tijms
Further reading:
Nice article. One other thing to point out is that JACC is the only specification that will allow you to do instance-based authorization. That is, using JACC, you can answer an authorization question based on properties of the actual method call, not just static properties (like role information). To my knowledge this is the only standardized way to ask and answer the question, "Can this user, passing these parameter values, do this thing at this time?"
ReplyDeleteThanks for the comment. The instance based authorization is indeed an interesting property that I surely have to address in a follow-up article. I guess it holds for web resources and URL parameters just as well. In this article though I only tried to address the bare basics and it already got way longer than intended.
Deletep.s. congratulations on your new job with Oracle :) Maybe you're now in the position to personally address some of your grievances with JACC ;) Things would be so much better if only that role mapper was there.
Very nice article. I just would like to add one more link that I used in conjunction with your article to implement a JACC provider. Although it doesn't offer much of its own, it cleared things in my mind:
ReplyDeletehttp://geronimo.apache.org/GMOxDEV/jacc-guide.html
Just to keep you updated. I was trying to use your implementation for custom JACC provider on IBM WAS 7.0 and to sum things up, I failed due to a very strange behavior related to class loader.
DeleteWill update you if it worked. But for now, I guess I will drop this approach and try something else, may be JAAS.
Thanks a lot for the link Mohamed! I've added it right away to the article.
DeleteAbout the custom JACC provider, I do have to stress that it's just an example to illustrate the concepts of what JACC does/requires. It's also not complete, as I largely left out the required state machine. The plan was to implement this as a kind of universal layer on top of it (demonstrating that user provided JACC modules don't necessarily need to implement this) but I never found the time to do this.
I'll see if I can clarify this in the post somehow.
All in all I found JACC to be really problematic and in practice almost impossible to work with, unfortunately. JAAS is something else entirely btw and for a Java EE environment is not something that can directly replace either JACC or JASPIC (in fact, both are kind of bridges to parts of JAAS).
Hello; random comment: looks like you may have misinterpreted the remove parameter in PolicyConfigurationFactory#getPolicyConfiguration(String, boolean). If it is true, it does not mean to clear all potentially stored PolicyConfigurations. It means to do whatever you need to do to semantically clear out the notional policy context that the PolicyConfiguration "fronts" before returning that PolicyConfiguration.
ReplyDeleteGood observation! I remember being slightly puzzled about the remove parameter, and I'm pretty sure you're right and I most likely misinterpreted that one. Silly me. Contrary to nearly every other Java EE spec, I don't actually use JACC myself (nor does anyone else in my organization), so none of the code presented here is battle tested in any way.
DeleteI still have hopes that as part of the coming Java EE 8 security effort there will be an opportunity to simplify and modernize JACC.
As you've been involved with JACC before (one of the very few persons I know to be honest), what do you think is the most important area of JACC that should be addressed first?
Role mapping for sure, or, if it's possible by joining together a JASPIC implementation with a JACC implementationāsomething I've heard, but have never triedāat least make that clear.
ReplyDeleteLess importantly, there are several areas in the specification that talk about ensuring a PolicyConfiguration is in the open state, but there's no way to test for that.
On the security-in-general side, it would be nice if a permission evaluation could be an enum, not a boolean, or if permissions could be evaluated in a stack, like PAM does with authentication modules. Hazy ideas here, but, you know, stuff like "I evaluated this permission and I implied it, but someone more authoritative than I might deny it".
One other quick observation since people are probably using some of your sample code. I think in your Policy implementation in your hasAccessViaRole method you are not considering the magic "*" role. I might have missed something.
ReplyDeleteLast sample code observation. While bearing in mind the spirit of what you're sayingāthere's no non-hackish way to get role information for a group of Principalsāit's worth noting that your hack doesn't really work in all cases for Glassfish. Specifically, the Principals you'll find in a Subject are the user and (it seems) the **groups** to which he belongs, not the roles. To truly get role mapping to work in Glassfish, you have to look at the org.glassfish.deployment.common.SecurityRoleMapper classāsee for example http://grepcode.com/file/repo1.maven.org/maven2/org.glassfish.deployment/dol/3.1.1/com/sun/enterprise/deployment/interfaces/SecurityRoleMapper.java/#114
ReplyDeleteSpecifically, the Principals you'll find in a Subject are the user and (it seems) the **groups** to which he belongs, not the roles.
DeleteThat's true, and as discussed outside the blog this was intentional.
By accepting the "roles" that are returned by the authentication module (commonly called groups at this point) directly, the JACC provider is effectively doing a 1:1 group-to-role mapping.
If there's any server specific group-to-role mapping in place then this is thus indeed ignored.
Supporting such mapping universally is of course impossible, but supporting it in a hacky way means probing the environment for any of the known server specific role mappers and e.g. calling them via reflection.
how to refer to custom classes within application in glassfish JACC provider configuration???
ReplyDeleteThat doesn't seem to be entirely trivial :(
DeleteThe problem is that a JACC provider is set for the entire AS and the Policy even for the entire JVM. There may be something possible with an interface that's defined in the archive (jar file) containing the JACC provider and which is then implemented by the application.
thanks a lot Arjan,but after implemented the new JACC and refer to it in glassfish JACC Provider and restart the glassfish again I have a problem when trying Login to Glassfish Administration Console it always give forbidden error. how could I overcome this problem ?
Deleteis there anyway to overcome this problem, as I need it urgently
DeletePlease note that the code shown for the JACC provider here is more like a demonstration of the concepts than an actual working and fully tested product. As mentioned in the article, the *required* state machine is still missing and as Laird Nelson mentioned above there are other small omissions.
DeleteSo, while you can use the code as a tool to learn and give you a general impression of what JACC requires from you, it's absolutely not recommended to use this for any kind of actual application.
I do plan to get back to this subject eventually and do some more testing, address the comments etc, but even then it will still be recommended only as an educational tool.
Also note that GlassFish comes with 2 JACC providers, an in-memory one (optional) and a file based one (default). Since GlassFish is fully open source you can study and modify these if needed.
See https://blogs.oracle.com/monzillo/entry/how_to_configure_an_alternative and https://blogs.oracle.com/monzillo/entry/prelude_includes_portable_in_memory