2012-05-15 1 views
8

Sono abbastanza nuovo in Java e Spring 3 (usato principalmente PHP negli ultimi 8 anni). Ho ottenuto la sicurezza molla 3 a lavorare con tutti i UserDetails default e userDetailsService e so di poter accedere alla registrato nel nome utente dell'utente in un controller utilizzando:Spring Security: custom userdetails

Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 
String username = auth.getName(); //get logged in username 

Ma ci sono due problemi non riesco a capire out:

  1. ci sono un sacco di altri dettagli utente Vorrei memorizzato quando un utente accede (come data di nascita, sesso, ecc) e di essere accessibile tramite i controller più tardi. Cosa devo fare in modo che l'oggetto userDetails creato contenga i miei campi personalizzati?

  2. Sto già chiamando "HttpSession session = request.getSession (true);" nella parte superiore di ciascuno dei miei metodi nel mio controller. È possibile memorizzare i dati utente dell'utente registrati in una sessione al momento dell'accesso, in modo che non sia necessario chiamare anche "Autenticazione auth = SecurityContextHolder.getContext(). GetAuthentication();" all'inizio di ogni metodo?

Security-applicationContext.xml:

<global-method-security secured-annotations="enabled"></global-method-security>  
<http auto-config='true' access-denied-page="/access-denied.html"> 
    <!-- NO RESTRICTIONS -->   
    <intercept-url pattern="/login.html" access="IS_AUTHENTICATED_ANONYMOUSLY" /> 
    <intercept-url pattern="/*.html" access="IS_AUTHENTICATED_ANONYMOUSLY" /> 
    <!-- RESTRICTED PAGES --> 
    <intercept-url pattern="/admin/*.html" access="ROLE_ADMIN" /> 
    <intercept-url pattern="/member/*.html" access="ROLE_ADMIN, ROLE_STAFF" /> 

    <form-login login-page="/login.html" 
       login-processing-url="/loginProcess" 
       authentication-failure-url="/login.html?login_error=1" 
       default-target-url="/member/home.html" /> 
    <logout logout-success-url="/login.html"/> 
</http> 

<authentication-manager> 
    <authentication-provider> 
     <jdbc-user-service data-source-ref="dataSource" authorities-by-username-query="SELECT U.username, UR.authority, U.userid FROM users U, userroles UR WHERE U.username=? AND U.roleid=UR.roleid LIMIT 1" /> 
     <password-encoder hash="md5"/> 
    </authentication-provider> 
</authentication-manager> 

login.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %> 
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%> 

<tiles:insertDefinition name="header" /> 
<tiles:insertDefinition name="menu" /> 
<tiles:insertDefinition name="prebody" /> 

<h1>Login</h1> 

<c:if test="${not empty param.login_error}"> 
    <font color="red"><c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>.<br /><br /></font> 
</c:if> 
<form name="f" action="<c:url value='/loginProcess'/>" method="POST"> 
    <table> 
     <tr><td>User:</td><td><input type='text' name='j_username' value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>' /></td></tr> 
      <tr><td>Password:</td><td><input type='password' name='j_password' /></td></tr> 
      <tr><td>&nbsp;</td><td><input type="checkbox" name="_spring_security_remember_me" /> Remember Me</td></tr> 
      <tr><td>&nbsp;</td><td><input name="submit" type="submit" value="Login" /></td></tr> 
     </table> 
    </form> 

<tiles:insertDefinition name="postbody" /> 
<tiles:insertDefinition name="footer" /> 

risposta

19

C'è molto da fare in questa domanda. Proverò a risolverlo a pezzi ...

Q # 1: Ci sono un paio di possibili approcci qui.

Approccio 1: se si hanno altri attributi che si desidera aggiungere all'oggetto UserDetails, è necessario fornire la propria implementazione alternativa dell'interfaccia UserDetails che include tali attributi insieme ai getter e setter corrispondenti. Ciò richiederebbe anche fornire la propria implementazione alternativa dell'interfaccia UserDetailsService. Questo componente dovrebbe capire come mantenere questi attributi aggiuntivi al datastore sottostante, o quando si legge da quel datastore, dovrebbe capire come popolare questi attributi aggiuntivi. Si potrebbe cablare tutto questo fino in questo modo:

<beans:bean id="userDetailsService" class="com.example.MyCustomeUserDetailsService"> 
<!-- ... --> 
</beans:bean> 

<authentication-manager alias="authenticationManager"> 
    <authentication-provider ref="authenticationProvider"/> 
</authentication-manager> 

<beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> 
    <beans:property name="userDetailsService" ref="userDetailsService"/> 
</beans:bean> 

Approache # 2: Come me, si possono trovare (in particolare nell'arco di diverse iterazioni) che si sta meglio servita per mantenere utente specifico dominio/dettagli dell'account separati da da specifici dettagli utente/account di Spring Security. Questo potrebbe o non potrebbe essere il tuo caso. Ma se riesci a trovare qualche saggezza in questo approccio, allora ti atterrai con la configurazione che hai attualmente e aggiungerai un altro oggetto dominio utente/account, repository/DAO corrispondente, ecc.Se si desidera recuperare l'utente/account di dominio specifico, è possibile farlo nel modo seguente:

