views:

3620

answers:

5

I'm wondering what the current state of art recommendation is regarding user authentication for a web application making use of JSF 2.0 (and if any components do exist) and JEE6 core mechanisms (login/check permissions/logouts) with user information hold in a JPA entity. The Sun 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.

Thanks, Niko

+3  A: 

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">Username:</h:outputLabel>
    <h:inputText id="j_username" required="true" />
    <h:message for="j_username" />
    <br />
    <h:outputLabel for="j_password">Password:</h:outputLabel>
    <h:inputSecret id="j_password" required="true" />
    <h:message for="j_password" />
    <br />
    <h:commandButton value="Login" />
</form>

Further you can 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 UserManager {

    private User user; // The JPA entity.

    public User getUser() {
        if (user == null) {
            Principal principal = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
            if (principal != null) {
                user = userDAO.find(principal.getName()); // Find User by j_username.
            }
        }
        return user;
    }

}

The User is obviously accessible in JSF EL by #{userManager.user}.

To logout do a HttpServletRequest#logout() (and set User to null!) or do a HttpSession#invalidate(). You can get a handle of the HttpServletRequest in JSF by ExternalContext#getRequest().

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 2: you can also grab 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:

public void login() {
    FacesContext context = FacesContext.getCurrentInstance();
    HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
    try {
        request.login(this.username, this.password);
        this.user = userDAO.find(this.username, this.password);
    } catch (ServletException e) {
        // Handle unknown username/password in request.login().
        context.addMessage(null, new FacesMessage("Unknown login"));
    }
}

I think the above is finally the best way.

BalusC
Perfect, many Thanks @BalusC for your fast and detailed answer!
ngeek
I updated the question to include a disclaimer that dispatching into `j_security_check` might not work on all servletcontainers.
BalusC
Link from the Java Tutorial on Using Programmatic Security with Web Applications: http://java.sun.com/javaee/6/docs/tutorial/doc/gjiie.html (using Servlets):On a servlet class you can use:`@WebServlet(name="testServlet", urlPatterns={"/ testServlet "}) @ServletSecurity(@HttpConstraint(rolesAllowed = {"testUser", "admin”}))`And on a per method level:`@ServletSecurity(httpMethodConstraints={ @HttpMethodConstraint("GET"), @HttpMethodConstraint(value="POST", rolesAllowed={"testUser"})})`
ngeek
And your point being..? Whether this is applicable in JSF? Well, in JSF there's only one servlet, the `FacesServlet` and you can't (and don't want to) modify it.
BalusC
Sure thing, this was just for completeness sake, and cross-link if someone to look how the solutions looks for building with your own servlet.
ngeek
Another approach seems to be making use of @SecurityLogin annotation on a managed bean as described by the article "Improving JSF Security Configuration With Secured Managed Beans" (although written on JSF 1.2 times, it still seems useful for JSF 2.0) onhttp://blogs.sun.com/enterprisetechtips/entry/improving_jsf_security_configuration_with
ngeek
@BalusC, are you aware of any containers that currently do not support this, or more precisely, we should avoid if wanting to do Java EE 6 with Servlet 3? Are any ones that one would expect to be compliant having issues with this?
jamiebarrow
As of now, Servlet 3.0 is only available in Glassfish v3. Tomcat 7.0 is still underway.
BalusC
+2  A: 

Thanks for your input @BalusC. I am using the request.login() method myself, however after the request is finished, the principal is gone and the servlet security constraints kick in and bounce my back to my login page.

For example: I navigate to .../secret/Users.jsf before logging in. The security constraints setup in web.xml knows that I have to be an admin and bounces me to my login form (plain login form, not j_security....) I log in successfully via the request.login() in my backing bean and am taken to ../secret/Users.jsf Once I do anything, including refreshing the screen, it bounces me back to the login page.

I've traced the problem and it's because the principal is only alive during login request. Once the request is completed, the principal is gone and a new log in is required. Using BASIC or FORM authentication (with j_security..) works which is telling me that they are doing something more than a request.login() internally.

Do we have to getUserPrincipal() and store it in session scope for the security constraints in web.xml to work properly?

Update I found this link describing a bug in Glassfish v3 (which I'm using) and it sounds like it's causing the issue with the principal not being persistent after the initial request falls out of scope: https://glassfish.dev.java.net/issues/show_bug.cgi?id=11340

Hope this helps, and if anyone knows anything different, please post it.

Jon
+1  A: 

Yes this issue https://glassfish.dev.java.net/issues/show_bug.cgi?id=11340 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

kind regards, hans

Hans Bacher
A: 

Using Hans Bacher method, I did update my glassfish windows distribution but after that the server cannot start anymore. I had no errors during updating.

monte_carlo
+2  A: 

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 {
   // Destroys the session for this user.
   request.getSession(false).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

Vítor Souza