Although Elytron was developed for the WildFly application server, it is possible to use Elytron outside of WildFly. This blog post is going to give an overview of how to secure an embedded Jetty server using Elytron. In particular, we’re going to take a look at an example embedded Jetty application that makes use of HTTP Basic authentication and we’re going to modify it so that it’s backed by Elytron for its security.

 

Simple embedded Jetty application

 

Let’s take a look at a simple embedded Jetty server application:

 

public static void main(String[] args) throws Exception {

    // Create the Jetty server instance
    Server server = new Server(8080);

    ConstraintSecurityHandler security = new ConstraintSecurityHandler();
    server.setHandler(security);

    // Create a constraint that specifies that accessing "/secured" requires authentication
    // and the authenticated user must have "admin" role
    Constraint constraint = new Constraint();
    constraint.setName("auth");
    constraint.setAuthenticate(true);
    constraint.setRoles(new String[]{"admin"});
    ConstraintMapping mapping = new ConstraintMapping();
    mapping.setPathSpec("/secured");
    mapping.setConstraint(constraint);
    security.setConstraintMappings(Collections.singletonList(mapping));

    // Security realm configuration
    // alice: alice123+,admin,employee
    // bob: bob123+,employee
    LoginService loginService = new HashLoginService("MyRealm", "src/test/resources/realm.properties");
    server.addBean(loginService);
    security.setLoginService(loginService);

    // Use Jetty's BasicAuthenticator
    security.setAuthenticator(new BasicAuthenticator());

    // Configure the handler we are securing
    ServletHandler servletHandler = new ServletHandler();
    servletHandler.addServletWithMapping(SecuredServlet.class, "/secured");
    security.setHandler(servletHandler);

    server.start();
}

 

The above example first creates a Jetty server instance. It then creates a constraint that specifies that accessing the /secured page requires authentication and that the authenticated user must have admin role. Next, it configures a Jetty security realm that’s backed by a properties file. It then specifies that Jetty’s BasicAuthenticator should be used to handle authentication. It also specifies the ServletHandler that’s being secured. This handler is configured with a simple servlet that just outputs the username of the authenticated user. Finally, the Jetty server instance is started.

 

Switching to Elytron

 

Let’s modify our simple Jetty application so that it’s backed by Elytron for its security. To do this, we're going to focus on the security related parts of the example, namely the security realm configuration, the Authenticator configuration, and the ServletHandler configuration. The code used in this example can be found here.

 

Dependencies

 

There are two key dependencies that are needed:

 

  • org.wildfly.security:wildfly-elytron - This provides the WildFly Elytron security framework.
  • org.wildfly.security.elytron-web:jetty-server - This provides classes for integrating Elytron based HTTP authentication with Jetty.

 

Creating an Elytron SecurityDomain

 

The first thing we’re going to do is create an Elytron security domain. We’re going to use a simple map backed security realm for this example but any type of Elytron security realm could be used here instead:

 

private static SecurityDomain createSecurityDomain() throws Exception {

        // Create an Elytron map-backed security realm
        SimpleMapBackedSecurityRealm simpleRealm = new SimpleMapBackedSecurityRealm(() -> new Provider[] { elytronProvider });
        Map<string, simplerealmentry=""> identityMap = new HashMap<>();

        // Add user alice
        identityMap.put("alice", new SimpleRealmEntry(getCredentialsForClearPassword("alice123+"), getAttributesForRoles("employee", "admin")));

        // Add user bob
        identityMap.put("bob", new SimpleRealmEntry(getCredentialsForClearPassword("bob123+"), getAttributesForRoles("employee")));

        simpleRealm.setIdentityMap(identityMap);

        // Add the map-backed security realm to a new security domain's list of realms
        SecurityDomain.Builder builder = SecurityDomain.builder()
                .addRealm("ExampleRealm", simpleRealm).build()
                .setPermissionMapper((principal, roles) -> PermissionVerifier.from(new LoginPermission()))
                .setDefaultRealmName("ExampleRealm");
        return builder.build();
}

 

