0
votes

This question is a follow up to here.

Upon login, my app needs to consult the container for authentication using request.login() and then set a "User" bean, which contains username, password, role, and a location-specific field. Upon successful authorization against the container (WAS 8), the user's role needs to be checked to figure out the appropriate welcome page to where the user should be redirected. My container implements a federated repository, which basically lumps together 3 LDAP branches, a database, and a flat file. There are many roles (5 for now, probably will be more later).

I have 2 beans: a RequestScoped Credentials bean and a SessionScoped bean named Login. Login contains a login() method. I have a problem in side the login method.

My problem is that whenever I use:

Principal userPrincipal = request.getUserPrincipal();
request.getUserPrincipal();
if (userPrincipal != null) {
    request.logout();
}
request.login(credentials.getUsername(), credentials.getPassword());
String name = userPrincipal.getName();

before:

Donor donor = loginService.getDonor(credentials.getUsername());
currentUser = new Users();
currentUser.setLocation(donor.getCenter().getCity());
currentUser.setRole("DONOR");
currentUser.setUserId(credentials.getUsername());
currentUser.setFirstName(donor.getFirstName());
currentUser.setLastName(donor.getLastName());
currentUser.setUsername(credentials.getUsername());
currentUser.setName(credentials.getUsername());
return "users?faces-redirect=true";

my currentUser User bean isn't stored in the session.

I've removed the first chunk of code and noticed that my User bean's info is then stored in the session and can be viewed on the subsequent users page. I've reintroduced the first chunk of code line by line and noticed that:

if (userPrincipal != null) {
    request.logout();
}

causes the problem.

How the heck am I supposed to store a User bean implementing programmatic security and JSF2.0/CDI? I've spent weeks on this. I can imagine implementing something more elaborate like a filter that catches the redirect, grabs the userprincipal, calls the db or ldap for additional attributes, and then redirects to the appropriate page...but I want to keep things simple. There has got to be a simple way to do this.

I originally tried my hand at login using j_security_check and specifying FORM in web.xml. Now, using a JSF 2.0 form and a login method, I noticed that FORM gets ignored in favor of BASIC. System.out.println("getAuthType?.." + request.getAuthType()); returns "BASIC" within the login method. This results in an annoying cache, which is why request.logout is required. Otherwise, request.login will fail.

I stumbled across this last night, which contains a link to here. So, there may be a way to obtain the userprincipal, set a User bean, and redirect to an appropriate welcome page. I don't know how outdated that 2nd link is though. It also seems to be the case that j_security_check can't be used in tandem with a programmatic means of determining which page I should redirect a user to based on role.

As to whether I should use j_security_check or programmatic security/JSF/CDI, I just need to figure out something that is simple and allows me to store the Login bean or an independent User bean in the session.

Here's my login form from my view:

<h:form id="loginForm">
    <fieldset>
        <div class="form-row">
            <h:outputLabel for="username" value="User ID"/>
            <h:inputText id="username" value="#{credentials.username}" 
required="true" size="20" />
        </div>
        <div class="form-row">
            <h:outputLabel for="password" value="Password"/>
            <h:inputSecret id="password" type="password" value="#
{credentials.password}" required="true" />
        </div>

        <div class="form-row"> 
            <h:commandButton styleClass="btn btn-warning" value="Sign In" 
type="submit" action="#{login.login}" />
            <a href="#" id="forgot-password">Forgot you password?</a>
        </div>     
    </fieldset>
</h:form>

Here's my Login bean (code has been stripped out and edited to just showcase the relevant parts):

import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Produces;
import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.security.auth.Subject;
import javax.security.auth.login.CredentialExpiredException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
@SessionScoped
@Named
public class Login implements Serializable {

    private static final long serialVersionUID = 7965455427888195913L;

    @Inject
    private Credentials credentials;

    @PersistenceContext
    private EntityManager userDatabase;

    @Inject
    LoginService loginService;

    private Users currentUser;
    private Service service;
    private String uniqueSecurityName;
    private String l;

    @SuppressWarnings("unchecked")
    public String login() {
        FacesContext context = FacesContext.getCurrentInstance();
        ExternalContext externalContext = context.getExternalContext();
        HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
        System.out.println("The login method has been called.");

        try {
        Principal userPrincipal = request.getUserPrincipal();
        request.getUserPrincipal();
        if (userPrincipal != null) {
            request.logout();
        }
        request.login(credentials.getUsername(), credentials.getPassword());
        String name = userPrincipal.getName();

        System.out.println("getRemoteUser?.." + request.getRemoteUser());
        System.out.println("getUserPrincipal?.." + request.getUserPrincipal());
        System.out.println("getAuthType?.." + request.getAuthType());         

        Donor donor = loginService.getDonor(credentials.getUsername());
        currentUser = new Users();
        currentUser.setLocation(donor.getCenter().getCity());
        currentUser.setRole("DONOR");
        currentUser.setUserId(credentials.getUsername());
        currentUser.setFirstName(donor.getFirstName());
        currentUser.setLastName(donor.getLastName());
        currentUser.setUsername(credentials.getUsername());
        currentUser.setName(credentials.getUsername());
        return "users?faces-redirect=true";
        } catch (Exception e) {}

        return null;
    }
    public void logout() {
        currentUser = null;
    }

