3

I have been digging through posts for too long now and getting dizzy, so I'm hoping one of the gurus here can help me with this.

I'm using Container Managed Authentication and it's working well. I have my Realm setup to authenticate against, set up protected urls in web.xml, have a login page etc.

But now I am hitting a problem...

I have JPA objects for my data model and some of these objects are 'audited' in that they track when they were created or updated and by who.

I'm using a @PrePersist handler in my code to set the createdOn and updatedOn fields on persist/update respectively, like so:

@PrePersist
protected void onCreate() {
    this.setCreatedOn(new Date());
}

This works great, but I am missing how I should get access to the currently logged in user from here... I need that to be able to set the createdBy field.

I'm using Resteasy and in my endpoint I do have access to the logged in username and am able to get my Account object:

@Path("/test")
public class TestEndpoint {
    @EJB
    AuthorizationService authService;

    @GET
    @Path("path")
    @Produces("application/json")
    @RolesAllowed("User")
    public Response test() {
        Account account = authService.getLoggedInAccount();
        return account == null ? Response.status(Status.NOT_FOUND).build() : Response.ok().entity(account).build();
    }
}

AuthorizationService is mine and looks like this:

@Stateless 
@LocalBean
public class AuthorizationService {
    @Inject 
    HttpServletRequest request;

    public Account getLoggedInAccount() {
        Account result = (Account) request.getAttribute(LOGGED_IN_USER);
        if (result == null) {
            Principal principal = request.getUserPrincipal();
            if (principal != null) {
                List<Account> results = crudService.find(Account.BY_NAME, Params.of("name", principal.getName()), 0, 0);
                if (results != null && results.size() > 0) {
                    result = results.get(0);
                    request.setAttribute(LOGGED_IN_USER, result);
                }
            }
        }
        return result; 
    }
}

This works. Notice that I cache the logged-in user on a request attribute so I don't send the query to the DB each time.

Until now I have been able to get by with this setup, but I feel I am doing this all wrong...

I would like to have one global interception point (filter?) where I populate ...something... (the request?) with the currently logged in user's Account object and then be able to inject it wherever it's needed... I would highly prefer solutions that do not create a session as I'm trying to make the app as scalable as possible.

Any tips on how to handle this? That golden link to the tutorial that explains this well maybe? Thanks for any help guys!

Stijn de Witt
  • 40,192
  • 13
  • 79
  • 80

2 Answers2

3

Your question is a bit unclear, but I suppose from the comments that you want to be able to inject current logged-in user's account into your CDI beans, like this:

@Inject @CurrentUser Account account;

For this, you need:

  • a CDI producer to customize account object creation
  • inject principal into the producer so that you may find appropriate Account for logged-in user
  • Custom @CurrentUser qualifier to match your injection points with the producer
  • producer should create a request scoped bean - hence call to producer (and then to DB) is cached and executed only first time per request

Now code example of the Producer:

public class CurrentAccountProducer {

    @Inject private Principal principal; // get logged-in principal

    /* called first time per request to find Account entity for principal
      - it will be cached and injected into @CurrentAccount @Inject points
    */
    @CurrentAccount 
    @RequestScoped
    @Produces
    public Account produceAccount() {
        if (principal == null) {
            return null; // null will be injected if user is not logged in
        } else {
            // part of your original code here...
            List<Account> results = crudService.find(Account.BY_NAME, Params.of("name", principal.getName()), 0, 0);
                if (results != null && results.size() > 0) {
                    return results.get(0);
                }
        }
    }
}

This producer is all you need to inject Account for logged-in user or null for anonymous. You may then modify your AuthorizationService like this:

@Stateless 
@LocalBean
public class AuthorizationService {
    @Inject 
    @CurrentAccount Account currentAccount;

    public Account getLoggedInAccount() {
        return currentAccount; 
    }
}

It can be even as easy as injecting Account directly into TestEndpoint, bypassing AuthorizationService, but it is better to encapsulate business logic into an EJB to run the logic in a transaction.

neun24
  • 222
  • 2
  • 10
OndroMih
  • 7,280
  • 1
  • 26
  • 44
  • 1
    'Your question is a bit unclear' .. *proceeds with fantastic answer of exactly what I want to achieve* ... :) Thank you, thank you!! I know my question was unclear but I've learned EJB from practice and even after years I still find it very hard to discuss it with others... The terminology is so confusing sometimes. Plus it changed so many times and all versions of the docs are still online. But to the point, your answer looks like it will give me exactly what I want. Going to try this out as soon as I find the time. – Stijn de Witt Sep 09 '15 at 20:09
  • @OndrejM The annotation `@Produces` is missing in the produceAccount method. You might want to update your response. – Franck Sep 10 '15 at 11:07
  • @Franck, you are right. I updated my answer. Thanks! – OndroMih Sep 10 '15 at 14:47
  • @StijndeWitt, I was happy to help you. It is true that Java EE is very unclear, as it evolved through various very different stages, and there are still old artifacts in the API as well as in documentation. It is good to start with (Java EE 7 tutorial)[https://docs.oracle.com/javaee/7/tutorial/], which includes only the fresh and sufficient knowledge. Although it does not go into much details, it will give you the pointers as to what to look for in other sources. – OndroMih Sep 10 '15 at 14:52
1

There is a set of predefined beans in CDI applications. Among them the java.security.Principal represents the identity of the current caller. So you just have to @Inject Principal wherever you need it. Another thing you might consider for your specific needs is the Auditing feature of the delta spike data module project which might helps you cut custom code even more.

Franck
  • 1,754
  • 1
  • 13
  • 14
  • Thanks. this does sound like a step forward. I wonder however if I could make my own Account class available for injection? It would be more convenient than using the Principal to get it each time. – Stijn de Witt Sep 08 '15 at 11:45
  • There is a code fragment in the link you gave that illustrates injecting the user: `@Inject private User user;`. However how do I make the logged in user available for injection in the first place? – Stijn de Witt Sep 08 '15 at 11:52
  • The container is responsible to set the current logged in user into Principal. So when you declare `@Inject Principal` in a JEE secure context, the principal instance is mapped to the current user. For delta spike specifics you can do something like: `@Inject private Principal principal; @Produces @CurrentUser public String currentUser() { return principal !=null?principal.getName():"anonymous"; }` – Franck Sep 08 '15 at 12:14
  • I'm sorry if I appear thick, but I still don't get it. I don't want to use deltaspike. I want to push my Account object into ... somewhere... so I can `@Inject private Account myOwnAccountObject` somewhere later. Is it possible? – Stijn de Witt Sep 08 '15 at 13:23
  • In this case you can simply transform getLoggedInAccount method into a bean factory with `@Produces @RequestScoped @CurrentUser` where `@CurrentUser` would be one of your CDI qualifier. Then you can `@Inject @CurrentUser Account` when you need it. – Franck Sep 08 '15 at 14:02