Realización de autenticación de usuario en Java EE / JSF utilizando j_security_check

156

Me pregunto cuál es el enfoque actual con respecto a la autenticación de usuario para una aplicación web que utiliza JSF 2.0 (y si existen componentes) y mecanismos principales de Java EE 6 (inicio de sesión / verificar permisos / cierres de sesión) con información de usuario en un JPA entidad. El tutorial de Oracle Java EE es un poco escaso en esto (solo maneja servlets).

Esto es sin utilizar un marco completamente diferente, como Spring-Security (acegi) o Seam, pero si es posible, tratar de seguir con la nueva plataforma Java EE 6 (perfil web).

ngeek
fuente

Respuestas:

85

Después de buscar en la Web y probar muchas formas diferentes, esto es lo que sugeriría para la autenticación Java EE 6:

Configure el reino de seguridad:

En mi caso, tenía los usuarios en la base de datos. Así que seguí esta publicación de blog para crear un Reino JDBC que pudiera autenticar a los usuarios según el nombre de usuario y las contraseñas hash MD5 en mi tabla de base de datos:

http://blog.gamatam.com/2009/11/jdbc-realm-setup-with-glassfish-v3.html

Nota: la publicación habla de un usuario y una tabla de grupo en la base de datos. Tenía una clase de usuario con un atributo de enumeración UserType asignado a través de anotaciones javax.persistence a la base de datos. Configuré el reino con la misma tabla para usuarios y grupos, usando la columna userType como la columna del grupo y funcionó bien.

Usar autenticación de formulario:

Aún siguiendo la publicación de blog anterior, configure su web.xml y sun-web.xml, pero en lugar de usar la autenticación BÁSICA, use FORM (en realidad, no importa cuál use, pero terminé usando FORM). Use el HTML estándar, no el JSF.

Luego use la sugerencia de BalusC anterior sobre la inicialización diferida de la información del usuario desde la base de datos. Sugirió hacerlo en un bean administrado obteniendo el director del contexto de caras. En su lugar, utilicé un bean de sesión con estado para almacenar información de sesión para cada usuario, así que inyecté el contexto de la sesión:

 @Resource
 private SessionContext sessionContext;

Con el director, puedo verificar el nombre de usuario y, usando el Administrador de Entidades EJB, obtener la información del Usuario de la base de datos y almacenarla en mi SessionInformationEJB.

Cerrar sesión:

También busqué la mejor forma de cerrar sesión. El mejor que he encontrado es usar un 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());
  }
 }

Aunque mi respuesta es muy tardía considerando la fecha de la pregunta, espero que esto ayude a otras personas que terminan aquí desde Google, tal como lo hice yo.

Ciao

Vítor Souza

Vítor E. Silva Souza
fuente
15
Un pequeño consejo: estás usando request.getSession (false) y estás llamando invalidate () en eso. request.getSession (false) puede devolver nulo si no hay sesión. Mejor comprobar si es nulo primero;)
Arjan Tijms
@Vitor: Hola ... ¿Te gustaría decir algo sobre cuándo es bueno pasar de la seguridad basada en contenedores a alternativas como shiro u otras? Vea una pregunta más centrada aquí: stackoverflow.com/questions/7782720/…
Rajat Gupta
Parece que Glassfish JDBC Realm no admite el almacenamiento de hashes de contraseñas saladas. ¿Es realmente la mejor práctica usarlo en ese caso?
Lii
Lo siento, no puedo ayudarte. No soy un experto en Glassfish. ¿Quizás hacer esa pregunta en un nuevo hilo para ver lo que dice la gente?
Vítor E. Silva Souza
1
Lii, puedes trabajar con sal utilizando el recipiente de pez de vidrio. Configure su healm para no usar ningún hash. Comparará el valor normal que inserte para la contraseña HttpServletResponse#login(user, password), de esa manera puede obtener de DB la sal del usuario, las iteraciones y lo que sea que use para salar, agregue la contraseña que el usuario ingresó usando esa sal y luego solicite al contenedor que se autentique con HttpServletResponse#login(user, password).
emportella
152

Supongo que desea autenticación basada en formularios mediante descriptores de implementación y j_security_check.

También puede hacer esto en JSF simplemente usando los mismos nombres de campo predefinidos j_usernamey j_passwordcomo se muestra en el tutorial.

P.ej

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

Puede realizar una carga Userlenta en el getter para verificar si Userya está conectado y, de lo contrario, verificar si Principalestá presente en la solicitud y, de ser así, obtener el Userasociado 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;
    }

}

El Useres obviamente accesible en JSF EL por #{auth.user}.

Para cerrar sesión, haga un HttpServletRequest#logout()(y configúrelo Usercomo nulo) Puede obtener un identificador de HttpServletRequesten JSF por ExternalContext#getRequest(). También puede simplemente invalidar la sesión por completo.

public String logout() {
    FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
    return "login?faces-redirect=true";
}

Para el remanente (que define usuarios, roles y restricciones en el descriptor de implementación y el ámbito), solo siga el tutorial Java EE 6 y la documentación del contenedor de servlet de la manera habitual.


Actualización : también puede usar el nuevo Servlet 3.0 HttpServletRequest#login()para hacer un inicio de sesión programático en lugar de usar el j_security_checkcual no puede ser alcanzado por un despachador en algunos contenedores de servlet. En este caso se puede usar un formulario JSF fullworthy y un grano con usernamey passwordpropiedades y un loginmétodo que se ven así:

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

Y esta vista abarcó el bean administrado que también recuerda la página solicitada inicialmente:

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

De esta manera Userse puede acceder a JSF EL por #{user}.

BalusC
fuente
1
Actualicé la pregunta para incluir un descargo de responsabilidad por el cual el envío j_security_checkpodría no funcionar en todos los contenedores de servlet.
BalusC
1
Enlace del Tutorial de Java sobre el uso de seguridad programática con aplicaciones web: java.sun.com/javaee/6/docs/tutorial/doc/gjiie.html (usando Servlets): en una clase de servlet puede usar: @WebServlet(name="testServlet", urlPatterns={"/ testServlet "}) @ServletSecurity(@HttpConstraint(rolesAllowed = {"testUser", "admin”})) Y por método nivel: @ServletSecurity(httpMethodConstraints={ @HttpMethodConstraint("GET"), @HttpMethodConstraint(value="POST", rolesAllowed={"testUser"})})
ngeek
3
¿Y tu punto es ...? Si esto es aplicable en JSF? Bueno, en JSF solo hay un servlet, FacesServlety no puedes (y no quieres) modificarlo.
BalusC
1
@BalusC - Cuando dice que lo anterior es la mejor manera, ¿quiere decir que usa j_security_check o un inicio de sesión programático?
simgineer
3
@impresor: la URL solicitada está disponible como un atributo de solicitud con el nombre definido por RequestDispatcher.FORWARD_REQUEST_URI. Los atributos de solicitud están en JSF disponibles por ExternalContext#getRequestMap().
BalusC
7

Cabe mencionar que es una opción dejar completamente los problemas de autenticación al controlador frontal, por ejemplo, un servidor web Apache y evaluar el HttpServletRequest.getRemoteUser (), que es la representación de JAVA para la variable de entorno REMOTE_USER. Esto también permite diseños de inicio de sesión sofisticados, como la autenticación Shibboleth. Filtrar solicitudes a un contenedor de servlets a través de un servidor web es un buen diseño para entornos de producción, a menudo se usa mod_jk para hacerlo.

Matthias Ronge
fuente