Notice that our security domain has two users, alice and bob, with passwords alice123+ and bob123+, respectively. Note that alice has both employee and admin role and bob only has employee role.

 

Creating an ElytronAuthenticator

 

The next thing we’re going to do is create an ElytronAuthenticator instance. ElytronAuthenticator is a class from the elytron-web-jetty project that implements Jetty’s Authenticator interface. It is the class that will be used for validating a request. In particular, ElytronAuthenticator’s validateRequest method uses Elytron APIs to perform authentication for a request by making use of an Elytron SecurityDomain and an Elytron HttpAuthenticationFactory.

 

We’re first going to create an Elytron HttpServerAuthenticationMechanismFactory which provides the Elytron HTTP Basic authentication mechanism:

 

HttpServerAuthenticationMechanismFactory providerFactory = new SecurityProviderServerMechanismFactory(() -> new Provider[] {new WildFlyElytronProvider()});
HttpServerAuthenticationMechanismFactory httpServerMechanismFactory = new FilterServerMechanismFactory(providerFactory, true, "BASIC");

 

We can now create an ElytronAuthenticator instance using our createSecurityDomain() method and our newly created httpServerMechanismFactory:

 

private static ElytronAuthenticator createElytronAuthenticator() {
        SecurityDomain securityDomain = createSecurityDomain();
        return ElytronAuthenticator.builder()
                .setSecurityDomain(securityDomain)
                .setMechanismConfigurationSelector(MechanismConfigurationSelector.constantSelector(MechanismConfiguration.builder()
                        .addMechanismRealm(MechanismRealmConfiguration.builder().setRealmName("Elytron Realm").build())
                        .build()))
                .setFactory(httpServerMechanismFactory)
                .build();
}

 

We can then tweak the example application to specify that authentication should be handled by our ElytronAuthenticator instance:

 

ElytronAuthenticator elytronAuthenticator = createElytronAuthenticator();
security.setAuthenticator(elytronAuthenticator);

 

Wrapping the ServletHandler with an ElytronRunAsHandler

 

Recall that the ServletHandler in the example was configured with a simple secured servlet that just outputs the username of the authenticated user. We’re now going to wrap that handler with an ElytronRunAsHandler, as shown below. This class is also from the elytron-web-jetty project and is used to associate the security identity that was produced after successfully validating a request with the current thread.

 

ServletHandler servletHandler = new ServletHandler();
ElytronRunAsHandler elytronRunAsHandler = new ElytronRunAsHandler(servletHandler);
servletHandler.addServletWithMapping(SecuredServlet.class, "/secured");
security.setHandler(elytronRunAsHandler);

 

Now, in our secured servlet, we can use securityDomain.getCurrentSecurityIdentity() to get the current user and then we can use getPrincipal().getName() to output the username of that authenticated user:

 

public static class SecuredServlet extends HttpServlet {
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter writer = response.getWriter();
            writer.println("Hello " + securityDomain.getCurrentSecurityIdentity().getPrincipal().getName() + "! You've authenticated successfully using Elytron!");
        }
}

 

These are all the changes that are needed to modify our example embedded Jetty application so that it’s backed by Elytron for its security. The complete code for this example can be found here.

 

Running the modified example application

 

To build and run the modified example application, the following command can be used:

 

mvn clean install exec:exec

 

This will start the embedded Jetty server instance and it will be listening for requests on port 8080.

 

Now try accessing the example application using http://localhost:8080/secured.

 

First, try to log in as bob using password bob123+. Since accessing the /secured page requires admin role and since bob is not an admin, you'll see an HTTP 403 error.

 

Next, try to log in as alice using password alice123+. Since alice has admin role, you'll be able to successfully log in and will see the following message:

Hello alice! You've authenticated successfully using Elytron!

 

Summary

 

This blog post has shown how to secure an embedded Jetty server using Elytron. The classes for integrating Elytron based HTTP authentication with Jetty can be found in the elytron-web-jetty project.