    public boolean isLoggedIn() {
        return currentUser != null;
    }

    @Produces
    @LoggedIn
    public Users getCurrentUser() {
        return currentUser;
    }
}

Here's my Credentials bean:

import java.io.Serializable;

import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Default;
import javax.inject.Named;

@RequestScoped
@Named
@Default
public class Credentials implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 6976596855571123825L;
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Here's the subsequent users.xhtml page (just for testing purposes to verify session info):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
      <title>Login</title>
      <link href="style/main.css" rel="stylesheet" type="text/css"/>
      <ui:insert name="head"/>
    </head>
<body>

  <div id="container">
    <div id="header">

    </div>

    <div id="sidebar">

    </div>

    <div id="content">
      <h1>Current User</h1>
        <h:dataTable value="#{login.currentUser}" var="u">
            <h:column>
                <f:facet name="header">
                 Username
              </f:facet>
                <h:outputText value="#{u.username}" />
            </h:column>
            <h:column>
                <f:facet name="header">
                 Name
              </f:facet>
                <h:outputText value="#{u.name}" />
            </h:column>
            <h:column>
                <f:facet name="header">
                 Password
              </f:facet>
                <h:outputText value="#{u.password}" />
            </h:column>
        </h:dataTable>
    </div>

    <br style="clear:both"/>
  </div>

</body>
</html>

Here's my Users bean:

import java.io.Serializable;

import javax.annotation.PostConstruct;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.FacesContext;
import javax.inject.Named;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Transient;

@Entity
public class Users {
    @Id
    private String username;
    private String name;
    private String password;
    @Transient
    private String role;
    @Transient
    private String location;
    @Transient
    private String userId;
    @Transient
    private String firstName;
    @Transient
    private String lastName;

    public Users() {
    }

    public String getUsername() {
        return username;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getName() {
        return name;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword() {
        return password;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return "User (username = " + username + ", name = " + name + ")";
    }
}
1
Login should be @RequestScoped and you should have another @SessionScoped managed bean to handle user data or even handle it as a session attribute.Luiggi Mendoza
@kolossus Well, Java Security, IMO, is probably the toughest Java EE subject to grasp considering j_security_check vs Programmatic Security and setting a domain/realm for your specific container. The problem is that if I don't accurately describe my problem, someone's going to waste their time and my time providing an answer that is worthless towards solving my problem.Chris Harris
@LuiggiMendoza - How should I access this new SessionScoped bean? I can't inject it into the RequestScoped Login bean, because that would result in a session object being created upon construction. I guess I have to store the user/userprincipal in the session map, redirect to an appropriate page based on role, and then inject a session bean, that populates its fields via the session map's user/userprincipal, into a RequestScoped bean?Chris Harris
@LuiggiMendoza - As for the session attribute approach you mentioned, I used to put a user object on the session map at one point. That seemed a bit goofy to me though. I guess I could create a SessionUtil class that has a Producer method that returns the user attribute/object from the session map.Chris Harris
I don't really see a problem having the user data stored as session attribute, but you can go with the @SessionScoped approach if you can. And yes, you can use an utility class to access session data (not necessarily for this user attribute only).Luiggi Mendoza

1 Answers

1
votes

Your problem is as you guessed ,

 if (userPrincipal != null) {
            request.logout();
        }

You are recreating the session with the above lines . It is not a good idea to destroy a session in a session scoped bean. This is resulting in session bean getting cleaned by CDI.

You would need to store the username , password as you are cleaning up session bean in action method.

@ApplicationScoped
class LoginService{

public boolean login(String username,String password) {
                String username = credentials.getUsername();
                String password = credentials.getPassword();
                Principal userPrincipal = request.getUserPrincipal();
                request.getUserPrincipal();
                if (userPrincipal != null) {
                    request.logout();
                }
                boolean loggedIn = request.login(username,password);
                if(loggedIn){
                users = new Users();
                users.setUsername(username);  
                session.setAttribute("Users",users);               
                }
              return loggedIn;
    }

}

Keep the action method to login in credential

  @RequestScoped  
    class Credential {
          @Inject  LoginService loginService;
          String username;
          String password;

          login(ActionEvent action){
           if(loginService.login(username,password))
               //redirect to users
            else
               //show error
          }
    }

Create an annotation to grab logged in user from CDI

@Retention(RetentionPolicy.RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface SpringBean {
    String value() default "";
}

Use a BeanLocator class to produce some object , you can add this method to LoginService but it is better to keep it separate

    public class BeanLocator {    
    @Produces @LoggedInUser
    public Object getSessionAttribute(InjectionPoint ip){
        String beanName = ip.getAnnotated().
                              getAnnotation(SessionAttribute.class).value();
        if(beanName==null || beanName.equals(""))
            beanName = getDefaultBeanName(ip.getMember().
                               getDeclaringClass().getName());
        return  FacesContext.getCurrentInstance().getExternalContext().
                    getSessionMap().get(beanName);
    }
   } 

Use it in other services like,

class MyOtherBean{
 @Inject @@LoggedInUser Users;
}