Wednesday, May 24, 2017

Draft list of changes in Servlet 4.0

The proposed final draft (PDF) of the Servlet 4.0 spec has just been made available at GitHub.

The major new feature is HTTP/2 support and specifically the push support that comes with it. Java EE already has support for push via WebSockets (including WebSocket support in JSF 2.3), but there are other interesting changes as well, such as for instance the Mapping Discovery API.

The following contains a terse list of changes, taken from section A.1 of the above linked Servlet 4.0 PFD document. All mentioned references to sections are to that document.

  1. Requirement to support HTTP/2, see Section 1.2, “What is a Servlet Container?” on page 1-1 and “What is a Servlet?” on page 1 1. This includes HTTP/2 server push, see “HTTP/2 Server Push” on page 3 29.
  2. Modify javadoc for ServletContext getAttribute() and getInitParameter(), specify that NullPointerException must be thrown if the argument “name” is null.
  3. Modify javadoc for ServletContext.setAttribute() and setInitParameter() to specify that NullPointerException must be thrown if the “name” argument is null.
  4. DeprecateHttpServletRequestWrapper.isRequestedSessionIdFromUrl().
  5. Add @Deprecated to classes and methods with @deprecated in javadoc: ServletContext, ServletRequestWrapper, SingleThreadModel, UnavailableException, HttpServletRequest, HttpServletResponse, HttpServletResponseWrapper, HttpSession, HttpSessionContext, HttpUtils.
  6. Add default-context-path in the schema of web.xml and the description in Section 30., “default-context-path Element” on page 14-180 and the figure, Section FIGURE 14-1, “web-app Element Structure”.
  7. Modify Section 7.7.1, “Threading Issues” to clarify non-thread safety of objects vended from requests and responses.
  8. Clarify metadata-complete in Section 8.1, “Annotations and pluggability”.
  9. Add default to methods in ServletContextAttributeListener, ServletContextListener, ServletRequestAttributeListener, ServletRequestListener, HttpSessionActivationListener, HttpSessionAttributeListener, HttpSessionBindingListener, HttpSessionListener.
  10. Add javax.servlet.GenericFilter and javax.servlet.http.HttpFilter
  11. Clarify the merging of in web.xml and web-fragment.xml in Section 8.2.3, “Assembling the descriptor from web.xml, web-fragment.xml and annotations”.
  12. Modify javadoc for ServletContext.getEffectiveSessionTrackingModes() without specifying the default value.
  13. Remove DTDs and Schemas from binary artifact for Servlet API.
  14. Add getSessionTimeout and setSessionTimeout in ServletContext. See javadoc, Section 4.4.4 and Section 7.5.
  15. Add addJspFile() in ServletContext. See javadoc, Section 4.4.1.4 and Section 4.4.1.7.
  16. Add request-character-encoding and response-character-encoding in the schema of web.xml. See the corresponding descriptions of the elements in Section 31. and Section 32.
  17. Add getRequestCharacterEncoding, setRequestCharacterEncoding, getResponseCharacterEncoding and setResponseCharacterEncoding in ServletContext. Update the corresponding javadoc of ServletContext, ServletRequest and ServletResponse. See Section 4.4.5, Section 3.12 and Section 5.6.
  18. Describe mapping discovery API. See Section 12.3, “Runtime Discovery of Mappings”.
  19. Update the javadoc of Registration, ServletContext, ServletRegistration for the behaviors of returned sets.
  20. Clarify the behaviors of complete and dispatch in AsyncContext before the container-initiated dispatch that called startAsync has returned to the container. See Section , “AsyncContext” on page 2-13.
  21. Clarify interpretation of fileName parameter for method Part.write(). See the javadoc for details.
  22. Clarify encoding used for reading the request URL. See Section 12.1, “Use of URL Paths” on page 12-125 for details.
  23. Specified support for HTTP trailer. See Section 5.3, “HTTP Trailer” for details. Add getTrailerFields(), isTrailerFieldsReady() in HttpServletRequest and setTrailerFields in HttpServletResponse. See the corresponding javadoc.

Tuesday, March 28, 2017

JSF 2.3 released!

After a long and at times intense spec and development process the JSF 2.3 EG is proud to announce that today we've released JSF 2.3.

JSF (JavaServer Faces), is a component based MVC framework that's part of Java EE. JSF 2.3 in particular is part of Java EE 8.

Major new features in JSF 2.3 are a tighter integration with CDI, support for WebSockets, a really cool component search expression framework (donated by PrimeFaces), basic support for extensionless URLs, and class level bean validation.

The age old native managed beans of JSF 2.3 have finally been deprecated (although they are still available for now) in favour of CDI. It's expected that these will be fully removed (pruned) in a future release.

The JSF 2.3 EG would like to thank everyone who contributed to JSF in whatever way, by creating bug reports, testing builds, providing comments and insights on the mailinglist and contributing code. Without those community contributions JSF 2.3 would not have been possible! Thanks to all our fantastic community members!

JSF 2.3 (Mojarra 2.3) can be downloaded per direct from the project's download page.

Maven coordinates for the implementation (includes API) are:

<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.faces</artifactId>
    <version>2.3.0</version>
</dependency>

The full implementation can be used for Servlet containers such as Tomcat and Jetty.

Maven coordinates for just the API are:

<dependency>
    <groupId>javax.faces</groupId>
    <artifactId>javax.faces-api</artifactId>
    <version>2.3</version>
</dependency>

The API jar can only be used as a compile time dependency.

