Esecuzione dell'autenticazione utente in Java EE / JSF utilizzando j_security_check


156

Mi chiedo quale sia l'approccio attuale per quanto riguarda l'autenticazione utente per un'applicazione Web che utilizza JSF 2.0 (e se esistono componenti) e i meccanismi principali di Java EE 6 (login / verifica autorizzazioni / logout) con le informazioni utente conservate in un JPA entità. Il tutorial Oracle Java EE è un po 'scarso su questo (gestisce solo servlet).

Questo è senza fare uso di un intero altro framework, come Spring-Security (acegi) o Seam, ma se possibile provando a rimanere con la nuova piattaforma Java EE 6 (profilo web).

Risposte:


85

Dopo aver cercato sul Web e provato molti modi diversi, ecco cosa suggerirei per l'autenticazione Java EE 6:

Imposta il regno della sicurezza:

Nel mio caso, avevo gli utenti nel database. Quindi ho seguito questo post del blog per creare un regno JDBC in grado di autenticare gli utenti in base al nome utente e alle password con hash MD5 nella mia tabella del database:

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

Nota: il post parla di un utente e di una tabella di gruppo nel database. Avevo una classe User con un attributo enum UserType mappato tramite annotazioni javax.persistence al database. Ho configurato il regno con la stessa tabella per utenti e gruppi, usando la colonna userType come colonna del gruppo e ha funzionato bene.

Usa autenticazione modulo:

Sempre seguendo il precedente post sul blog, configura web.xml e sun-web.xml, ma invece di usare l'autenticazione BASIC, usa FORM (in realtà, non importa quale usi, ma alla fine ho usato FORM). Usa l'HTML standard, non il JSF.

Quindi utilizzare il suggerimento di BalusC sopra sull'inizializzazione lenta delle informazioni utente dal database. Ha suggerito di farlo in un bean gestito ottenendo il principale dal contesto delle facce. Ho usato, invece, un bean di sessione con stato per memorizzare le informazioni sulla sessione per ciascun utente, quindi ho inserito il contesto della sessione:

 @Resource
 private SessionContext sessionContext;

Con il principale, posso controllare il nome utente e, utilizzando l'EJB Entity Manager, ottenere le informazioni sull'utente dal database e archiviarle nel mio SessionInformationbean.

Disconnettersi:

Ho anche cercato il modo migliore per disconnettersi. Il migliore che ho trovato sta usando 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());
  }
 }

Anche se la mia risposta è davvero in ritardo considerando la data della domanda, spero che questo aiuti altre persone che finiscono qui da Google, proprio come ho fatto io.

Ciao,

Vítor Souza


15
Un piccolo consiglio: stai usando request.getSession (false) e stai chiamando invalidate () su questo. request.getSession (false) può restituire null se non è presente alcuna sessione. Meglio controllare se è prima null;)
Arjan Tijms il

@Vitor: Ciao..Vuoi dire qualcosa su quando è bene passare dalla sicurezza basata su container ad alternative come Shiro o altre? Vedi la domanda più mirata qui: stackoverflow.com/questions/7782720/…
Rajat Gupta,

Sembra che il Glassfish JDBC Realm non supporti la memorizzazione degli hash delle password salate. È davvero la migliore pratica usarlo in quel caso?
Lii,

Scusa, non posso aiutarti. Non sono un esperto di Glassfish. Forse fai quella domanda in una nuova discussione per vedere cosa dicono le persone?
Vítor E. Silva Souza,

1
Lii, puoi lavorare con il sale usando il contenitore del pesce vetro. Configura il tuo healm per non usare alcun hash. Confronterà il valore semplice che inserisci per la password HttpServletResponse#login(user, password), in questo modo puoi semplicemente ottenere da DB il salt dell'utente, le iterazioni e qualunque cosa tu usi per la salatura, hash la password immessa dall'utente usando quel salt e quindi chiedere al contenitore di autenticarti con HttpServletResponse#login(user, password).
emportella,

152

Suppongo che tu voglia un'autenticazione basata su form usando descrittori di distribuzione e j_security_check.

Puoi anche farlo in JSF semplicemente usando gli stessi nomi di campo predefiniti j_usernamee j_passwordcome dimostrato nel tutorial.

Per esempio

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

È possibile eseguire il caricamento lento in User getter per verificare se Userè già stato effettuato l'accesso e, in caso contrario, verificare se Principalè presente nella richiesta e, in tal caso, quindi ottenere il Userrelativo 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;
    }

}

Il Userè ovviamente accessibile in JSF EL da #{auth.user}.

Per disconnettersi, eseguire un HttpServletRequest#logout()(e impostareUser su null!). È possibile ottenere un handle di HttpServletRequestin JSF da ExternalContext#getRequest(). Puoi anche semplicemente invalidare la sessione del tutto.

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

Per il residuo (definizione di utenti, ruoli e vincoli nel descrittore di distribuzione e nel regno), basta seguire l'esercitazione Java EE 6 e la documentazione del servletcontainer nel solito modo.


Aggiornamento : è inoltre possibile utilizzare il nuovo Servlet 3.0 HttpServletRequest#login()per eseguire un accesso programmatico anziché utilizzare j_security_checkche potrebbe non essere di per sé raggiungibile da un dispatcher in alcuni servletcontainer. In questo caso puoi usare un modulo JSF completo e un bean conusername e passwordproprietà e un loginmetodo che assomigliano a questo:

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

E questa vista ha gestito il bean gestito che ricorda anche la pagina inizialmente richiesta:

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

In questo modo Userè accessibile in JSF EL da #{user}.


1
Ho aggiornato la domanda per includere una dichiarazione di non responsabilità in cui l'invio in j_security_checkpotrebbe non funzionare su tutti i servletcontainer.
BalusC,

1
Link dal tutorial Java sull'uso della sicurezza programmatica con applicazioni Web: java.sun.com/javaee/6/docs/tutorial/doc/gjiie.html (usando Servlet): su una classe servlet puoi usare: @WebServlet(name="testServlet", urlPatterns={"/ testServlet "}) @ServletSecurity(@HttpConstraint(rolesAllowed = {"testUser", "admin”})) E su un metodo livello: @ServletSecurity(httpMethodConstraints={ @HttpMethodConstraint("GET"), @HttpMethodConstraint(value="POST", rolesAllowed={"testUser"})})
ngeek,

3
E il tuo punto è ...? Se questo è applicabile in JSF? Bene, in JSF c'è solo un servlet, il FacesServlete non puoi (e non vuoi) modificarlo.
BalusC,

1
@BalusC - Quando dici quanto sopra è il modo migliore intendi usare j_security_check o un login programmatico?
simgineer,

3
@simgineer: l'URL richiesto è disponibile come attributo di richiesta con il nome definito da RequestDispatcher.FORWARD_REQUEST_URI. Gli attributi della richiesta sono disponibili in JSF ExternalContext#getRequestMap().
BalusC,

7

Va notato che è un'opzione per lasciare completamente i problemi di autenticazione al front controller, ad esempio un server web Apache e valutare invece HttpServletRequest.getRemoteUser (), che è la rappresentazione JAVA per la variabile di ambiente REMOTE_USER. Ciò consente anche sofisticati progetti di accesso come l'autenticazione Shibboleth. Il filtraggio delle richieste a un container servlet tramite un server Web è una buona progettazione per ambienti di produzione, spesso viene utilizzato mod_jk per farlo.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.