JBoss AS7: securing subsystem web applications

In JBoss AS7 it is possible for extension subsystems to publish web applications programmatically (i.e. without going through the deployer). This, for example, is how the default "welcome" web app in AS7 works (look at web/src/main/java/org/jboss/as/web/WelcomeContextService.java and web/src/main/java/org/jboss/as/web/WelcomeContextConsoleServlet.java in the AS7 source to see how it is done).

 

Using the org.apache.catalina.core.StandardContext API it is possible to configure all the aspects that are accessible via the usual web.xml declarative configuration, including security constraints and roles. The class that wires all that configuration into the container is org.apache.catalina.startup.ContextConfig which needs to be added to the context as a lifecycle listener. Unfortunately, in JBossWeb, that class has been changed not to hook up the authenticators. Instead, a specialized org.jboss.as.web.deployment.JBossContextConfig has to be used. JBossContextConfig however requires a DeploymentUnit, a container for metadata collected from web.xml, jboss-web.xml and annotations, and setting it up is non-trivial (look at the webservices subsystem for an example of a dynamically generated DeploymentUnit based on JAXWS annotations).

 

An alternative solution is to use the following subclass of the default ContextConfig:

 

 

import org.apache.catalina.startup.ContextConfig;
import org.jboss.as.web.WebLogger;

public class SecureContextConfig extends ContextConfig {
   @Override
   protected void completeConfig() {
      if (ok) {
         resolveServletSecurity();
      }
      if (ok) {
         validateSecurityRoles();
      }
      // Configure an authenticator if we need one
      if (ok) {
         authenticatorConfig();
      }
      // Make our application unavailable if problems were encountered
      if (!ok) {
         WebLogger.WEB_LOGGER.unavailable(context.getName());
         context.setConfigured(false);
      }
   }
}

 

 

Together with the attached SecurityContext custom valve you can then setup your context's security as follows (I'm skipping all context configuration related to docbase, servlets, etc and focusing only on the security bits):

 

 

      StandardContext context = new StandardContext();
      context.addLifecycleListener(new SecureContextConfig());

      SecurityConstraint constraint = new SecurityConstraint();
      SecurityCollection webCollection = new SecurityCollection();
      webCollection.addPattern("/*");
      webCollection.addMethod("GET");
      constraint.addCollection(webCollection);
      constraint.setAuthConstraint(true);
      constraint.addAuthRole("MyRole");
      context.addConstraint(constraint);
      LoginConfig login = new LoginConfig();
      login.setAuthMethod("BASIC");
      login.setRealmName("ApplicationRealm");
      context.setLoginConfig(login);
      JBossWebRealm realm = new JBossWebRealm();
      SecurityDomainContext securityDomainContext = securityDomainContextInjector.getValue();
      realm.setAuthenticationManager(securityDomainContext.getAuthenticationManager());
      realm.setAuthorizationManager(securityDomainContext.getAuthorizationManager());
      realm.setMappingManager(securityDomainContext.getMappingManager());
      realm.setAuditManager(securityDomainContext.getAuditManager());
      context.setRealm(realm);
      context.addValve(new SecurityContext("/contextPath", securityDomain));

 

 

Don't forget to add the required security domain as a dependency to your service when constructing your ServiceBuilder

 

builder.addDependency(
               SecurityDomainService.SERVICE_NAME.append(securityDomain),
               SecurityDomainContext.class,
               service.getSecurityDomainContextInjector()
);

 

 

Out of the box, the authenticator will automatically support BASIC, FORM, DIGEST and CLIENT-CERT. If you need SPNEGO, add the following valve

 

 

if("SPNEGO".equals(authMethod)) {
         context.addValve(new NegotiationAuthenticator());
}