Application servers Payara and GlassFish can be trivially updated by replacing the JSF 2.2 glassfish/modules/javax.faces.jar with the 2.3 version. It's usually a good idea to clear the OSGI cache after that (e.g. rm -rf [payara/gf gome]/ glassfish/domains/domain1/osgi-cache/felix/)

Arjan Tijms

Sunday, December 4, 2016

The state of portable authentication in Java EE, end 2016 update

In the beginning and middle of this year we looked at how well modern Java EE servers supported portable authentication (JASPIC) in Java EE. As the end of 2016 approaches we take a third look to see how things are progressing.

Since our last time new versions of all servers have been released. Payara went from 163-beta to 164, WildFly went from 10.0 to 10.1, Liberty beta went from 2016-5 to 2016-11, WebLogic went from 12.2.1 to 12.2.1.2 and TomEE went from 7.0 to 7.0.2. We also added a new server, namely Tomcat. Tomcat was indirectly already tested via TomEE, but given the importance of standalone Tomcat we decided to put this one in explicitly. Do note that Tomcat is not a full or web profile Java EE server, so the integration tests for technologies it doesn't support (like JSF, CDI, etc) are simply omitted.

Tests were added for request.authenticate, an injected CDI request, the servlet path after a forward, isMandatory in a SAM, and finally for a SAM request to be seen by a Filter. For the first time there was also a test removed, namely including a JSF based resource from a SAM. This fails on many servers but the failure appeared to be a general JSF failure unrelated to JASPIC and/or its integration with the Java EE environment.

The results of running the latest series of JASPIC tests are shown below:

