Question

I am trying to design an app that is based on microservice architecture. Backend is written in JavaEE (micro profile, not Spring Boot), while for front-end I would use Angular5.

Now I am wondering how I could implement an authentication which would require user to present a valid X.509 certificate (.p12 file) signed with my self-signed CA. After user presents his certificate, I would ask from him his password (stored in database), while I would read his e-mail from certificate and log him in based on those two inputs. If user is correctly authenticated, I would send him JWT token back, with which he would access data on my angular pages.

I don't know how to begin with such implementation, is any of it done on client side, or is everything handled on server side and if it is, how since my backend is only REST API.

I am looking for general idea how to implement this and if possible some literature that is addressing this type of requirement.

I have various micro services which perform logic of my application and I would like to support different types of users: regular users, moderators and admins. Each microservice can have different access rules. (ie. Option to retrieve all data is available to everyone, option to retrieve one particular data is only for registered users, while option to create new data is available only to mods and admins.). I want to achieve that regular users login only using their email and password (both stored in DB), I have no problems with that, however I would want that mods and admins need to present x509 certificate in order to login. In both cases I would then generate JWT token and send it back to user (in token I would write their role, which would determine whether they can perform an action or not.)

I am not as interested in implementation as I am in overall workflow my app should support in order to implement such thing. I can figure out the each detail later, however I am not sure how would I proceed in REST/Angular way. I have idea how to do it with server rendering app, but now I want to migrate to client rendering.

Was it helpful?

Solution

This probably belongs on stackoverflow but here's a fairly minimal implementation of retrieving the client-cert using JAX-RS filters:

import java.io.IOException;
import java.security.Principal;
import java.security.cert.X509Certificate;

import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.ext.Provider;

@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements javax.ws.rs.container.ContainerRequestFilter
{
  @Context
  private ResourceInfo resourceInfo;

  private static final ResponseBuilder ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED).entity("User not authenticated");
  private static final ResponseBuilder ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN).entity("Access denied");

  @Override
  public void filter(ContainerRequestContext request) throws IOException
  {
    Principal principal = getPrincipal(request);

    if (principal == null) {
      abortAccessDenied(request);
      return;
    }

    User user = new User(principal);

    request.setSecurityContext(new ReportsSecurityContext(user));

    if (!user.isAllowed(request)) {
      abortNotAllowed(request);
    }
  }

  private void abortNotAllowed(ContainerRequestContext request)
  {
    request.abortWith(ACCESS_FORBIDDEN.build());
  }

  private void abortAccessDenied(ContainerRequestContext request)
  {
    request.abortWith(ACCESS_DENIED.build());
  }

  private static Principal getPrincipal(ContainerRequestContext requestContext) throws IOException
  {
    X509Certificate[] certificates = (X509Certificate[]) requestContext.getProperty("javax.servlet.request.X509Certificate");

    if (certificates != null && certificates.length > 0) {
      return certificates[0].getSubjectX500Principal();
    } else {
      return null;
    }
  }
}

User is a custom type and you'll need to register the filter. You will also need to make sure your self-signed CA cert is in your truststore.

You'll also need to configure your SSL to use client auths. I use embedded Jetty and the setWantClientAuth(true) or setNeedClientAuth(true) on the SslContextFactory.

Licensed under: CC-BY-SA with attribution
scroll top