User user = userDao.getByUsername(SecurityContextHolder.getContext().getAuthentication().getName()); 

Q # 2: Primavera di sicurezza memorizza automaticamente le UserDetails nella sessione (a meno che non hai preso esplicitamente passi per eseguire l'override quel comportamento). Quindi non c'è bisogno che tu lo faccia da solo in ognuno dei tuoi metodi di controllo. L'oggetto SecurityContextHolder con cui hai avuto a che fare è in realtà popolato (da SS) con SecurityContext che include l'oggetto Authentication, UserDetails, ecc. All'inizio di ogni richiesta. Questo contesto viene cancellato alla fine di ogni richiesta, ma i dati rimangono sempre nella sessione.

Vale la pena notare, tuttavia, che non è davvero una buona pratica occuparsi di HttpServletRequest, oggetti HttpSession, ecc. In un controller Spring MVC se si riesce ad evitarlo. La primavera offre quasi sempre mezzi più puliti e più idiomatici per realizzare le cose senza il la necessità di farlo. Il vantaggio sarebbe che le firme e la logica del metodo controller cessano di dipendere da cose che sono difficili da prendere in giro in un test unitario (ad esempio HttpSession) e invece che dipendenti dai propri oggetti di dominio (o stub/mock di quegli oggetti di dominio). Questo aumenta drasticamente la testabilità dei tuoi controller ... e quindi aumenta la probabilità che testerai i tuoi controller. :)

Spero che questo aiuti alcuni.

+0

Grazie Kent, è stato molto utile! Ci scusiamo per il casino, la prima volta che abbiamo programmato Spring. Mi piace l'approccio n. 2, dove inserirò quella riga di codice, all'inizio di ogni metodo? E suppongo che sulla base di tale domanda, se vado con quel secondo approccio è possibile memorizzare l'oggetto utente in modo che io non t necessario creare un nuovo oggetto utente e andare al db per gli stessi dati ogni volta? – Felix

+1

La risposta è "dipende". Trovo che con l'approccio # 2, ho molto raramente bisogno di accedere all'account di un utente (dal punto di vista del dominio). Forse accedo solo a scopo di visualizzazione o modifica del loro "profilo". Se questo è il caso, vorrei solo leggere fino al datastore per queste informazioni quando è necessario. Se, tuttavia, è necessario accedere frequentemente agli attributi dell'utente (dal punto di vista del dominio, forse un attributo dell'oggetto utente deve apparire nell'intestazione ed è quindi necessario in ogni richiesta), allora probabilmente dovresti riconsiderare approccio # 1. –

+1

Grazie Kent. Ho finito per fare una soluzione di tipo ibrido. Quando ho bisogno di un utente connesso, eseguo un metodo in un controller wrapper per ottenere i dati. Il metodo controlla se la sessione contiene un oggetto utente e se lo fa controlla se il nome utente dell'oggetto è uguale a quello del nome utente securityContext. In caso contrario (o se l'oggetto utente sessione è nullo) ottiene i dati utente dal database e archivia l'oggetto in sessione. Due istruzioni extra if, ma una chiamata al database in meno. – Felix

1

accedendo direttamente la sessione è un po 'disordinato, e può essere soggetto a errori. Ad esempio, se l'utente viene autenticato utilizzando remember-me o qualche altro meccanismo che non prevede un reindirizzamento, la sessione non verrà popolata fino a quando tale richiesta non verrà completata.

Vorrei utilizzare un'interfaccia utente personalizzata per disporre le chiamate su SecurityContextHolder. Vedi my answer to this related question.

+0

Buona chiamata, è passato un po 'da quando ho usato le sessioni. – Felix

2

A mio parere, l'implementazione di Custom UserDetails è ottima ma dovrebbe essere utilizzata solo per le caratteristiche immutabili dell'utente.

Una volta che l'oggetto utente personalizzato sovrascrive UserDetails, non è facilmente modificabile. Devi creare un oggetto di autenticazione completamente nuovo con i dettagli modificati e non puoi semplicemente inserire l'oggetto UserDetails modificato nel contesto di sicurezza.

Nell'applicazione che sto creando l'ho capito e ho dovuto rearchitettarlo in modo tale che in caso di autenticazione avvenuta con dettagli sull'utente che stanno cambiando ad ogni richiesta (ma che non voglio ricaricare dal db in poi caricamento di ogni pagina) dovranno essere mantenuti nella sessione separatamente, ma ancora accessibili/modificabili solo dopo un controllo di autenticazione.

Cercando di capire se questo WebArgumentResolver menzionato in https://stackoverflow.com/a/8769670/1411545 è una soluzione migliore per la mia situazione.