2016-03-14 12 views
6

Dato un meccanismo di autenticazione di tipo FORM definito per un'app Web Java, come si acquisisce l'accesso eseguito prima di essere reindirizzato alla risorsa richiesta? C'è qualche tipo di ascoltatore in cui posso mettere il mio codice per essere eseguito quando un utente accede?Autenticazione Java EE: come acquisire l'evento di accesso?

Mi sento come definire un filtro non è la soluzione migliore, poiché il filtro è collegato alla risorsa e verrebbe invocato anche quando l'utente è già autenticato e richiede una risorsa. Mi chiedo se ci sia qualche classe/metodo attivato solo dall'evento di accesso.

risposta

7

Non esiste evento in Java EE. Ancora. Come parte di JSR375, la sicurezza gestita dal contenitore verrà completamente rielaborata poiché è attualmente scattered attraverso diverse implementazioni del contenitore e non è compatibile cross-contenitore. Questo è descritto in questa presentazione Java EE 8 Security API.

Esiste già un'implementazione di riferimento dell'API di sicurezza in corso, Soteria, sviluppata tra l'altro dal mio collega Arjan Tijms. Con la nuova API di sicurezza, CDI verrà utilizzato per attivare gli eventi di autenticazione che è possibile solo @Observes. La discussione sulle specifiche è avvenuta nel this mailing list thread. Non è ancora concretamente implementato a Soteria.

Fino ad allora, supponendo l'autenticazione basata su FORM in base alla quale il principal utente è memorizzato internamente nella sessione, la soluzione migliore è verificare manualmente un filtro servlet se c'è una presenza utente presente nella richiesta mentre la propria rappresentazione dell'utente loggato è assente nella sessione HTTP.

@Override 
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { 
    HttpServletRequest request = (HttpServletRequest) req; 
    String username = request.getRemoteUser(); 

    if (username != null && request.getSession().getAttribute("user") == null) { 
     // First-time login. You can do your thing here. 
     User user = yourUserService.find(username); 
     request.getSession().setAttribute("user", user); 
    } 

    chain.doFilter(req, res); 
} 

Do atto che la registrazione di un filtro sulla /j_security_check non è garantito per funzionare come contenitore decente gestirà internamente prima che i primi filtri sono colpiti, per ovvie ragioni di sicurezza (filtri forniti dagli utenti possono manipolare la richiesta in un cattivo modo, accidentalmente o volutamente).

Se invece capita di utilizzare un server Java EE utilizza il Undertow servletcontainer, come ad esempio WildFly, poi c'è un modo più pulito per agganciare i suoi eventi di notifica interna e quindi generare eventi CDI personalizzato.Questo è spiegato in this blog di Arjan Tijms. Come mostrato nel blog, in ultima analisi, è possibile finire con un fagiolo CDI come questo:

@SessionScoped 
public class SessionAuthListener implements Serializable { 

    private static final long serialVersionUID = 1L; 

    public void onAuthenticated(@Observes AuthenticatedEvent event) { 
     String username = event.getUserPrincipal().getName(); 
     // Do something with name, e.g. audit, 
     // load User instance into session, etc 
    } 

    public void onLoggedOut(@Observes LoggedOutEvent event) { 
     // take some action, e.g. audit, null out User, etc 
    } 
} 
+0

Odio COSÌ per non dare la possibilità di invitare più di una volta! Detto questo, la soluzione Undertow renderà la tua webapp non completamente portatile, è qualcosa che sto cercando di evitare in questo momento.A questo punto, aggiungere un filtro per un intervallo di URL è probabilmente la soluzione che applicherò, ero solo un po 'preoccupato per il sovraccarico che potrebbe derivare da un filtro così ampiamente applicato, ma alla fine della giornata la maggior parte del tempo si tradurrà in una condizione non verificata se ... pensi che sarebbe un "overhead accettabile"? –

+0

:) SO ha il concetto di [taglie] (http://meta.stackexchange.com/questions/16065/how-does-the-bounty-system-work). Inoltre, alcuni utenti hanno una lista dei desideri o un link di donazione sul loro profilo. Per quanto riguarda l'overhead, questo è davvero trascurabile. Forse qualche nanosecondo. Ci sono in una vera e propria applicazione web le cose migliori da ottimizzare. – BalusC

0

È possibile utilizzare il filtro servlet sull'URI j_security_check. Questo filtro non verrà richiamato su ogni richiesta, ma solo sulla richiesta di accesso.

Controllare la pagina seguente - Developing servlet filters for form login processing - funziona in WebSphere App Server e Profilo di WebSphere Liberty.

Avendo tale filtro:

@WebFilter("/j_security_check") 
public class LoginFilter implements Filter { 
... 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 
     System.out.println("Filter called 1: " +((HttpServletRequest)request).getUserPrincipal()); 
    chain.doFilter(request, response); 
     System.out.println("Filter called 2: " + ((HttpServletRequest)request).getUserPrincipal()); 

} 

dà il seguente risultato:

// on incorrect login 
Filter called 1: null 
[AUDIT ] CWWKS1100A: Authentication did not succeed for user ID user1. An invalid user ID or password was specified. 
Filter called 2: null 

// on correct login 
Filter called 1: null 
Filter called 2: WSPrincipal:user1 

UPDATE

altro modo possibile per farlo è quello di utilizzare il proprio servlet per il login, modificare l'azione nella tua pagina di accesso a quel servlet e usa il metodo request.login(). Questa è l'API servlet quindi dovrebbe funzionare anche in Wildfly e hai il pieno controllo sul login. Hai solo bisogno di scoprire come passa wildfly l'URL della risorsa originariamente richiesto (WebSphere lo fa tramite cookie).

Servlet pseudo codice:

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
    String user = request.getParameter("j_username"); 
    String password = request.getParameter("j_password"); 
    try { 
     request.login(user, password); 
     // redirect to requested resource 
    } catch (Exception e) { 
     // login failed - redirect to error login page 
    } 
+1

Questo suona una buona idea e, in teoria, dovrebbe funzionare, ma per me non lo è, e non so perché. Il filtro sembra non essere attivato dalla chiamata 'j_security_check'. Ad esempio, un filtro con 'url-pattern' impostato su' * 'viene invocato per ogni risorsa non protetta, ma non viene eseguito per un login con password errata, per esempio. Oppure viene eseguito una sola volta (per l'URI della risorsa, non per l'URI 'j_security_check') quando si effettua l'accesso. Wildfly 8 qui. [collegamento rilevante] (http://www.theserverside.com/news/thread.tss?thread_id=32640) –

+0

@LuigiCortese Ho usato il seguente servlet mapping '@WebFilter ("/j_security_check ")' e il filtro viene chiamato solo quando si invia il modulo di accesso. Funziona perfettamente bene in WebSphere Liberty – Gas

+0

@BalusC il commento non è corretto o è stato formato in modo errato. Il mio filtro è correttamente invocato su j_security_check. Quindi non capisco cosa intendi per "internamente" – Gas