Skip to content

Instantly share code, notes, and snippets.

@dblevins
Created March 12, 2015 06:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dblevins/211a45b893999748c7c4 to your computer and use it in GitHub Desktop.
Save dblevins/211a45b893999748c7c4 to your computer and use it in GitHub Desktop.

Looking to crack the ice. Hopefully inspire some brainstorming.

The following is a set of events Jean-Louis Monteiro created in our impl for using CDI to extend login events to the application.

The impetus of this one came from our attempt to kill a particularly inflexible interface in Tomcat called Realm. It started out hard-coded to a specific login approach (basic auth) and has grown awkwardly since.

java.security.Principal authenticate(java.lang.String username, java.lang.String password);
java.security.Principal authenticate(String username, String digest,
                              String nonce, String nc, String cnonce,
                              String qop, String realm,
                              String md5a2);    
java.security.Principal authenticate(org.ietf.jgss.GSSContext gssContext, boolean storeCreds);
java.security.Principal authenticate(java.security.cert.X509Certificate[] x509Certificates);

As you can see, this has obvious downsides.

Cons:

  • not extendable. each new auth scheme requires the interface to be updated.
  • each existing implementation has to implement the new auth scheme. (at least pre-java8)
  • caller and callee are still ultimately tied together

Clearly this is a false interface and separates very little. The calling side and the implementing side are doing a specific dance -- they are tied together. This is perhaps unavoidable. More on that.

Possible Pros:

  • strongly typed
  • self documenting
  • extremely simple to implement a scheme supported by the interface.

If you look at JAAS in comparison, there is effectively a single method that counts. This is from CallbackHandler, effectively the thing that really logs people in.

void handle(Callback[] callbacks)
throws java.io.IOException, UnsupportedCallbackException;

The observation on this one is it is effectively the same in function to the hard-coded Tomcat Realm API if it looked like this:

java.security.Principal[] authenticate(Object[] arguments);

Very little is actually being specified. If it is, then perhaps one could argue that public static void main(String[]) is also a security API :)

The Principal objects come in a slightly round-a-bout way, but you get the idea. It is the polar opposite of the Tomcat Realm API.

Pros:

  • extendable

Cons:

  • too loosely typed
  • not self-descriptive
  • very hard to implement an auth scheme
  • caller and callee are still ultimately tied together

I'll note both approaches have the same downside; the caller and callee are still ultimately tied together.

When the data you pass back and forth is essentially Object[] there is an incredible amount of casting involved. You aren't relying on the interface, but your foreknowledge of the code calling you. You haven't separated anything ultimately and only accomplish obfuscating that simple fact. It makes a good show of looking like it accomplishes something by having the arguments be a specific interface, but as mentioned that interface may as well be Object.

The calling side may as well just use reflection to find an "authenticate" method in the callee that will accept the data it is capable of passing in.

The idea Jean-Louis crafted up, which I thought was quite genius, use CDI Events. A callback handler is basically an event handler. Sounds like we have several genius' on the list and this is at the forefront of everyone's minds. That's more than exciting. :)

Here's an idea of what those look like. Putting them here not because I think their already perfect, but so we have something to start tearing up. Let's get creative. Some comments from me inline.

public abstract class BaseAuthenticationEvent {
    private Principal principal;

    public Principal getPrincipal() {
        return principal;
    }

    public void setPrincipal(final Principal principal) {
        this.principal = principal;
    }
}

Other options might include making principle a collection or this to a "Context" class of some sort and avoiding inheritance.

public class DigestAuthenticationEvent extends BaseAuthenticationEvent {

    private final String username;
    private final String digest;
    private final String nonce;
    private final String nc;
    private final String cnonce;
    private final String qop;
    private final String realm;
    private final String md5a2;

    // getters, setters and constructor stripped
}

Clearly for Digest auth. I'd expect any required scheme to have a standard event type. New ones can be provided, but there should be concrete classes for the minimum. The event object need only simple and dumb approach to passing arguments.

public class GssAuthenticationEvent extends BaseAuthenticationEvent {

    private final org.ietf.jgss.GSSContext gssContext;
    private final boolean storeCreds;

    // getters, setters and constructor stripped
}

public class SslAuthenticationEvent extends BaseAuthenticationEvent {

    private final java.security.cert.X509Certificate[] certs;

    // getters, setters and constructor stripped
}

public class UserPasswordAuthenticationEvent extends BaseAuthenticationEvent {

    private final String username;
    private final String credential;

    // getters, setters and constructor stripped
}

I'm not a fan of the inheritance, but I love the basic idea.

The obvious use is that authentication could be implemented with a simple observer such as:

public void authenticate(@Observes UserPasswordAuthenticationEvent event)

The inheritance clearly has CDI benefit in that one could observe BaseAuthenticationEvent such as:

public void authenticate(@Observes BaseAuthenticationEvent event)

This of course raises tons of question, and that of course is the idea :)

  • Who is allowed to implement this?
  • How should one reject login?
  • Do we let both the app and the container @Observes the event?
  • Do we require @Observes to use a qualifier that explicitly states the scheme? @Scheme("BASIC")

There is another event for adding roles (more Principle instances), but let's see what kind of ideas this fosters.

I'm sort of curious if there isn't a producing side to this as well:

@Produces
public UserPasswordAuthenticationEvent filter(@Authorization("Basic") String headerValue)

Maybe a bit too low level, but interesting idea to treat the HTTP Authorization header itself as some kind of event or parameter to a producer.

Theoretically the container could sign up to handle 100% of the parts in the common case where the app does not want to handle anything, but we might theoretically have the container NOT add built in producers and observers when it is determined the app has them.

Again, all brainstorming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment