Performing user authentication in Java EE / JSF using j_security_check
Asked Answered
P

4

160

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.

Pomiferous answered 5/2, 2010 at 11:45 Comment(0)
T
86

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

Tunstall answered 8/6, 2010 at 12:32 Comment(7)
A small word of advice: you're using request.getSession(false) and calling invalidate() on that. request.getSession(false) may return null if there's no session. Better check if it's null first ;)Connotative
@Vitor: Hi..Would you like to say something about when it is good to move from container based security to alternatives like shiro or others? See more focussed question here:stackoverflow.com/questions/7782720/…Ronnaronnholm
It seems like the Glassfish JDBC Realm does not support storing salted passwords hashes. Is it really best practice to use it in that case?Glanville
Sorry, can't help you. I'm not a Glassfish expert. Maybe ask that question in a new thread to see what people say?Sulfuric
Lii, you can work with salted using the glassfish container. Configure your healm not to use any hash. It'll compare the plain value you insert for password on HttpServletResponse#login(user, password), that way you can just get from DB the user's salt, iterations and whatever you use for salting, hash the password the user entered using that salt and then ask the container to authenticate with HttpServletResponse#login(user, password).Magnetometer
emportella, that sounds like a great solution! That's the last missing piece of information for this otherwise very useful answer.Glanville
Also see this question. There is an open source loginmodule for glassfish and firefly that uses salted passwords: m9aertner/PBKDF2. It works well.Shrewish
H
154

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}.

Hasty answered 5/2, 2010 at 12:31 Comment(31)
I updated the question to include a disclaimer that dispatching into j_security_check might not work on all servletcontainers.Hasty
Link from the Java Tutorial on Using Programmatic Security with Web Applications: 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"})})Pomiferous
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.Hasty
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.Pomiferous
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) on blogs.sun.com/enterprisetechtips/entry/…Pomiferous
@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?Blakley
As of now, Servlet 3.0 is only available in Glassfish v3. Tomcat 7.0 is still underway.Hasty
@Hasty Your UserManager class is SessionScoped. Isn't that impractical, since you are also referencing the user entity in it? If you change any attributes on the user object (at some other place), your user instance would be outdated.Janitor
@Theo: No, it isn't. The logged-in user should be stored sessionwide. The some other place should just get the user from the usermanager and alter it (preferably by the usermanager's methods), not obtain a different instance.Hasty
@Hasty I agree that the user principal should be stored session wide. But the user JPA entity can contain a lot more than the user principal. Why not get a fresh instance from the database on every request? Because the user object might get changed in some backend methods that doesn't use any classes from the frontend (such as the UserManager).Janitor
@Theo: because it's inefficient in an average webapplication. You can however just do so if the specific environment/functional requirements require it.Hasty
I ended up using progmmatic login with HttpServletRequest#login(). If, like me, you are looking for a way to redirect the user to the page he came from I suggest looking at this question link @Hasty maybe you can reference this question under Update 2?Celeski
@BalusC:Would you like to say something about when it is good to move from container based security to alternatives like shiro or others? See more focussed question here:stackoverflow.com/questions/7782720/…Ronnaronnholm
There's one problem I've found - when calling request.login() from the HttpServletRequest implementation, the SSO cookie is not created by Glassfish 3.1 - you have to call the action through a form (j_security_check). That smells!Colonist
Concerning the lazy initialization, I would prefer to put this stuff in a @PostConstruct bean method (@PostConstruct void initialize() { if (user == null) { Principal principal =..., and let the getUser() simply returning user.Geotaxis
@Denis: that won't work if the session scoped bean is created before user is logged in.Hasty
What if I have no choice i.e. JavaEE 5 + JSF 2. Is there a way to achieve the same result as HttpServletRequest#login?Grill
@Hasty - When you say the above is the best way do you mean using j_security_check or a programmatic login?Darling
@simgineer: Programmatic login. It allows you more fine grained control over validation.Hasty
@Hasty - I switched to the programmatic mode as suggested so I could set the user instance immediately without using a filter however when I use the login method above it doesn't take me to the requested url after a successful login. Are there an additional steps we have to do to have the user forwarded or redirected to the original resource he requested? Or perhaps would you know what I have not configured incorrectly?Darling
@simgineer: the requested URL is available as a request attribute with the name as definied by RequestDispatcher.FORWARD_REQUEST_URI. Request attributes are in JSF available by ExternalContext#getRequestMap().Hasty
@Hasty - Is this correct call to get the map in UserBean.login()? Map<String, String> rpMap = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap(); If so my map has 5 entries in it which did not have the original resource requested. Here are my map contents: rpmap: key: j_idt25 value: j_idt25 key: j_idt25:username value: admin key: j_idt25:password value: admin key: j_idt25:j_idt33 value: Login key: javax.faces.ViewState value: 1617763866821071380:1071397943707136043Darling
For a way to add password salting to this solution (which you should) see emportella's comment on Vítor E. Silva Souza's answer.Glanville
@BalusC, in case of {{HttpServletRequest#login()}} method, how would one configure {{login-config}} in web.xml? And how to achieve so that the login page would be shown when trying to access restricted resources while not logged in?Leffert
@Vsevolod: Just the same way. The only difference is that login page submits via a JSF form to a JSF bean with HttpServletRequest#login() instead via a plain HTML form to /j_security_check.Hasty
@BalusC, also access to the login page needs to be unrestricted, that was my problem.Leffert
@Hasty I am also using programmatic login now. I do some some checks before using 'request.login', to see if the user is verified by an admin from my webapp, or if the user is active (my webapp allows Admins to acticate and deactive users). My question is that if an user would modify my web page and change the form so it uses the j_security_check action, I would think he could bypass my programmatic login, right? Do I need to change the login-config or something, so this can't happen. Or do you have an other suggestion. I do think it would be an unlike scencario, but it could chappen.Cia
@Hasty I confirmed and with the dev console in the browser replaced the html where the form from JSF was, with a pure HTML form with j_security_check stuff and could login. I have this idea to set a variable in the session only when doing programmatical login, and in a filter on the secured area check if this variable is set, if not invalidate session and redirect to login page.Cia
@Hasty Why did you put the user entity in session map? the entity has the password field, isn't that a security risk ? Should I empty the the password field before I add it to the sessionMap?Eindhoven
@usertest: where in the code did you see that the User entity has a password property?Hasty
@Hasty User user = userService.find(username, password); I assumed that user is the entity with email an password, Am I wrong?Eindhoven
T
86

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

Tunstall answered 8/6, 2010 at 12:32 Comment(7)
A small word of advice: you're using request.getSession(false) and calling invalidate() on that. request.getSession(false) may return null if there's no session. Better check if it's null first ;)Connotative
@Vitor: Hi..Would you like to say something about when it is good to move from container based security to alternatives like shiro or others? See more focussed question here:stackoverflow.com/questions/7782720/…Ronnaronnholm
It seems like the Glassfish JDBC Realm does not support storing salted passwords hashes. Is it really best practice to use it in that case?Glanville
Sorry, can't help you. I'm not a Glassfish expert. Maybe ask that question in a new thread to see what people say?Sulfuric
Lii, you can work with salted using the glassfish container. Configure your healm not to use any hash. It'll compare the plain value you insert for password on HttpServletResponse#login(user, password), that way you can just get from DB the user's salt, iterations and whatever you use for salting, hash the password the user entered using that salt and then ask the container to authenticate with HttpServletResponse#login(user, password).Magnetometer
emportella, that sounds like a great solution! That's the last missing piece of information for this otherwise very useful answer.Glanville
Also see this question. There is an open source loginmodule for glassfish and firefly that uses salted passwords: m9aertner/PBKDF2. It works well.Shrewish
I
7

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.

Istanbul answered 5/11, 2012 at 11:4 Comment(0)
O
4

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
Omnipresent answered 20/5, 2010 at 19:6 Comment(1)
link is broken. which issue were you referring to?Darling

© 2022 - 2024 — McMap. All rights reserved.