Running the Java EE 7 samples JASPIC tests
Module Test Payara 164 WildFly 10.1 Liberty 2016-11 Weblogic 12.2.1.2 TomEE 7.0.2 Tomcat 8.5.6
async-authentication testBasicAsync
Passed
Passed
Passed
Passed
Passed
-
basic-authentication testProtectedPageNotLoggedin
Passed
Passed
Passed
Passed
Passed
Passed
basic-authentication testProtectedPageLoggedin
Passed
Passed
Passed
Passed
Passed
Passed
basic-authentication testPublicPageLoggedin
Passed
Passed
Passed
Passed
Passed
Passed
basic-authentication testPublicPageNotLoggedin
Passed
Passed
Passed
Passed
Passed
Passed
basic-authentication testPublicAccessIsStateless
Passed
Passed
Passed
Passed
Passed
Passed
basic-authentication testProtectedAccessIsStateless
Passed
Passed
Passed
Passed
Passed
Passed
basic-authentication testProtectedAccessIsStateless2
Passed
Passed
Passed
Passed
Passed
Passed
basic-authentication testProtectedThenPublicAccessIsStateless
Passed
Passed
Passed
Passed
Passed
Passed
custom-principal testProtectedPageLoggedin
Passed
Passed
Passed
Failure
Passed
Passed
custom-principal testPublicPageLoggedin
Passed
Passed
Passed
Failure
Passed
Passed
custom-principal testPublicAccessIsStateless
Passed
Passed
Passed
Passed
Passed
Passed
custom-principal testProtectedAccessIsStateless
Passed
Passed
Passed
Passed
Passed
Passed
custom-principal testProtectedAccessIsStateless2
Passed
Passed
Passed
Passed
Passed
Passed
custom-principal testProtectedThenPublicAccessIsStateless
Passed
Passed
Passed
Passed
Passed
Passed
dispatching testBasicForwardViaProtectedResource
Passed
Passed
Passed
Passed
Passed
Passed
dispatching testBasicForwardViaPublicResource
Passed
Passed
Passed
Passed
Passed
Passed
dispatching testBasicIncludeViaPublicResource
Passed
Passed
Passed
Passed
Passed
Passed
dispatching-jsf-cdi testCDIForwardWithRequestProtected
Passed
Passed
Passed
Passed
Passed
-
dispatching-jsf-cdi testCDIForwardWithRequestInjectPublic
Passed
Passed
Passed
Passed
Failure
-
dispatching-jsf-cdi testCDIForwardViaProtectedResource
Passed
Passed
Passed
Passed
Passed
-
dispatching-jsf-cdi testCDIForwardViaPublicResource
Passed
Passed
Passed
Passed
Passed
-
dispatching-jsf-cdi testCDIForwardWithRequestPublic
Passed
Passed
Passed
Passed
Passed
-
dispatching-jsf-cdi testCDIForwardWithRequestInjectProtected
Passed
Passed
Passed
Passed
Failure
-
dispatching-jsf-cdi testCDIIncludeViaPublicResource
Passed
Passed
Passed
Passed
Passed
-
dispatching-jsf-cdi testJSFwithCDIForwardViaPublicResource
Passed
Passed
Passed
Passed
Passed
-
dispatching-jsf-cdi testJSFwithCDIForwardViaProtectedResource
Passed
Passed
Passed
Passed
Passed
-
dispatching-jsf-cdi testJSFForwardViaPublicResource
Passed
Passed
Passed
Passed
Passed
-
dispatching-jsf-cdi testJSFForwardViaProtectedResource
Passed
Passed
Passed
Passed
Passed
-
ejb-propagation publicServletCallingProtectedEJB
Passed
Passed
Passed
Failure
Passed
-
ejb-propagation protectedServletCallingProtectedEJB
Passed
Passed
Passed
Failure
Passed
-
ejb-propagation publicServletCallingPublicEJBThenLogout
Passed
Passed
Passed
Passed
Passed
-
ejb-propagation protectedServletCallingPublicEJB
Passed
Passed
Passed
Passed
Passed
-
ejb-register-session testRemembersSession
Passed
Passed
Passed
Failure
Passed
-
ejb-register-session testRemembersSession
Passed
Passed
Passed
Failure
Passed
-
invoke-ejb-cdi protectedInvokeCDIFromSecureResponse
Passed
Passed
Failure
Failure
Passed
-
invoke-ejb-cdi protectedInvokeCDIFromCleanSubject
Passed
Passed
Passed
Passed
Passed
-
invoke-ejb-cdi protectedInvokeCDIFromValidateRequest
Passed
Passed
Failure
Failure
Passed
-
invoke-ejb-cdi publicInvokeCDIUseInjectedRequestFromValidateRequest
Passed
Passed
Failure
Failure
Passed
-
invoke-ejb-cdi publicInvokeCDIFromSecureResponse
Passed
Passed
Failure
Failure
Passed
-
invoke-ejb-cdi publicInvokeCDIFromValidateRequest
Passed
Passed
Failure
Failure
Passed
-
invoke-ejb-cdi publicInvokeCDIUseInjectedRequestFromCleanSubject
Passed
Passed
Passed
Passed
Passed
-
invoke-ejb-cdi publicInvokeCDIUseInjectedRequestFromSecureResponse
Passed
Passed
Failure
Failure
Passed
-
invoke-ejb-cdi publicInvokeCDIFromCleanSubject
Passed
Passed
Passed
Passed
Passed
-
invoke-ejb-cdi protectedInvokeEJBFromSecureResponse
Passed
Passed
Passed
Passed
Passed
-
invoke-ejb-cdi protectedInvokeEJBFromCleanSubject
Passed
Passed
Passed
Passed
Passed
-
invoke-ejb-cdi protectedInvokeEJBFromValidateRequest
Passed
Passed
Passed
Passed
Passed
-
invoke-ejb-cdi publicInvokeEJBFromSecureResponse
Passed
Passed
Passed
Passed
Passed
-
invoke-ejb-cdi publicInvokeEJBFromValidateRequest
Passed
Passed
Passed
Passed
Passed
-
invoke-ejb-cdi publicInvokeEJBFromCleanSubject
Passed
Passed
Passed
Passed
Passed
-
jacc-propagation callingJACCWhenAuthenticated
Passed
Passed
Failure
Failure
Failure
-
jacc-propagation callingJACCWhenAuthenticated
Passed
Passed
Failure
Failure
Failure
-
jacc-propagation callingJACCWhenNotAuthenticated
Passed
Passed
Failure
Failure
Failure
-
lifecycle testBasicSAMMethodsCalled
Passed
Passed
Passed
Passed
Passed
Passed
lifecycle testLogout
Passed
Passed
Passed
Passed
Passed
Passed
lifecycle testProtectedIsMandatory
Passed
Passed
Passed
Passed
Passed
Passed
lifecycle testPublicIsNonMandatory
Passed
Passed
Passed
Passed
Passed
Passed
programmatic-authentication testAuthenticateFailFirstTwice
Passed
Passed
Passed
Passed
Passed
Passed
programmatic-authentication testAuthenticate
Passed
Passed
Passed
Passed
Passed
Passed
programmatic-authentication testAuthenticateFailFirstOnce
Passed
Passed
Passed
Passed
Passed
Passed
register-session testJoinSessionIsOptional
Passed
Passed
Passed
Failure
Passed
Passed
register-session testRemembersSession
Passed
Passed
Passed
Failure
Passed
Passed
register-session testJoinSessionIsOptional
Passed
Passed
Passed
Failure
Passed
Passed
register-session testRemembersSession
Passed
Passed
Passed
Failure
Passed
Passed
status-codes test404inResponse
Passed
Passed
Passed
Passed
Passed
Passed
status-codes test404inResponse
Passed
Passed
Passed
Passed
Passed
Passed
wrapping testResponseWrapping
Passed
Passed
Passed
Passed
Passed
Passed
wrapping testDeclaredFilterResponseWrapping
Passed
Passed
Failure
Passed
Passed
Passed
wrapping testProgrammaticFilterResponseWrapping
Passed
Passed
Failure
Passed
Passed
Passed
wrapping testDeclaredFilterRequestWrapping
Passed
Passed
Failure
Passed
Passed
Passed
wrapping testProgrammaticFilterRequestWrapping
Passed
Passed
Failure
Passed
Passed
Passed
wrapping testRequestWrapping
Passed
Passed
Passed
Passed
Passed
Passed

As can be see we now have 3 perfectly scoring servers; Payara, WildFly and Tomcat now pass every test being thrown at them. All 3 do this for the first time, and the slightly older versions of each of them contain a variety of (small) bugs that prevents a perfect score.

Liberty still fails the CDI tests as before, but there's a hack available that can let Liberty pass these. Since we test as much as possible out of the box here this hack was not applied. JACC propagation also doesn't work, but this is because there's no JACC provider available by default.

Perhaps the most surprising result of this round is that WebLogic is not longer completely broken when it comes to JASPIC. While WebLogic 12.1.x had decent support for JASPIC, a major regression was introduced in 12.2.1.0 were the basic cases didn't work properly anymore. When the basic cases fail it's almost pointless to continue testing since in practice when an authentication itself doesn't work anymore it doesn't matter so much whether say request wrapping is supported or not. WebLogic does have major problems with the "register session" feature (which likely caused the regression with basic authentication in the first place), doesn't support setting a custom principal (a major feature of JASPIC), and has problems with propagating to EJB. WebLogic fails the same CDI and JACC tests as Liberty does. Contrary to Liberty, WebLogic -does- have a default JACC provider but it's not activated out of the box.

