Durchführen der Benutzerauthentifizierung in Java EE / JSF mithilfe von j_security_check


156

Ich frage mich, was der aktuelle Ansatz in Bezug auf die Benutzerauthentifizierung für eine Webanwendung ist, die JSF 2.0 (und ob Komponenten vorhanden sind) und Java EE 6-Kernmechanismen (Anmelde- / Überprüfungsberechtigungen / Abmeldungen) verwendet, wobei Benutzerinformationen in einer JPA gespeichert sind Entität. Das Oracle Java EE-Tutorial ist hier etwas spärlich (behandelt nur Servlets).

Dies geschieht, ohne ein ganz anderes Framework wie Spring-Security (acegi) oder Seam zu verwenden, sondern wenn möglich, hoffentlich an der neuen Java EE 6-Plattform (Webprofil) festzuhalten.

Antworten:


85

Nachdem Sie das Web durchsucht und viele verschiedene Methoden ausprobiert haben, würde ich Folgendes für die Java EE 6-Authentifizierung vorschlagen:

Richten Sie den Sicherheitsbereich ein:

In meinem Fall hatte ich die Benutzer in der Datenbank. Daher folgte ich diesem Blog-Beitrag, um ein JDBC-Realm zu erstellen, mit dem Benutzer anhand des Benutzernamens und der MD5-Hash-Kennwörter in meiner Datenbanktabelle authentifiziert werden können:

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

Hinweis: Der Beitrag behandelt einen Benutzer und eine Gruppentabelle in der Datenbank. Ich hatte eine Benutzerklasse mit einem UserType-Enum-Attribut, das über javax.persistence-Annotationen der Datenbank zugeordnet wurde. Ich habe den Bereich mit derselben Tabelle für Benutzer und Gruppen konfiguriert, wobei die Spalte userType als Gruppenspalte verwendet wurde, und es hat einwandfrei funktioniert.

Formularauthentifizierung verwenden:

Befolgen Sie weiterhin den obigen Blog-Beitrag und konfigurieren Sie Ihre web.xml und sun-web.xml. Verwenden Sie jedoch anstelle der BASIC-Authentifizierung FORM (eigentlich spielt es keine Rolle, welche Sie verwenden, aber ich habe letztendlich FORM verwendet). Verwenden Sie das Standard-HTML, nicht das JSF.

Verwenden Sie dann den obigen Tipp von BalusC, um die Benutzerinformationen aus der Datenbank verzögert zu initialisieren. Er schlug vor, dies in einer verwalteten Bean zu tun, um den Principal aus dem Gesichtskontext zu erhalten. Ich habe stattdessen eine Stateful Session Bean verwendet, um Sitzungsinformationen für jeden Benutzer zu speichern, also habe ich den Sitzungskontext eingefügt:

 @Resource
 private SessionContext sessionContext;

Mit dem Principal kann ich den Benutzernamen überprüfen und mit dem EJB Entity Manager die Benutzerinformationen aus der Datenbank abrufen und in meinem SessionInformationEJB speichern .

Ausloggen:

Ich habe mich auch nach dem besten Weg zum Abmelden umgesehen. Das Beste, was ich gefunden habe, ist die Verwendung eines Servlets:

 @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());
  }
 }

Obwohl meine Antwort angesichts des Datums der Frage sehr spät ist, hoffe ich, dass dies anderen Menschen hilft, die von Google hierher kommen, genau wie ich.

Ciao,

Vítor Souza


15
Ein kleiner Ratschlag: Sie verwenden request.getSession (false) und rufen dazu invalidate () auf. request.getSession (false) gibt möglicherweise null zurück, wenn keine Sitzung vorhanden ist. Überprüfen Sie besser, ob es zuerst null ist;)
Arjan Tijms

@Vitor: Hi..Wollen Sie etwas darüber sagen, wann es gut ist, von containergestützter Sicherheit zu Alternativen wie Shiro oder anderen überzugehen? Weitere fokussierte Fragen finden Sie hier: stackoverflow.com/questions/7782720/…
Rajat Gupta

Es scheint, dass das Glassfish JDBC Realm das Speichern von gesalzenen Passwort-Hashes nicht unterstützt. Ist es in diesem Fall wirklich die beste Vorgehensweise, es zu verwenden?
Lii

Entschuldigung, kann dir nicht helfen. Ich bin kein Glassfish-Experte. Vielleicht diese Frage in einem neuen Thread stellen, um zu sehen, was die Leute sagen?
Vítor E. Silva Souza

