0

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.

closed as too broad by gnat, Andres F., 8bittree, Laiv, amon Feb 6 '18 at 12:21

Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 1
    see Where to start? – gnat Jan 31 '18 at 4:30
  • You need to use optional client-cert authentication (see setWantClientAuth in my answer). If the user doesn't supply one, then you don't let them login as an admin. – JimmyJames Jan 31 '18 at 14:51
1

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.

  • this is one part of what i am interested in, thanks, however I am more interested in how i should design an app so that it will support what it want. I made some edit to question, hopefully to make it more clear. – Miha Jamsek Jan 31 '18 at 12:37
  • Just one more thing: Is this filter automatically applied to all jax rs sources or do i have to manually set for each method? As i don't want this done at every request, just on one specific. – Miha Jamsek Feb 1 '18 at 20:19
  • @MihaJamsek In Jetty, it's set at the SSLContextFactory. I've only used a single one of these instances in an application. You might be able to split the traffic up across multiple instances but I'm not sure that works and I probably wouldn't go that way. An easier approach is to look at the Request in the filter and if it's not a path you are concerned with, return early. – JimmyJames Feb 1 '18 at 21:14

Not the answer you're looking for? Browse other questions tagged or ask your own question.