I'm wondering what the current approach is regarding user authentication for a web application making use of JSF 2.0 (and if any components do exist) and Java EE 6 core mechanisms (login/check permissions/logouts) with user information hold in a JPA entity. The Oracle Java EE tutorial is a bit sparse on this (only handles servlets).
This is without making use of a whole other framework, like Spring-Security (acegi), or Seam, but trying to stick hopefully with the new Java EE 6 platform (web profile) if possible.
I suppose you want form based authentication using deployment descriptors and j_security_check.
You can also do this in JSF by just using the same predefinied field names j_username and j_password as demonstrated in the tutorial.
E.g.
<form action="j_security_check" method="post">
<h:outputLabel for="j_username" value="Username" />
<h:inputText id="j_username" />
<br />
<h:outputLabel for="j_password" value="Password" />
<h:inputSecret id="j_password" />
<br />
<h:commandButton value="Login" />
</form>
You could do lazy loading in the User getter to check if the User is already logged in and if not, then check if the Principal is present in the request and if so, then get the User associated with j_username.
package com.stackoverflow.q2206911;
import java.io.IOException;
import java.security.Principal;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
#ManagedBean
#SessionScoped
public class Auth {
private User user; // The JPA entity.
#EJB
private UserService userService;
public User getUser() {
if (user == null) {
Principal principal = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
if (principal != null) {
user = userService.find(principal.getName()); // Find User by j_username.
}
}
return user;
}
}
The User is obviously accessible in JSF EL by #{auth.user}.
To logout do a HttpServletRequest#logout() (and set User to null!). You can get a handle of the HttpServletRequest in JSF by ExternalContext#getRequest(). You can also just invalidate the session altogether.
public String logout() {
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
return "login?faces-redirect=true";
}
For the remnant (defining users, roles and constraints in deployment descriptor and realm), just follow the Java EE 6 tutorial and the servletcontainer documentation the usual way.
Update: you can also use the new Servlet 3.0 HttpServletRequest#login() to do a programmatic login instead of using j_security_check which may not per-se be reachable by a dispatcher in some servletcontainers. In this case you can use a fullworthy JSF form and a bean with username and password properties and a login method which look like this:
<h:form>
<h:outputLabel for="username" value="Username" />
<h:inputText id="username" value="#{auth.username}" required="true" />
<h:message for="username" />
<br />
<h:outputLabel for="password" value="Password" />
<h:inputSecret id="password" value="#{auth.password}" required="true" />
<h:message for="password" />
<br />
<h:commandButton value="Login" action="#{auth.login}" />
<h:messages globalOnly="true" />
</h:form>
And this view scoped managed bean which also remembers the initially requested page:
#ManagedBean
#ViewScoped
public class Auth {
private String username;
private String password;
private String originalURL;
#PostConstruct
public void init() {
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
originalURL = (String) externalContext.getRequestMap().get(RequestDispatcher.FORWARD_REQUEST_URI);
if (originalURL == null) {
originalURL = externalContext.getRequestContextPath() + "/home.xhtml";
} else {
String originalQuery = (String) externalContext.getRequestMap().get(RequestDispatcher.FORWARD_QUERY_STRING);
if (originalQuery != null) {
originalURL += "?" + originalQuery;
}
}
}
#EJB
private UserService userService;
public void login() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext externalContext = context.getExternalContext();
HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
try {
request.login(username, password);
User user = userService.find(username, password);
externalContext.getSessionMap().put("user", user);
externalContext.redirect(originalURL);
} catch (ServletException e) {
// Handle unknown username/password in request.login().
context.addMessage(null, new FacesMessage("Unknown login"));
}
}
public void logout() throws IOException {
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
externalContext.invalidateSession();
externalContext.redirect(externalContext.getRequestContextPath() + "/login.xhtml");
}
// Getters/setters for username and password.
}
This way the User is accessible in JSF EL by #{user}.
After searching the Web and trying many different ways, here's what I'd suggest for Java EE 6 authentication:
Set up the security realm:
In my case, I had the users in the database. So I followed this blog post to create a JDBC Realm that could authenticate users based on username and MD5-hashed passwords in my database table:
http://blog.gamatam.com/2009/11/jdbc-realm-setup-with-glassfish-v3.html
Note: the post talks about a user and a group table in the database. I had a User class with a UserType enum attribute mapped via javax.persistence annotations to the database. I configured the realm with the same table for users and groups, using the userType column as the group column and it worked fine.
Use form authentication:
Still following the above blog post, configure your web.xml and sun-web.xml, but instead of using BASIC authentication, use FORM (actually, it doesn't matter which one you use, but I ended up using FORM). Use the standard HTML , not the JSF .
Then use BalusC's tip above on lazy initializing the user information from the database. He suggested doing it in a managed bean getting the principal from the faces context. I used, instead, a stateful session bean to store session information for each user, so I injected the session context:
#Resource
private SessionContext sessionContext;
With the principal, I can check the username and, using the EJB Entity Manager, get the User information from the database and store in my SessionInformation EJB.
Logout:
I also looked around for the best way to logout. The best one that I've found is using a Servlet:
#WebServlet(name = "LogoutServlet", urlPatterns = {"/logout"})
public class LogoutServlet extends HttpServlet {
#Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession(false);
// Destroys the session for this user.
if (session != null)
session.invalidate();
// Redirects back to the initial page.
response.sendRedirect(request.getContextPath());
}
}
Although my answer is really late considering the date of the question, I hope this helps other people that end up here from Google, just like I did.
Ciao,
VĂtor Souza
It should be mentioned that it is an option to completely leave authentication issues to the front controller, e.g. an Apache Webserver and evaluate the HttpServletRequest.getRemoteUser() instead, which is the JAVA representation for the REMOTE_USER environment variable. This allows also sophisticated log in designs such as Shibboleth authentication. Filtering Requests to a servlet container through a web server is a good design for production environments, often mod_jk is used to do so.
The issue HttpServletRequest.login does not set authentication state in session has been fixed in 3.0.1. Update glassfish to the latest version and you're done.
Updating is quite straightforward:
glassfishv3/bin/pkg set-authority -P dev.glassfish.org
glassfishv3/bin/pkg image-update
Related
I am using Container security (J2EE Security) in a PrimeFaces application and I cannot get the originally requested URL. After the user is authenticated, I want to direct them back to their original request. I cannot find that original request anywhere.
J2EE security provider is configured in Weblogic and form authentication is in the web XML like this:
<login-config>
<auth-method>FORM</auth-method>
<realm-name>myrealm</realm-name>
<form-login-config>
<form-login-page>/faces/login.xhtml</form-login-page>
<form-error-page>/faces/unauthorized.xhtml</form-error-page>
</form-login-config>
</login-config>
In the login form, I do the authentication inside the bean. Imagine a simple form as such:
<h:form>
<p:inputText id="userName" value="#{userManager.userName}" />
<p:password id="password" value="#{userManager.password}"/>
<p:commandButton action="#{userManager.login()}" value="Login"/>
</h:form>
Login Bean would look like this:
#Named
#SessionScoped
public class UserManager implements Serializable {
private String userName;
private String password;
private String requestedURL;
#Inject
private HttpServletRequest request;
#PostConstruct
public void init() {
requestedURL = request.getHeader("Referer"); //this is null
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
String forwardRequestURI = (String)externalContext.getRequestMap().get(RequestDispatcher.FORWARD_REQUEST_URI);
}
public void login() {
request.login(getUserName(), getPassword());
//if successful, redirect back to place they came from
}
}
Both request.getHeader("Referer") and the RequestDispatcher.FORWARD_REQUEST_URI are null. What is the magic I need here?
For Weblogic, use:
String targetUrl = (String) session.getAttribute("weblogic.formauth.targeturl");
I have already a login functional screen built using JSF 2.2 and PrimeFaces 5.3. However, I have a problem. Say the user enters username and password but one of them is incorrect. When that happens, I display an error message to the user: "Username and/or password is incorrect". The problem is that at that moment I am storing the session. I ONLY want to store a session once the user successfully is logged in. My Login form looks like this:
<f:metadata>
<f:viewParam name="tipo" value="#{loginTipoController.tipo.tipo}" />
</f:metadata>
<h:form id="login">
<p:focus context="login" />
<p:graphicImage url="img/logog.JPG" width="448" height="119" />
<p>Digite su usuario y password.</p>
<p:outputLabel for="usuario" value="Usuario:" />
<p:inputText id="usuario" value="#{loginController.login.username}" />
<p:outputLabel for="password" value="Password:" />
<p:password id="password" value="#{loginController.login.password}" />
<p:messages autoUpdate="true" for="usuarioPassword" />
<p:commandButton value="Login" action="#{loginController.login()}" />
<p:button value="Regresar" outcome="index.jsf" />
</h:form>
The controller class (loginController), looks like this:
#ManagedBean
#SessionScoped
public class LoginController {
private Login login;
private LoginTipo loginTipo;
private LoginService service;
public Login getLogin() {
return login;
}
public void setLogin(Login login) {
this.login = login;
}
public LoginService getService() {
return service;
}
public void setService(LoginService service) {
this.service = service;
}
#PostConstruct
public void init()
{
login = new Login();
service = new LoginService();
}
public String login()
{
FacesContext facesCont = FacesContext.getCurrentInstance();
loginTipo = facesCont.getApplication().evaluateExpressionGet(facesCont,
"#{loginTipoController.tipo}", LoginTipo.class);
login = service.login(login.getUsername(), login.getPassword(), loginTipo.getTipo());
if(login.getMensaje_id() != 0)
{
FacesContext.getCurrentInstance().addMessage("usuarioPassword",
new FacesMessage(login.getMensaje()));
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.invalidateSession();
return "";
}
else
{
return login.getMensaje();
}
}
//logout method
public void logout()
{
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.invalidateSession();
try
{
ec.redirect(ec.getRequestContextPath() + "/index.jsf");
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
I am not putting the LoginService() class code because it is simply going to the database and checking if the user exists or not. I just want to know what do I need to do so that the session is only stored when the user successfully logs in.
Notice that on the login() method of the controller class, when the user fails to log in, I display an error message. After that, I have the following piece of code:
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.invalidateSession();
I am destroying the session. I want to avoid to have to do that!
I understand that you basically want to clear out the login form on login fail. You could just clear out the bean properties representing form data.
login = new Login();
loginTipo = null;
The bean itself does not necessarily need to be session scoped. It can be request scoped and you could manually put the user object in session. I understand Login represents the user. In that case, once the login is successful, do as follows:
ec.invalidateSession();
ec.getSessionMap().put("login", login);
return "/userhome?faces-redirect=true";
The logged-in user will be available in the session scope as #{login}.
Explicitly invalidating the session right before login is a good security practice to avoid session fixation attacks. Invalidating it on every login fail is indeed unnecessary.
I've tried to implement the basic notification system for a basic social network with p:poll on view layer and a simple NotificationService class which gets the new notifications from DB and refreshes the notifications list of NotificationBean which is viewscoped for each user. Process flow similar to this:
-Poll calls NotificationBean.getNewNotifications for example every 15 sec.
--getNewNotifications calls NotificationService and DAO methods
---notificationList of user is refreshed
----DataTable on view layer shows new notifications
But the concern of p:poll is about it's performance because it sends a query at every interval expiration.
PrimeFaces has PrimePush which based on Atmosphere Framework, it opens web-sockets and seems like more suitable for creating notifications system.
But I don't know which components and which properties of them should be used. It has p:socket component with channel property. Should I use usernames as a channel values? Below code coming from PrimeFaces showcase and summarizes the last sentences:
<p:socket onMessage="handleMessage" channel="/notifications" />
As far as I understood from this showcase example this p:socket listens notifications channel. And pusher code snippet is:
PushContext pushContext = PushContextFactory.getDefault().getPushContext();
pushContext.push("/notifications", new FacesMessage(summary, detail));
But this will notify all user pages, I need a pusher which notifies specific user. Let say there are 2 users and assume that User1 adds User2 as a friend. There must be sth. like that:
pushContext.push("User2/notifications", new FacesMessage("friendship request", "from User1"));
But I am not sure this is the correct usage for this kind of functional requirement or not. Considering scalability of the app there can be expensive cost of opening so many channels per a process.
Thanks for helping.
PrimeFaces push supports one or more channels to push. To be able to create private channels for specific reasons; for example per user like in your case, you can create more than one channels. I had used unique ids for this purpose.
Basically, I've implemented a managed bean which is application scoped that handles user channel matching which should be considered. You can maintain it in different ways.
#ManagedBean
#ApplicationScoped
public class ChannelsBean {
Map<String, String> channels = new HashMap<String, String>();
public void addChannel(String user, String channel) {
channels.put(user, channel);
}
public String getChannel(String user) {
return channels.get(user);
}
}
Then inject this bean in your backing bean which sends notifications.
#ManagedBean
#SessionScoped
public class GrowlBean {
private String channel;
#ManagedProperty(value = "#{channelsBean}")
private ChannelsBean channels;
private String sendMessageUser;
private String user;
#PostConstruct
public void doPostConstruction() {
channel = "/" + UUID.randomUUID().toString();
channels.addChannel(user, channel);
}
public void send() {
PushContext pushContext = PushContextFactory.getDefault().getPushContext();
pushContext.push(channels.getChannel(sendMessageUser), new FacesMessage("Hi ", user));
}
//Getter Setters
}
You should give the channel value to p:socket. Here is the kickoff example of the page;
<p:growl widgetVar="growl" showDetail="true" />
<h:form>
<p:panel header="Growl">
<h:panelGrid columns="2">
<p:outputLabel for="user" value="User: " />
<p:inputText id="user" value="#{growlBean.sendMessageUser}" required="true" />
</h:panelGrid>
<p:commandButton value="Send" actionListener="#{growlBean.send}" />
</p:panel>
</h:form>
<p:socket onMessage="handleMessage" channel="#{growlBean.channel}" />
<script type="text/javascript">
function handleMessage(facesmessage) {
facesmessage.severity = 'info';
growl.show([facesmessage]);
}
</script>
For scalability issues, you should maintain the active or inactive channels. You can remove the one which is not in session or inactive for some time. Remove channels by using #PreDestroy annotation when beans are destroying. There is one channel for one user session in my solution.
My suggestion is; do not use usernames explicitly on the pages. It is not good for security reasons.
Simple question from a beginner at JSF:
I have very simple JSF form:
<h:form>
<p>#{messages.loginTextfieldUsername}</p>
<h:inputText value="#{userServiceImpl.user.name}" />
<p>#{messages.loginTextfieldPassword}</p>
<h:inputSecret value="#{userServiceImpl.user.password}" />
<h:commandButton value="#{messages.loginButtonLogin}" action="#{userServiceImpl.authenticateUser}" />
</h:form>
The userServiceImpl class is:
#Named
#RequestScoped
public class UserServiceImpl implements UserService {
private UserSession userSession;
private User user;
#Inject
public UserServiceImpl(UserSession userSession) {
this.userSession = userSession;
}
#PostConstruct
public void prepareService() {
user = new User();
}
#Override
public View authenticateUser() {
userSession.setLoggedUser(user);
return View.MAIN;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
My goal is pretty simple: when the user hits the login button, I want to authenticate the user.
The problem is:
When the authenticate method is called, the User attributes are null. I debugged the application and the getUser method is called and the values are properly set, but at some point (which I did not find [yet]) before the authenticateUser is called the User attributes are set to null...
I'm aware that this is a pretty basic question... but are you able to point out where my mistake is?
Thanks.
Based on your previous question, you seem to have experimented with <managed-bean-scope> of none in faces-config.xml for some reason. The problem symptoms matches exactly when using #ManagedBean #NoneScoped. You seem to have configured this bean in faces-config.xml as well on a none scope which totally explains this problem. With the none scope, a brand new bean instance is been created everytime when #{userServiceImpl} is been evaluated in EL. Your form submit has thus effectively created 3 beans: one where the user name is set, another one where user password is set and another one where action is invoked.
You need to remove the managed bean configuration from faces-config.xml. You should not use it when you intend to use #Inject (or #ManagedBean). The faces-config.xml way of configuring beans is a leftover from old JSF 1.x ages when annotations weren't available. As of JSF 2.x it would only override any bean management annotations.
I have a JSF page in which I show the details of a given user:
<h:form>
<p>
<h:outputLabel value="User Name" for="userName" />
<h:outputText id="userName" value="#{userController.user.name}" />
</p>
<p>
<h:outputLabel value="Email" for="email" />
<h:outputText id="email" value="#{userController.user.email}" />
</p>
<p>
<h:commandLink value="Edit" action="#{userController.edit(userController.user.id)}" />
</p>
</h:form>
This works fine with the exception of the Edit commandLink. When I click it, I expect userController.edit to be passed the ID of the user that's currently being rendered on the page.
However looks like as userController is a request scoped bean that has a #PostConstruct method that assigns a new User to the user field, always 0 is being passed to the action method as the ID of a new instance of User is null which apparently gets converted to 0.
How can I fix this controller/page, without changing the scope of the controller, to pass the correct ID to the edit action? Here's the code for the controller:
#Model
public class UserController {
#Inject
#UserRepository
private EntityManager entityManager;
#Inject
private UserTransaction tx;
private User user;
public String edit(Long id) {
System.out.println("id = " + id);
// fetch the user with id from the db
return "edit";
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#PostConstruct
private void init() {
user = new User();
}
}
The approach is weird but I can imagine when you need this. (e.g. you don't wand to write to session and flash scope don't work in distributed environment etc.). BTW if you can't use session scope consider using flash scope.
Apart from correctness of the approach, try adding aditional field to the bean:
protected Long currentId;
public Long getCurrentId() {
return currentId == null ? user.getId() : currentId;//or simply return it, I don't know how you play with the user field
}
public void setCurrentId(Long currentId) {
this.currentId = currentId;
}
Remove parameter from edit action and use currentId field, and to your view add param (hidden field will not work here):
<f:viewParam name="id" value="#{userController.currentId}" />
Then in action you can get user id from the previous view simply accessing this.currentId.
And BTW, try load your User from another place, not int #PostConstruct. If you create user in #PostConstruct then when you are in edit method the currentId will be from previous view, and user.id will be from #PostConstruct. So if you don't want to store user in session scope, but use only request scope you have to persist it immediately after creation (e.g. in database). In edit method you have to get it back using currentId.