ee7-sandbox

Authentication and Authorization:JWT and CDI

As Spring competier, CDI was introduced as part of Java EE 6 which provides a standard IOC container for Java EE, and also provides lots of new features that are not included in Spring, such as a simple Event processing mechanism, Decorate, Alternative etc.

In our before example, we can use CDI to expose a current user into RequestScoped in the AuthenticationFilter, and use it anywhere within the request.

Inject a @AuthenticatedUser CDI Event in AuthenticationFilter.

@Inject
@AuthenticatedUser
Event<String> userAuthenticatedEvent;

AuthenticatedUser is standard CDI @Qualifier to identify CDI beans.

After extracted user information from JWT token, fire userAuthenticatedEvent.

String username = jwtHelper.parseToken(token);
userAuthenticatedEvent.fire(username);

We do not setup the SecurityContext in this sample, so JAAS annotations is not activiated.

AuthenticatedUserProducer is responsible for handling this event.

@RequestScoped
public class AuthenticatedUserProducer {

    @Produces
    @RequestScoped
    @AuthenticatedUser
    private User authenticatedUser;

    @Inject
    UserRepository users;

    public void handleAuthenticationEvent(@Observes @AuthenticatedUser String username) {
        this.authenticatedUser = users.findByUsername(username);
    }

}

In the above codes, handleAuthenticationEvent method accepts the event payload as arguments. In this method, it queries user by username and exposes the result in RequestScoped.

@Produces
@RequestScoped
@AuthenticatedUser
private User authenticatedUser;

Now there is a User bean available in @RequestScoped.

In AuthorizationFilter, you can inject it and use it to process authorization.

@Inject
@AuthenticatedUser
User currentUser;

You can also get the current user in business logic codes.

For example, add auditor automaticially in the entities. Use a JPA EntityListener to archive this purpose.

public class AuditEntityListener {

    private static final Logger LOG = Logger.getLogger(AuditEntityListener.class.getName());

//    @Inject
//    @AuthenticatedUser
//    User user;
//    @PersistenceContext
//    EntityManager em;
    @PrePersist
    public void beforePersist(Object entity) {
        if (entity instanceof AbstractAuditableEntity) {
            AbstractAuditableEntity o = (AbstractAuditableEntity) entity;
            final OffsetDateTime now = OffsetDateTime.now();
            o.setCreatedAt(now);
            o.setUpdatedAt(now);
            o.setCreatedBy(currentUser());
        }
    }

    @PreUpdate
    public void beforeUpdate(Object entity) {
        if (entity instanceof AbstractAuditableEntity) {
            AbstractAuditableEntity o = (AbstractAuditableEntity) entity;
            o.setUpdatedAt(OffsetDateTime.now());
            o.setUpdatedBy(currentUser());
        }
    }

    private String currentUser() {
        User user = CDI.current().select(User.class, new AuthenticatedUserLiteral()).get();
        LOG.log(Level.FINEST, "get current user form EntityListener@{0}", user);
        return user.getUsername();
    }
}

NOTE: JPA 2.1 should support @Inject in EntityListener class, I have written a post to describe the JPA CDI support feature before. But unfortunately it does not work in our case(Hibernate and Wildfly).

The JWT token based authentication and authorization is heavily inspired by Best practice for REST token-based authentication with JAX-RS and Jersey on stackoverflow, thank Cássio Mazzochi Molin for providing detailed steps to implement this solution.

Get source codes from my Github account, and play it yourself.

https://github.com/hantsy/angularjs-ee7-sample

There is a cdi folder to demonstrate the JWT token based security.