TomEE performs very well, but has a small hick up when an injected request is used in a forwarded resource. It also fails the JACC propagation, but this is because TomEE doesn't implement JACC at all for the Web module (only for EJB).

An important additional aspect not shown in the test table above is whether JASPIC works out of the box (like e.g. a Servlet Filter does), or whether it needs (server specific) activation of some sort.

In this regard, Liberty, WebLogic, TomEE and Tomcat need zero activation. There's a small caveat with Liberty and that's if you use the server specific group to role mapping in combination with a JASPIC SAM, all role checking fails. This is a somewhat peculiar incompatibility. All other servers don't care whether the groups that are set come from a portable JASPIC SAM or from something proprietary when doing the server specific/proprietary group to role mapping.

Payara and WildFly both need an activation of some kind. Payara needs a glassfish-web.xml where default group to role mapping is activated (or alternatively, where groups are actually mapped to roles), while WildFly needs a jboss-web.xml where the "jaspitest" security domain is set. WildFly should eventually lift this mandated activation when the new Elytron security system is introduced, which could possibly happen in WildFly 11. Payara could eventually default to default group to role mapping. WebLogic made this move before, but this is somewhat of a major change that should be done carefully if indeed planned.

Conclusion

Three servers, namely WildFly 10.1, Payara 164 and Tomcat 8.5.6 now pass all tests, but two of them (WildFly and Payara) need some kind of activation so are not perfect-perfect, but still very, very close to it. TomEE performs very well too, with only a minor regression and all the major core authentication functionality working perfectly.

Liberty and WebLogic have a bit more work left to be done, where as core features are concerned Liberty fails the request/response wrapping partially, while WebLogic fails the custom principal, register session and EJB propagation.

In a next article we'll be looking at what things look like after we applied the CDI/Weld hack for Liberty and Weblogic, and if JACC propagation works on them when we install/activate a JACC provider.

Arjan Tijms

Monday, August 29, 2016

Custom authorization rules on IBM Liberty

Last month we presented a way how a Java EE application can provide custom rules for authorization. The code shown in that article was developed and tested using Payara. We're now going to look at how the code can be used on some other servers, starting with IBM's Liberty.

Liberty has a highly modularised architecture and features a rather amazing way by which and end user can compose the runtime. By means of its server.xml file, a Liberty user can add or remove nearly every individual feature that Liberty has independently. The Liberty core system keeps track of the dependencies of each module representing such feature and adds or removes its dependencies accordingly (in a way not quite unlike what a tool like Maven does at build time).

All this power does come at some price. For Liberty the JACC provider which is needed to implement the logic behind the custom authorization rules has to be turned into a Liberty specific module (called a user feature), which is quite a bit more work than needed for other servers (even the ones that are also modular).

Summarised we have to:

  • Create an OSGi bundle
  • Add our JACC provider jar to this bundle
  • Implement a Liberty specific service that returns the policy and factory instances from our provider
  • Make sure we have the right imports and exports
  • Create a Liberty Feature that references the previously created bundle and provides the right exports
  • Installing the feature in Liberty

IBM provides documentation on how to install a JACC provider within Liberty, but unfortunately the documentation is not entirely clear, and even less fortunately it's not fully correct.

As in some of the other documents from IBM the JACC installation has some very terse instructions. They may be clear if you're a rather experienced Liberty and/or OSGi user, but if you're not a sentence like the following might be a bit puzzling:

Package the component into an OSGi bundle that is part of your user feature, along with your JACC provider.
The above is more a summary of what we have to do, not how to actually do it. We look at the how in some more detail below:

Create an OSGi bundle

Luckily IBM also has instructions for a "hello world" user feature, which are much more complete and easier to follow. Not so lucky was that after creating an "OSGI Bundle Project" as indicated by those instructions I got a compile error right away in the project. For some unfathomable reason the package org.osgi.framework could not be found. Eventually it appeared that next to a target runtime (which was set to the installed Liberty server), Eclipse also has the notion of a target platform. The first one normally provides the packages a project compiles against, but in an OSGi project things are never that simple. The target platform changes automatically when you set the target runtime for your project, even though for former is global for your entire workspace and the latter is per project.

The target platform can be set in Eclipse in Preferences -> Plug-in Development -> Target Platform. See the screenshot below:

Switching to "Running platform" solved the compile error for org.osgi.framework, but the IBM specific bundles couldn't be resolved then. Switching to "Liberty beta 2016.8 with SPI" solved the problem, but later on I could switch back to "Liberty beta 2016.8" without the compile error coming back. Eclipse sometimes works in mysterious ways.

Add our JACC provider jar to this bundle

Not mentioned anywhere in the IBM documentation, but the existing JACC provider jar had to be copied into the BundleContent folder of the OSGi bundle project. The Eclipse manifest editor (runtime tab -> classpath pane) for some reason doesn't let you pick from this location, but it can be easily added directly to the MANIFEST.FM file by adding the following to it:

Bundle-ClassPath: .,
 cdi-jacc-provider-0.1-SNAPSHOT.jar
After the JACC provider jar has been added in this way, the manifest editor shows the jar, but it will not have been added to the classpath of the project itself. This has to be done separately by going to project -> properties -> Java Build Path -> Libraries and adding the jar there too. See the screenshot below:

Implement a Liberty specific service that returns the policy and factory instances from our provider

Contrary to all other servers, Liberty requires an amount of Liberty specific code in the module. The above mentioned instructions give an example of this, but unfortunately that code for a required service seemed to be rather faulty. Among others several imports were missing, the "getPolicyConfigurationFactory" method name was wrong (should be "getPolicyConfigFactory"), the return type of that same method was wrong (instead of "Policy" it should be "PolicyConfigurationFactory", then there's an active method that used a variable called "configprops" that's not defined anywhere, etc.

Additionally, the service uses the @Component annotation, which is said to generally make things easier, but I wasn't able to use it. After importing the right package for it, Liberty kept logging the following error when starting up:

[ERROR ] CWWKE0702E: Could not resolve module: JaccBundle [250] Unresolved requirement: Import-Package: org.osgi.service.component.annotations; version="1.3.0"

Being unable to resolve this, I removed the annotation and implemented a traditional Activator instead. The code for both the service and the activator is shown below:

Service:

package jaccbundle;

import static java.lang.System.setProperty;
import static java.lang.Thread.currentThread;

import java.security.Policy;

import javax.security.jacc.PolicyConfigurationFactory;

import org.omnifaces.jaccprovider.jacc.policy.DefaultPolicy;

import com.ibm.wsspi.security.authorization.jacc.ProviderService;

public class MyJaccProviderService implements ProviderService {
    
    @Override
    public Policy getPolicy() {
        return new DefaultPolicy();
    }

    @Override
    public PolicyConfigurationFactory getPolicyConfigFactory() {
        
        ClassLoader originalContextClassloader = 
            currentThread().getContextClassLoader();
        try {
            currentThread()
              .setContextClassLoader(
                      this.getClass().getClassLoader());
            
            setProperty(
                "javax.security.jacc.PolicyConfigurationFactory.provider",
                "org.omnifaces.jaccprovider.jacc.configuration.TestPolicyConfigurationFactory");
            
            return PolicyConfigurationFactory.getPolicyConfigurationFactory();
        } catch (Exception e) {
            return null;
        } finally {
            Thread.currentThread()
                  .setContextClassLoader(
                      originalContextClassloader);
        }
    }
}

Activator

package jaccbundle;

import static org.osgi.framework.Constants.SERVICE_PID;

import java.util.Hashtable;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;

import com.ibm.wsspi.security.authorization.jacc.ProviderService;

public class Activator implements BundleActivator {
    
    private static final String CONFIG_PID = "jaccBundle";
    private ServiceRegistration<ProviderService> jaccProviderService;

    public void start(BundleContext context) throws Exception {
        jaccProviderService = context.registerService(
            ProviderService.class, 
            new MyJaccProviderService(), 
            getDefaults());
    }

    public void stop(BundleContext context) throws Exception {
        if (jaccProviderService != null) {
            jaccProviderService.unregister();
            jaccProviderService = null;
        }
    }

    Hashtable<String, ?> getDefaults() {
        Hashtable<String, String> defaults = new Hashtable<>();
        defaults.put(SERVICE_PID, CONFIG_PID);
        return defaults;
    }

}

Make sure we have the right imports and exports

The Eclipse manifest editor offers a handy "Analyze code and add dependencies to the MANIFEST.FM" feature, but like the matter duplicator it doesn't work and has never worked. They can be added manually though. In the editor it's Dependencies -> Imported packages for the imports, and Runtime -> Exported packages for the exports. See below:

After some trial and error the MANIFEST.FM eventually looked like this:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: JaccBundle
Bundle-SymbolicName: JaccBundle
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: jaccbundle.Activator
Bundle-ClassPath: .,
 cdi-jacc-provider-0.1-SNAPSHOT.jar
Import-Package: com.ibm.wsspi.security.authorization.jacc;version="1.0.0",
 javax.enterprise.context;version="1.1.0",
 javax.enterprise.context.spi;version="1.1.0",
 javax.enterprise.inject;version="1.1.0",
 javax.enterprise.inject.spi;version="1.1.0",
 javax.inject;version="1.0.0",
 javax.naming,
 javax.security.auth,
 javax.security.jacc;version="1.5.0",
 org.osgi.framework
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Export-Package: jaccbundle,
 org.omnifaces.jaccprovider.cdi,
 org.omnifaces.jaccprovider.jacc

Create a Liberty Feature that references the previously created bundle and provides the right exports

After having created the Liberty Feature project using the IBM instruction for the hello world feature, there's relatively little to be done here. We do have to add the exports that we already exported from the bundle again here.

This can be done via the editor for the one and only file in our feature project called SUBSYSTEM.FM:

The raw file will then look as follows:
Subsystem-ManifestVersion: 1.0
IBM-Feature-Version: 2
IBM-ShortName: JaccFeature
Subsystem-SymbolicName: JaccFeature;visibility:=public
Subsystem-Version: 1.0.0.qualifier
Subsystem-Type: osgi.subsystem.feature
Subsystem-Content: JaccBundle;version="1.0.0"
Manifest-Version: 1.0
IBM-API-Package: org.omnifaces.jaccprovider.cdi,
 org.omnifaces.jaccprovider.jacc
The IBM JACC installation instructions mentioned one other point here:
Ensure that your feature includes the OSGi subsystem content: com.ibm.ws.javaee.jacc.1.5; location:="dev/api/spec/".
I could however not get this to work. Adding it to SUBSYSTEM.FM as follows:
Subsystem-Content: JaccBundle;version="1.0.0",com.ibm.ws.javaee.jacc.1.5;location:="dev/api/spec/"
Resulted in the following error when starting Liberty:
Bundle location:="dev/api/spec/" cannot be 
 resolved
Adding it as:
Subsystem-Content: JaccBundle;version="1.0.0",com.ibm.ws.javaee.jacc.1.5
Yielded the following error:
[ERROR   ] CWWKE0702E: Could not resolve module: com.ibm.ws.javaee.jacc.1.5 [251]
  Another singleton bundle selected: osgi.identity; osgi.identity="com.ibm.ws.javaee.jacc.1.5"; type="osgi.bundle"; version:Version="1.0.13.20160722-1732"; singleton:="true"
Without this directive the JACC provider already worked as expected, so I just left it out. It's intended use is somewhat puzzling though.

The workspace with all artefacts combined for the two required projects looks as follows:

Installing the feature in Liberty

As explained in the hello world instructions by IBM, the feature with the embedded bundle with the embedded JACC provider jar can be installed via the Feature project by right clicking on it in Eclipse and selecting "Install Feature". After that is done, the final step is to add the new feature to Liberty's server.xml file, for which there again is a graphical editor in Eclipse:

The raw file look as follows:

<server description="Liberty beta">

    <featureManager>
        <feature>javaee-7.0</feature>
        <feature>localConnector-1.0</feature>
        <feature>usr:JaccFeature</feature>
    </featureManager>

    <httpEndpoint httpPort="8080" httpsPort="9443" id="defaultHttpEndpoint"/>
    
    <webContainer deferServletLoad="false"/>
    <ejbContainer startEJBsAtAppStart="true"/>

    <applicationManager autoExpand="true"/>
    <applicationMonitor updateTrigger="mbean"/>

</server>

After this we can finally run a web application to test things, and it indeed seems to work as expected!

One thing to keep in mind is that Liberty initialises the CDI request scope rather late (after authentication has been done). For some of the calls to our custom JACC provider this is way too late. Fortunately, CDI *does* work, but only for scopes that don't depend on the current request (like the application scope).

The authorization mechanism as demonstrated in the previous article most naturally uses the application scope anyway, so here that's no problem. For the brave, the request scope for CDI can forcefully and in a hacky way be activated earlier (e.g. in a JASPIC SAM) as follows:

Object weldInitialListener = request.getServletContext().getAttribute("org.jboss.weld.servlet.WeldInitialListener");
ServletRequestEvent event = new ServletRequestEvent(request.getServletContext(), request);
        
ELProcessor elProcessor = new ELProcessor();
elProcessor.defineBean("weldInitialListener", weldInitialListener);
elProcessor.defineBean("event", event);
elProcessor.eval("weldInitialListener.requestInitialized(event)");
Note again that this is a hack, and as such not guaranteed to work without any problems or keep working on newer versions of Liberty. It was only briefly tested on Liberty Beta 2016.8

Conclusion

Installing a JACC provider on Liberty is a lot of work. The vendor documentation is not clear on this and even contains errors. After a user feature has been created it's relatively easy though to add it to Liberty. Publishing it to a repository (not discussed in the article) would make it even easier to use the JACC provider on other instances of Liberty.

The amount of work that's required and the need to dive into vendor specific documentation could largely be eliminated by having a Java EE standard way to add a JACC provider from within an application (like what is currently possible for JASPIC SAMs). Hopefully a JACC MR will consider this.

Regardless, with the JACC provider installed once Liberty users can from then on easily add custom authorization rules to their applications. Care should be taken though that the JACC provider is just a POC. It shows great promise, but has not been extensively tested yet.

Arjan Tijms

Saturday, July 23, 2016

Simplified custom authorization rules in Java EE

In a previous article we looked at implementing a Java EE authorization module using the JACC specification. This module implemented the default authorization rules as specified by the JACC-, Servlet- and EJB specifications. In this article we go beyond that default algorithm and take a look at providing our own custom authorization rules.

In order to implement custom rules, one would traditionally ship an entire JACC provider with factory, configuration and policy. Not only is this a lot of code (JACC doesn't have any code that can be reused, for the smallest change everything needs to be implemented from scratch), it's also problematic that a JACC provider is global for the entire application server, while authorization rules are almost always specific for an individual application. Even when you adhere to the best practice of using one application per server, it's still quite a hassle to re-install the JACC provider after every little change separately from the application.

For this article we're therefor going to employ a different approach; use CDI to delegate from a single JACC provider that's installed once, to an application specific CDI bean. Incidentally this is largely the same thing Soteria/JSR 375 is doing for authentication mechanisms, which the key difference that the Java EE authentication SPI -does- allow authentication modules to be installed per application.

For the actual custom authorization rule we took inspiration from a rather interesting question and answer on StackOverflow:

Is it possible to determine group membership of a user on demand instead of when logging in via a ServerAuthModule (JASPIC)?

As it appears, the answer to this question is yes ;)

In order to come to a solution for the above stated problem we first took the original JACC provider from the previous article and refactored it a little. The permissions that were previously put in the intermediate base class TestPolicyConfigurationPermissions were factored out to a separate struct like class:

public class SecurityConstraints {

    private Permissions excludedPermissions = new Permissions();
    private Permissions uncheckedPermissions = new Permissions();
    private Map<String, Permissions> perRolePermissions = new HashMap<String, Permissions>();

    // + getters/setters
Furthermore a new class was introduced that holds the caller (name) principal, the (mapped) roles and the raw set of unmapped principals (which are often server specific):
public class Caller {

    private Principal callerPrincipal;
    private List<String> roles;
    private List<Principal> unmappedPrincipals;

    // + getters/setters
Next we define an interface for the application to implement. Our JACC provider will call this at certain points during the authorization process:
public interface AuthorizationMechanism {
    
    default Boolean preAuthenticatePreAuthorize(Permission requestedPermission, SecurityConstraints securityConstraints) {
        return null;
    }
    
    default Boolean preAuthenticatePreAuthorizeByRole(Permission requestedPermission, SecurityConstraints securityConstraints) {
        return null;
    }

    default Boolean postAuthenticatePreAuthorize(Permission requestedPermission, Caller caller, SecurityConstraints securityConstraints) {
        return null;
    }
    
    default Boolean postAuthenticatePreAuthorizeByRole(Permission requestedPermission, Caller caller, SecurityConstraints securityConstraints) {
        return null;
    }
}
As can be seen we distinguish for authorization decisions before authentication has taken place and thereafter, and for those at the start of an authorization decision and right before it comes time to check for role based permissions. The difference between those last two moments is that for the latter the tests for excluded permissions (those granted to no one) and unchecked (permissions granted to everyone) have already been performed. (Note that methods have been used for now, but perhaps events are the better solution here)

With these artefacts in place we can now modify the JACC Policy class to request a bean via CDI that implements the AuthorizationMechanism interface, and if it exists call one of the appropriate methods. The following shows an excerpt of this:

boolean postAuthenticate = domain.getPrincipals().length > 0;

AuthorizationMechanism mechanism = getBeanReferenceExtra(AuthorizationMechanism.class);
Caller caller = null;

if (postAuthenticate) {
    caller = new Caller(
        roleMapper.getCallerPrincipalFromPrincipals(currentUserPrincipals),
        roleMapper.getMappedRolesFromPrincipals(currentUserPrincipals),
        currentUserPrincipals);
}

if (mechanism != null) {
    Boolean authorizationOutcome = postAuthenticate? 
        mechanism.postAuthenticatePreAuthorize(requestedPermission, caller, securityConstraints) :
        mechanism.preAuthenticatePreAuthorize(requestedPermission, securityConstraints);
    
    if (authorizationOutcome != null) {
        return authorizationOutcome;
    }
}

In the code above getBeanReferenceExtra uses CDI to fetch a bean of type AuthorizationMechanism. This method works around a bug in Payara where the so-called component namespaces aren't being set. This bug is pretty much the same as also existed for calling a JASPIC SAM. If the mechanism is not null, then depending on whether authentication has already happened or not either the postAuthenticatePreAuthorize or the preAuthenticatePreAuthorize is called.

The JACC Policy doesn't tell us explicitly if the call is before or after authentication, but we can deduct this by looking at the passed-in principals. The assumption is that before authentication there are no principals at all, and after authentication there's at least one (if there was no actual authentication being done, the so-called "unauthenticated caller principal" is added).

For further details see the full source code.

With the JACC provider in place we can now create a Java EE web application that takes advantage of it. We'll start with a custom JSR 375 IdentityStore that only authenticates a single user named "test" and doesn't return any groups:

@RequestScoped
public class CustomIdentityStore implements IdentityStore {
      
    public CredentialValidationResult validate(UsernamePasswordCredential usernamePasswordCredential) {
        
        if (usernamePasswordCredential.getCaller().equals("test") && 
            usernamePasswordCredential.getPassword().compareTo("secret1")) {
            
            return new CredentialValidationResult(
                new CallerPrincipal("test"), 
                null // no static groups, dynamically handled via authorization mechanism 
            );
        }
        
        return INVALID_RESULT;
    }
    
}
We also add a custom JSR 375 HttpAuthenticationMechanism, one that's just used for testing purposes:
@RequestScoped
public class CustomAuthenticationMechanism implements HttpAuthenticationMechanism {
    
    @Inject
    private IdentityStore identityStore;

    @Override
    public AuthStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext) throws AuthException {

        if (request.getParameter("name") != null && request.getParameter("password") != null) {

            CredentialValidationResult result = identityStore.validate(
                new UsernamePasswordCredential(request.getParameter("name"), request.getParameter("password")));

            if (result.getStatus() == VALID) {
                return httpMessageContext.notifyContainerAboutLogin(
                    result.getCallerPrincipal(), result.getCallerGroups());
            } else {
                throw new AuthException("Login failed");
            }
        } 

        return httpMessageContext.doNothing();
    }
    
}
Note that we could have omitted both the custom identity store and custom authentication mechanism and instead configured the default embedded store and the default BASIC authentication mechanism, but now we more clearly see what's exactly happening in the authentication process. Also note that the HttpAuthenticationMechanism is a CDI based HTTP specific variant of the ServerAuthModule that was mentioned in the SO question cited above.

Let's now finally look at our custom authorization rule, which basically looks as follows:

@ApplicationScoped
public class CustomAuthorizationMechanism implements AuthorizationMechanism {
   
    @Override
    public Boolean postAuthenticatePreAuthorizeByRole(Permission requestedPermission, Caller caller, SecurityConstraints securityConstraints) {
        
        return getRequiredRoles(securityConstraints.getPerRolePermissions(), requestedPermission)
                .stream()
                .anyMatch(role -> isInRole(caller.getCallerPrincipal().getName(), role));
    }
}
What's happening here is that our custom code is being asked to authorize the caller for some requested permission. Such permission can e.g. be a WebResourcePermission for a given protected path like /foo/bar.

In order to do the on demand membership check as was asked for in the SO question we start with using the getRequiredRoles method and the collected SecurityConstraints to obtain a list of roles that are required for the requested permission. We then look if the current caller is in any of those roles using the isInRole method. This method can do whatever backend lookup is needed, but for an example application we'll mock it using a simple map where we put caller "test" in roles "foo", "bar" and "kaz".

For completeness, the getRequiredRoles method gets the roles that are required for a requested permission as follows:

return perRolePermissions
            .entrySet().stream()
            .filter(entry -> entry.getValue().implies(requestedPermission))
            .map(e -> e.getKey())
            .collect(toList());
In order to test if the authorization rule works we add a protected Servlet as follows:
@DeclareRoles({ "foo", "bar", "kaz" })
@WebServlet("/protected/servlet")
@ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
public class ProtectedServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.getWriter().write("This is a protected servlet \n");

        String webName = null;
        if (request.getUserPrincipal() != null) {
            webName = request.getUserPrincipal().getName();
        }

        response.getWriter().write("web username: " + webName + "\n");

        response.getWriter().write("web user has role \"foo\": " + request.isUserInRole("foo") + "\n");
        response.getWriter().write("web user has role \"bar\": " + request.isUserInRole("bar") + "\n");
        response.getWriter().write("web user has role \"kaz\": " + request.isUserInRole("kaz") + "\n");
    }

}
The Servlet shown here is protected by the role "foo", so with the above given IdentityStore which doesn't return any groups/roles for our caller "test", we would normally not be able to access this Servlet. Yet, with the custom authorization rule in place we're able to access this Servlet just fine when authenticating as user "test".

