3
votes

I am playing with JavaEE 7 and tried to write a webapp with a simple login mechanism.

There is an EJB entity class persisted with JPA called User holding data about users. In the WAR, a session scoped managed bean called UserManagedBean is responsible for tracking the current user, thus it has a property of type User that is set when someone logs in successfully. A filter is watching the value of this property and redirects to login page if necessary. Of course, both User and UserManagedBean are serializable (implementing the interface and not containing anything non-serializable).

My problem is that after the successful login, refreshing the page throws me back to the login page AND my previously set user property is now null (actually this is why the filter triggers the redirect).

Here is what I tried:

  • Separated logic and data: UserManagedBean now only has this one property and a few helper methods, no EJBs, nothing related to Java magic.
  • Tried setting the javax.faces.STATE_SAVING_METHOD context parameter in the web.xml to both server and client, nothing changed.
  • Verified that the session scoped managed bean remains the same: only one of it is created, but somehow the value of user is nulled after navigating from the login page.
  • According to the NetBeans debugger, the user field is not accessed apart from setting it to the logged in user.
  • Specifying custom serialization methods revealed that UserManagedBean is not serialized or deserialized during the experiment.
  • Debugging in Chrome suggests that the session ID is saved in the cookie and is not changed when the value of user is lost.
  • No exceptions are detected.

I must be missing something trivial, any help would be appreciated.

(UPDATE: It was indeed trivial and not related to JSF or managed beans, see my answer below.)

My code is the following:

User class:

@Entity(name = "USERS")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    private String username;
    private boolean administrator;
    private byte[] salt;
    private byte[] passwordHash;

    public String getUsername() {
        return username;
    }

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

    public boolean isAdministrator() {
        return administrator;
    }

    public void setAdministrator(boolean administrator) {
        this.administrator = administrator;
    }

    public byte[] getSalt() {
        return salt;
    }

    public void setSalt(byte[] salt) {
        this.salt = salt;
    }

    public byte[] getPasswordHash() {
        return passwordHash;
    }

    public void setPasswordHash(byte[] passwordHash) {
        this.passwordHash = passwordHash;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (username != null ? username.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof User)) {
            return false;
        }
        User other = (User) object;
        if ((this.username == null && other.username != null) || (this.username != null && !this.username.equals(other.username))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "hu.bme.aut.mv.testbay.ejb.entities.User[ id=" + username + " ]";
    }

}

UserManagedBean class:

@ManagedBean(name = "userManagedBean")
@SessionScoped
public class UserManagedBean implements Serializable {
    private static final long serialVersionUID = 1L;

    private User currentUser;

    public User getCurrentUser() {
        return currentUser;
    }

    public void setCurrentUser(User user) {
        this.currentUser = user;
    }

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

    public boolean isAdmin() {
        return currentUser != null && currentUser.isAdministrator();
    }

    public String logout() {
        currentUser = null;
        return "/faces/index.xhtml";
    }

    /**
     * Creates a new instance of UserManagedBean
     */
    public UserManagedBean() {
        System.out.println("UserManagedBean constructed!");
    }
}

The filter (doBeforeProcessing):

HttpSession session = ((HttpServletRequest) request).getSession(false);
UserManagedBean userManagedBean = (session != null) ? (UserManagedBean) session.getAttribute("userManagedBean") : null;

if (userManagedBean == null || userManagedBean.getCurrentUser() == null) {
    ((HttpServletResponse)response).sendRedirect(((HttpServletRequest) request).getContextPath() + "/faces/login.xhtml");
}

UPDATE:

It is important to note that the user is properly set once, and transition to the welcome screen happens as it should. However, the nex request finds the user property empty.

The code triggering the authentication is in the request scoped LoginManagedBean class:

@ManagedBean
@RequestScoped
public class LoginManagedBean implements Serializable {

    @EJB
    private AuthenticationSessionBeanLocal authBean;

    @ManagedProperty("#{userManagedBean}")
    private UserManagedBean userManagedBean;

    @PostConstruct
    public void Dummy() {
        User user = userManagedBean.getCurrentUser();
    }

    public UserManagedBean getUserManagedBean() {
        return userManagedBean;
    }

    public void setUserManagedBean(UserManagedBean userManagedBean) {
        this.userManagedBean = userManagedBean;
    }

    private String username;
    private String password;

    //Some getters and setters...
    //...

    public String login() throws NoSuchAlgorithmException {
        if (authenticate(username, password)) {
            if (userManagedBean.getCurrentUser().isAdministrator())
                return "/faces/admin/welcome.xhtml?faces-redirect=true";
            else
                return "/faces/testing/welcome.xhtml?faces-redirect=true";
        }
        return null;   
    }

    private boolean authenticate(String username, String password) throws NoSuchAlgorithmException {
        userManagedBean.setCurrentUser(authBean.authenticate(username, password));
        if (userManagedBean.getCurrentUser() == null)
            return false;
        return true;
    }

    //Constructor and methods...
    //...
}
2
it's not clear to me how you're using the filter. Usually, the login page is a request-scoped user-less page, and the login action calls the filter code to create / retrieve the User entity and assign it to the session-scoped UserManagerBean.Leo
Another request scoped managed bean does the authentication and retrieves the User entity with the help of an EJB, setting it in the UserManagedBean that is present all the time. The filter is activated when a page other than the login or the registration form is requested, and checks if the UserManagedBean instance contains a User entity or not. Logging out sets this property to null (and apparently, something else does the same unintentionally).Vincent
How are you transitioning from the login screen to your main authenticated page?Leo
@Leo, please see my update.Vincent
session.getAttribute("userManagedBean"). Where did you set the attribute before getting it out?Mr.J4mes

2 Answers

1
votes

It was indeed a silly mistake.

NetBeans breakpoints did not signal it, but the field was indeed accessed by the logout function, which was called after each navigation. It turned out that I misused a PrimeFaces component. I wanted to set the outcome attribute of the logout commandButton to the logout method on the UserManagedBean, but NetBeans autocomplete put a pair of parentheses after the method name which I did not notice. So instead of getting an error that the outcome attribute does not work like action and should recieve a path as a string, the EL evaluated the logout() method to set the outcome to the returned url of the login page, but the method also logged out the user silently.

The only thing I wonder is how NetBeans failed to break both in the set method and the field breakpoint that should have triggered when the field was accessed.

0
votes

you can put the variable in the view (that is View scoped) and retrieve it / modify it with getter and setter. This would also allow you to test the controller in a better way, by mocking the view response.