Search code examples
jakarta-eeglassfishpayara

Assign roles to user


Instead of having all users with right to do everything, I am trying to add roles (Admin, User) to working Java EE project.

I had started on my own, altering tables, writing own mechanism and planned to control that programmatically with @WebFilter.

Soon, I figured out that Java EE has something that looks like built in mechanism for this purpose.

What I wanted to use is this role filter annotation:

@RolesAllowed("ADMIN")
public void adminAllowedFunction(){
    //code
}

But how to assing role to actual user/session. Also, what user mean in this context ? Needs to be record in table fulfilling some convention, or all what i am doing is assigning role to actual session ?

I have also read something about mapping roles to glassfish/payara users (realms). Is that the right way ? I didn't found something clear explained, only about <security-role-mapping> in glassfish-web.xml, but how to grab that in java code ?

I am really confused from that Java EE mechanism. I have doubted that I did not understand for what it is intended.

I have tried denny for all roles this function:

@DenyAll
public String getUserName(){
    return (String) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("loggedUser");

}

But I could not get the conditions when it would been denied.

I will be grateful for every useful information. Also, do not worry about bunch of code examples.


Solution

  • In general you need to use a Java EE authentication mechanism. Such authentication mechanism is called at the beginning of a request, and will interact with the caller to obtain credentials. If those credentials are validated correctly, it can set a caller name or caller principal alongside with several roles in the current request.

    Where the caller name and roles come from is not important for Java EE. Your code can invent them on the fly, fetch them from an in-memory store, query them from a DB, get them from an LDAP server, etc etc.

    Often, but not necessarily, an authentication mechanism delegates the task of validating credentials to a specialised component that interacts with a kind of store like mentioned above. This is called an "identity store" (there's about 20 other terms for this same thing, but Java EE standardised on that).

    The following gives an example of an identity store:

    @ApplicationScoped
    public class TestIdentityStore implements IdentityStore {
    
        public CredentialValidationResult validate(UsernamePasswordCredential usernamePasswordCredential) {
    
            if (usernamePasswordCredential.compareTo("reza", "secret1")) {
                return new CredentialValidationResult("reza", new HashSet<>(asList("foo", "bar")));
            }
    
            return INVALID_RESULT;
        }
    
    }
    

    A custom authentication mechanism looks like this:

    @ApplicationScoped
    public class TestAuthenticationMechanism implements HttpAuthenticationMechanism {
    
        @Inject
        private IdentityStoreHandler identityStoreHandler;
    
        @Override
        public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext) throws AuthenticationException {
    
            if (notNull(request.getParameter("name"), request.getParameter("password"))) {
    
                // Get the (caller) name and password from the request
                // NOTE: This is for the smallest possible example only. In practice
                // putting the password in a request query parameter is highly
                // insecure
                String name = request.getParameter("name");
                Password password = new Password(request.getParameter("password"));
    
                // Delegate the {credentials in -> identity data out} function to
                // the Identity Store
                CredentialValidationResult result = identityStoreHandler.validate(
                    new UsernamePasswordCredential(name, password));
    
                if (result.getStatus() == VALID) {
                    // Communicate the details of the authenticated user to the
                    // container. In many cases the underlying handler will just store the details 
                    // and the container will actually handle the login after we return from 
                    // this method.
                    return httpMessageContext.notifyContainerAboutLogin(
                        result.getCallerPrincipal(), result.getCallerGroups());
                } 
    
                return httpMessageContext.responseUnauthorized();
            } 
    
            return httpMessageContext.doNothing();
        }
    
    }
    

    Note that the above is for DEMO purposes only and is not a realistic or even secure authentication mechanism!

    See also: https://github.com/eclipse-ee4j/soteria/tree/master/test/app-mem/src/main/java/org/glassfish/soteria/test

    Often time you would not write your own authentication mechanism or even identity store, but would re-use ones offered by Java EE. For example:

    @CustomFormAuthenticationMechanismDefinition(
        loginToContinue = @LoginToContinue(
            loginPage="/login.jsf",
            errorPage=""
        )
    )
    @ApplicationScoped
    public class ApplicationInit {
    }
    

    The notion of a user exists by the ability to query or inject the current caller/user principal. For instance using:

    @Inject
    private Principal principal;
    

    Or using e.g. the Servlet API:

    HttpServletRequest#getUserPrincipal();
    

    This is same "user" (authenticated identity) that @RolesAllowed and similar constructs also check against.

    Group to role mapping is always optional, and defaults to groups being the same as roles. Some servers don't even support group to role mapping at all.