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 + ")";
}
}
@RequestScoped
and you should have another@SessionScoped
managed bean to handle user data or even handle it as a session attribute. – Luiggi Mendoza@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