Next we added a preAuthenticatePreAuthorize method to our authorization mechanism with some logging and also added logging to our existing postAuthenticatePreAuthorizeByRole method. We then deployed the application as "custom-authorization" to Payara 4.1.1.162 and requested http://localhost:8080/custom-authorization/protected/servlet?name=test&password=secret1. This resulted in the following log entries:

preAuthenticatePreAuthorize called. Requested permission: ("javax.security.jacc.WebUserDataPermission" "/protected/servlet" "GET")
preAuthenticatePreAuthorize called. Requested permission: ("javax.security.jacc.WebResourcePermission" "/protected/servlet" "GET")

validateRequest called. Authentication mandatory: true

postAuthenticatePreAuthorizeByRole called. Requested permission: ("javax.security.jacc.WebResourcePermission" "/protected/servlet" "GET")

postAuthenticatePreAuthorizeByRole called. Requested permission: ("javax.security.jacc.WebRoleRefPermission" "org.omnifaces.authorization.ProtectedServlet" "foo")
postAuthenticatePreAuthorizeByRole called. Requested permission: ("javax.security.jacc.WebRoleRefPermission" "org.omnifaces.authorization.ProtectedServlet" "bar")
postAuthenticatePreAuthorizeByRole called. Requested permission: ("javax.security.jacc.WebRoleRefPermission" "org.omnifaces.authorization.ProtectedServlet" "kaz")
What we see happening here is that our authorization mechanism is initially called twice before authentication. First to see if the request can be aborted early by checking if the protocol (http/https) is allowed, and secondly to see if the resource is allowed to be accessed publicly. If the resource is allowed to be accessed publicly, authentication is still being asked for, but it's not mandatory then.

