0

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 + ")";
    }
}
Community
  • 1
  • 1
Chris Harris
  • 1,329
  • 4
  • 17
  • 28
  • 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 Mar 25 '13 at 17:24
  • @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 Mar 25 '13 at 17:25
  • @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 Mar 25 '13 at 17:38
  • @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 Mar 25 '13 at 17:40
  • 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 Mar 25 '13 at 17:41
  • @LuiggiMendoza - Thanks! I'll attempt a user object in the session map again. I'll try a `@RequestScoped` bean that uses `@Producer` method to return a User bean. You're right. I wasn't going to use this utility to return just one user attribute. – Chris Harris Mar 25 '13 at 18:05

1 Answers1

1

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;
}
Avinash Singh
  • 3,421
  • 2
  • 20
  • 21
  • Hello again, Avinash. O.K. Good to have a confirmation that I'm indeed destroying/recreating the session. I've read in many places that I should set Credentials as RequestScoped though. Every example I've ever seen using a Credentials object always sets it to request in order to dismiss the user's password. Wouldn't I want to keep Credentials as RequestScoped? I'll try what you're saying, but I just don't want to open up a potential security hole. `@Produces @LoggedIn` is used elsewhere on other pages that I didn't include in my post. – Chris Harris Mar 25 '13 at 17:57
  • Yes you should definetly keep credentials requestscoped , I overlooked it. Your Login should also be RequestScoped however you can keep a bean like UserBean(or Users in your case) to keep User details in session scope. – Avinash Singh Mar 25 '13 at 18:02
  • O.K. It sounds like an implementation is being solidified. So, final question regarding "UserBean(or Users in your case): to inject or not to inject? I would think that I need to inject, otherwise CDI will ignore the `@SessionScoped` bean. However, if I inject a `@SessionScoped` Users bean into my `@RequestScoped` Login bean via CDI, that will create a session every time someone visits the login/index page. I guess I should go with a `@RequestScoped` utility bean that uses a `@Producer` method to produce a Users object as I talked about with @LuiggiMendoza? – Chris Harris Mar 25 '13 at 18:20
  • You are correct the request.login will recreate the session bean as well , keep it like `@Produces @SessionScoped @LoggedIn Users login(String username,String password)` – Avinash Singh Mar 25 '13 at 18:44
  • Ahhhh, that's a completely different approach than what I had for the login method, which was going to reside in a `@RequestScoped` util class (much like one that produces a FacesContext) and pulls from the session map. I like it! My login method is currently returning a String, which is essentially the navigation. Sorry - one last question or two now as a result: So, I could return a Users session bean, but instead now use the externalContext to redirect? Oh, and what if login fails? I'm then producing a Users session object, which is unnecessary and susceptible to DDOS. – Chris Harris Mar 25 '13 at 20:19
  • I have updated my example , may be combining Produces in context bean is not a good idea as it is increasing complexity , keep it separate and define your annotation and its producer. – Avinash Singh Mar 25 '13 at 22:35