web-dev-qa-db-fra.com

Authentification utilisateur dans Java EE / JSF en utilisant j_security_check

Je me demande quelle est l’approche actuelle en ce qui concerne l’authentification des utilisateurs pour une application Web utilisant JSF 2.0 (et des composants existants) et Java EE 6 (connexion/vérification des autorisations/logouts) avec des informations utilisateur dans une entité JPA: Le tutoriel Oracle Java EE est un peu clairsemé à ce sujet (ne traite que les servlets).

Ceci est sans en utilisant un tout autre framework, comme Spring-Security (acegi), ou Seam, mais en essayant de coller avec espoir à la nouvelle Java EE 6 (profil web) si possible.

155
ngeek

Après avoir cherché sur le Web et essayé de nombreuses façons différentes, voici ce que je suggérerais pour Java EE 6:

Configurez le domaine de la sécurité:

Dans mon cas, j'avais les utilisateurs dans la base de données. J'ai donc suivi cet article de blog pour créer un royaume JDBC pouvant authentifier les utilisateurs en fonction du nom d'utilisateur et des mots de passe hachés MD5 dans ma table de base de données:

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

Remarque: la publication parle d'un utilisateur et d'une table de groupe dans la base de données. J'avais une classe User avec un attribut UserType enum mappé via des annotations javax.persistence à la base de données. J'ai configuré le domaine avec la même table pour les utilisateurs et les groupes, en utilisant la colonne userType comme colonne de groupe et tout s'est bien déroulé.

Utiliser l'authentification par formulaire:

Toujours en suivant le blog ci-dessus, configurez votre web.xml et Sun-web.xml, mais au lieu d'utiliser l'authentification BASIC, utilisez FORM (en fait, peu importe celui que vous utilisez, mais j'ai fini par utiliser FORM). Utilisez le code HTML standard, pas le fichier JSF.

Ensuite, utilisez le conseil de BalusC ci-dessus sur l’initialisation paresseuse des informations d’utilisateur de la base de données. Il a suggéré de le faire dans un haricot géré en extrayant le principal du contexte des visages. Au lieu de cela, j'ai utilisé un bean de session avec état pour stocker les informations de session pour chaque utilisateur. J'ai donc injecté le contexte de session:

 @Resource
 private SessionContext sessionContext;

Avec le principal, je peux vérifier le nom d'utilisateur et, à l'aide du gestionnaire d'entité EJB, obtenir les informations sur l'utilisateur de la base de données et les stocker dans mon SessionInformation EJB.

Se déconnecter:

J'ai aussi regardé autour de moi pour trouver le meilleur moyen de me déconnecter. Le meilleur que j'ai trouvé utilise 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());
  }
 }

Bien que ma réponse soit très tardive compte tenu de la date de la question, j'espère que cela aidera d'autres personnes qui se retrouvent ici chez Google, tout comme je l'ai fait.

Ciao,

Vítor Souza

84

Je suppose que vous voulez authentification basée sur un formulaire using descripteurs de déploiement et j_security_check.

Vous pouvez également le faire dans JSF en utilisant simplement les mêmes noms de champs prédéfinis j_username Et j_password, Comme indiqué dans le didacticiel.

Par exemple.

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

Vous pouvez effectuer un chargement paresseux dans le User getter pour vérifier si le User est déjà connecté et si ce n’est pas le cas, vérifiez si le Principal est présent dans la demande et, le cas échéant, , puis obtenez le User associé à 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;
    }

}

Le User est évidemment accessible dans JSF EL par #{auth.user}.

Pour vous déconnecter, faites un HttpServletRequest#logout() (et définissez User sur null!). Vous pouvez obtenir un descripteur de HttpServletRequest dans JSF par ExternalContext#getRequest() . Vous pouvez également simplement invalider la session.

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

Pour le reste (définition des utilisateurs, des rôles et des contraintes dans le descripteur de déploiement et le domaine), suivez simplement le tutoriel Java EE 6 et la documentation servletcontainer].


Update : vous pouvez également utiliser le nouveau Servlet 3.0 HttpServletRequest#login() ) pour effectuer une connexion par programme au lieu de j_security_check Qui peut ne pas être accessible en soi par un répartiteur dans certains conteneurs de servlets. Dans ce cas, vous pouvez utiliser un formulaire JSF complet et un bean avec les propriétés username et password et une méthode login qui ressemble à ceci:

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

Et cette vue couvre le bean géré qui se souvient également de la page initialement demandée:

@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 cette façon, le User est accessible dans JSF EL par #{user}.

148
BalusC

Il convient de mentionner qu’il est possible de laisser complètement les problèmes d’authentification au contrôleur frontal, par exemple. un serveur Web Apache et évalue HttpServletRequest.getRemoteUser () à la place, ce qui correspond à la représentation Java de la variable d'environnement REMOTE_USER. Cela permet également de consigner de manière sophistiquée des conceptions telles que l'authentification Shibboleth. Filtrage des demandes sur un servlet Le conteneur via un serveur Web est une bonne conception pour les environnements de production, mod_jk est souvent utilisé pour cela.

7
Matthias Ronge

Le problème HttpServletRequest.login ne définit pas l'état d'authentification dans la session a été corrigé dans la version 3.0.1. Mettez à jour glassfish avec la dernière version et vous avez terminé.

La mise à jour est assez simple:

glassfishv3/bin/pkg set-authority -P dev.glassfish.org
glassfishv3/bin/pkg image-update
4
Hans Bacher