For the request to "/protected/servlet" authentication is mandatory as shown above. Since authentication is thus mandatory, our authorization mechanism is called again after authentication for the same requested WebResourcePermission permission, but this time the caller data corresponding to the authenticated identity is also available. Our method will return "true" here, since ("javax.security.jacc.WebResourcePermission" "/protected/servlet" "GET") will turn out to require the role "foo", and our mock "isInRole" method will return "true" for caller "test" and role "foo".

We therefor continue to our requested resource, which means the servlet will be invoked.

Interesting to note here is that HttpServletRequest#isUserInRole is not implemented internally by checking a simple list of roles, but by calling our authorization module with a requested permission ("javax.security.jacc.WebRoleRefPermission" "org.omnifaces.authorization.ProtectedServlet" "[role name]"). The three isUserInRole calls that the servlet shown above does therefor show up in the log as three calls to our authorization mechanism, each for a different role.

Also interesting to note is that our authorization mechanism handles a requested permission to access "/protected/servlet" and a requested permission for isUserInRole("foo") completely identical. If necessary the authorization mechanism can of course inspect the requested permission, but for this use case that wasn't necessary.

To contrast the call to the protected servlet, let's also take a quick look at the log entries resulting from a call to a public servlet that's otherwise identical to the protected one:

preAuthenticatePreAuthorize called. Requested permission: ("javax.security.jacc.WebUserDataPermission" "/public/servlet" "GET")
preAuthenticatePreAuthorize called. Requested permission: ("javax.security.jacc.WebResourcePermission" "/public/servlet" "GET")

