Realización de autenticación de usuario en Java EE/JSF utilizando j_security_check
java-ee authentication (4)
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 los mecanismos centrales de Java EE 6 (inicio / verificación de permisos / desconexiones) con información de usuario contenida en un JPA entidad. El tutorial de Oracle Java EE es un poco escaso en esto (solo maneja servlets).
Esto es sin hacer uso de un marco completo diferente, como Spring-Security (acegi), o Seam, pero tratando de mantenerse con la nueva plataforma Java EE 6 (perfil web) si es posible.
Cabe mencionar que es una opción dejar por completo los problemas de autenticación al controlador frontal, por ejemplo, un servidor web Apache y evaluar el HttpServletRequest.getRemoteUser () en su lugar, que es la representación JAVA para la variable de entorno REMOTE_USER. Esto también permite el inicio de sesión sofisticado de diseños como la autenticación Shibboleth. El filtrado de 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.
Después de buscar en la Web y probar de muchas maneras diferentes, esto es lo que sugeriría para la autenticación de Java EE 6:
Configurar el reino de la 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 basados en el nombre de usuario y contraseñas con hash MD5 en la tabla de mi base de datos:
http://blog.gamatam.com/2009/11/jdbc-realm-setup-with-glassfish-v3.html
Nota: la publicación habla sobre un usuario y una tabla de grupo en la base de datos. Tenía una clase de usuario con un atributo enum UserType mapeado 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 columna de grupo y funcionó bien.
Usar autenticación de formulario:
Siguiendo la publicación del blog anterior, configure su web.xml y sun-web.xml, pero en lugar de usar autenticación BASIC, use FORM (en realidad, no importa cuál use, pero terminé usando FORMULARIO). Use el HTML estándar, no el JSF.
A continuación, utilice la sugerencia de BalusC anterior sobre la inicialización lenta de la información del usuario de la base de datos. Sugirió hacerlo en un frijol administrado obteniendo el principal del contexto de rostros. Usé, en cambio, 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 EJB SessionInformation
.
Cerrar sesión:
También busqué la mejor manera de desconectarme. El mejor que he encontrado es el uso de 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 tarde considerando la fecha de la pregunta, espero que esto ayude a otras personas que terminan aquí en Google, tal como yo lo hice.
Ciao,
Vítor Souza
El problema HttpServletRequest.login no establece el estado de autenticación en la sesión se ha solucionado en 3.0.1. Actualiza glassfish a la última versión y listo.
La actualización es bastante sencilla:
glassfishv3/bin/pkg set-authority -P dev.glassfish.org
glassfishv3/bin/pkg image-update
Supongo que quiere autenticación basada en formularios usando descriptores de implementación y j_security_check
.
También puede hacer esto en JSF simplemente usando los mismos nombres predefinidos de campo j_username
y j_password
como se demostró 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 hacer una carga diferida en el User
getter para verificar si el User
ya está conectado y si no, luego verificar si el Principal
está presente en la solicitud y, si es así, obtener el User
asociado con j_username
.
package com..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 User
es obviamente accesible en JSF EL por #{auth.user}
.
Para HttpServletRequest#logout()
haga una HttpServletRequest#logout()
(¡y configure User
null!). Puede obtener un control de HttpServletRequest
en JSF por ExternalContext#getRequest()
. También puede invalidar la sesión por completo.
public String logout() {
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
return "login?faces-redirect=true";
}
Para el remanente (definir usuarios, roles y restricciones en el descriptor de implementación y el reino), solo siga el tutorial de Java EE 6 y la documentación del servletcontainer 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 j_security_check
que puede no ser accesible por un operador en algunos servletcontainers. En este caso, puede usar un formulario JSF completo y un bean con propiedades de username
y password
, y un método de login
que se ve 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 tiene un ámbito de beans gestionados 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 forma, el User
puede acceder a JSF EL mediante #{user}
.