1
Lii, du kannst mit gesalzenem Glasfischbehälter arbeiten. Konfigurieren Sie Ihr Healm so, dass kein Hash verwendet wird. Es wird der einfache Wert verglichen, den Sie für das Kennwort eingeben. Auf HttpServletResponse#login(user, password)diese Weise können Sie aus der Datenbank das Salz des Benutzers, die Iterationen und alles, was Sie zum Salzen verwenden, abrufen, das Kennwort, das der Benutzer mit diesem Salz eingegeben hat, hashen und dann den Container zur Authentifizierung auffordern HttpServletResponse#login(user, password).
Emportella

152

Ich nehme an, Sie möchten eine formularbasierte Authentifizierung mithilfe von Bereitstellungsdeskriptoren und j_security_check.

Sie können dies auch in JSF tun, indem Sie nur dieselben vordefinierten Feldnamen verwenden j_usernameund j_passwordwie im Lernprogramm gezeigt.

Z.B

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

Sie können den UserGetter faul laden , um zu überprüfen, ob der Userbereits angemeldet ist. Wenn nicht, überprüfen Sie, ob der Principalin der Anforderung vorhanden ist, und wenn ja, holen Sie sich den Userzugehörigen 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;
    }

}

Das Userist offensichtlich in JSF EL von zugänglich #{auth.user}.

Um sich abzumelden, machen Sie a HttpServletRequest#logout()(und setzen Sie Userauf null!). Sie können ein Handle des HttpServletRequestin JSF von erhalten ExternalContext#getRequest(). Sie können die Sitzung auch einfach insgesamt ungültig machen.

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

Befolgen Sie für den Rest (Definieren von Benutzern, Rollen und Einschränkungen in Deployment Descriptor und Realm) einfach das Java EE 6-Lernprogramm und die Servletcontainer-Dokumentation auf die übliche Weise.


Update : Sie können auch das neue Servlet 3.0 verwenden HttpServletRequest#login(), um eine programmatische Anmeldung j_security_checkdurchzuführen, anstatt eine zu verwenden, die in einigen Servlet-Containern möglicherweise per se nicht von einem Dispatcher erreichbar ist. In diesem Fall können Sie ein vollwertiges JSF-Formular und eine Bean mit usernameund passwordEigenschaften sowie eine loginMethode verwenden, die folgendermaßen aussieht:

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

Und diese Ansicht hat eine verwaltete Bean, die sich auch an die ursprünglich angeforderte Seite erinnert:

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

Auf diese Weise Userist der in JSF EL über zugänglich #{user}.


1
Ich habe die Frage so aktualisiert, dass sie einen Haftungsausschluss enthält, dass der Versand in j_security_checkmöglicherweise nicht auf allen Servlet-Containern funktioniert.
BalusC

1
Link aus dem Java-Tutorial zur Verwendung programmatischer Sicherheit mit Webanwendungen: java.sun.com/javaee/6/docs/tutorial/doc/gjiie.html (unter Verwendung von Servlets): Für eine Servlet-Klasse können Sie Folgendes verwenden: @WebServlet(name="testServlet", urlPatterns={"/ testServlet "}) @ServletSecurity(@HttpConstraint(rolesAllowed = {"testUser", "admin”})) Und für eine Methode pro Level: @ServletSecurity(httpMethodConstraints={ @HttpMethodConstraint("GET"), @HttpMethodConstraint(value="POST", rolesAllowed={"testUser"})})
Ngeek

3
Und dein Punkt ist ..? Ob dies in JSF gilt? Nun, in JSF gibt es nur ein Servlet, das FacesServletSie nicht ändern können (und wollen).
BalusC

1
@BalusC - Wenn Sie sagen, dass dies der beste Weg ist, meinen Sie damit, j_security_check oder ein programmatisches Login zu verwenden?
Simgineer

3
@simgineer: Die angeforderte URL ist als Anforderungsattribut mit dem durch definierten Namen verfügbar RequestDispatcher.FORWARD_REQUEST_URI. Anforderungsattribute sind in JSF verfügbar von ExternalContext#getRequestMap().
BalusC

7

Es sollte erwähnt werden, dass es eine Option ist, Authentifizierungsprobleme vollständig dem Front-Controller, z. B. einem Apache-Webserver, zu überlassen und stattdessen den HttpServletRequest.getRemoteUser () auszuwerten, der die JAVA-Darstellung für die Umgebungsvariable REMOTE_USER darstellt. Dies ermöglicht auch ausgefeilte Anmeldedesigns wie die Shibboleth-Authentifizierung. Das Filtern von Anforderungen an einen Servlet-Container über einen Webserver ist ein gutes Design für Produktionsumgebungen. Oft wird dazu mod_jk verwendet.


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.