validateRequest called. Authentication mandatory: false

postAuthenticatePreAuthorizeByRole called. Requested permission: ("javax.security.jacc.WebRoleRefPermission" "org.omnifaces.authorization.PublicServlet" "foo")
postAuthenticatePreAuthorizeByRole called. Requested permission: ("javax.security.jacc.WebRoleRefPermission" "org.omnifaces.authorization.PublicServlet" "bar")
postAuthenticatePreAuthorizeByRole called. Requested permission: ("javax.security.jacc.WebRoleRefPermission" "org.omnifaces.authorization.PublicServlet" "kaz")
What we see here is that our authorization mechanism is initially called twice again, but since the default authorization algorithm will grant access to the anonymous caller the subsequent authentication is not mandatory and our authorization mechanism will also not be called a second time for the same resource permission.

For more details see the full source code of the example application.

Conclusion

We've seen that custom authorization rules allow us to do things that are normally not thought possible using Java EE security. Java EE's security model is clearly much more advanced than just the basic ability to check for roles, but much of its power has likely been untapped by many.

The reason for this is the relative obscurity of the JACC specification, the difficulty to install a provider and the very large amount of work required for even the smallest amount of custom authorization logic. The approach presented in this article allows us to re-use an existing JACC provider and provide custom authorization logic with a minimal amount of code.

What we presented here is only a prototype, but it may serve as a basis for authorization in the Java EE Security API (JSR 375).

Arjan Tijms