2011-01-12 14 views
89

Dopo che un nuovo utente ha inviato un modulo "Nuovo account", desidero registrare manualmente quell'utente in modo che non debbano accedere alla pagina successiva.Come impostare manualmente un utente autenticato in Spring Security/SpringMVC

La normale pagina di accesso al modulo che attraversa l'interceptor di sicurezza funziona perfettamente.

Nel regolatore-conto-forma nuova Sto generando un UsernamePasswordAuthenticationToken e porla in SecurityContext manualmente:

SecurityContextHolder.getContext().setAuthentication(authentication); 

Sulla stessa pagina dopo controllo che l'utente è collegato a:

SecurityContextHolder.getContext().getAuthentication().getAuthorities(); 

Questo restituisce le autorizzazioni impostate in precedenza nell'autenticazione. Tutto bene.

Ma quando lo stesso codice viene chiamato nella pagina successiva che carico, il token di autenticazione è solo UserAnonymous.

Non sono chiaro perché non ha mantenuto l'autenticazione impostata sulla richiesta precedente. qualche idea?

  • Potrebbe avere a che fare con l'ID di sessione non impostato correttamente?
  • C'è qualcosa che potrebbe sovrascrivere la mia autenticazione in qualche modo?
  • Forse ho solo bisogno di un altro passaggio per salvare l'autenticazione?
  • Oppure c'è qualcosa che devo fare per dichiarare l'autenticazione attraverso l'intera sessione piuttosto che una singola richiesta in qualche modo?

Solo cercando alcuni pensieri che potrebbero aiutarmi a vedere cosa sta succedendo qui.

+1

Puoi seguire la mia risposta a http://stackoverflow.com/questions/4824395/spring-security-forward-directive-cant-forward-to-login-form/7972971#7972971 – AlexK

+1

Lettori, fai attenzione alle risposte a questa domanda se ti dicono di fare: 'SecurityContextHolder.getContext().setAuthentication (autenticazione) '. Funziona, ed è comune, ma ci sono seri difetti di funzionalità che incontrerai se lo fai. Per maggiori informazioni, vedi la mia domanda, e la risposta: https://stackoverflow.com/questions/47233187/spring-security-manual-login-best-practice – goat

risposta

59

Ho avuto lo stesso problema di un po 'di tempo fa. Non riesco a ricordare i dettagli ma il codice seguente ha funzionato per me. Questo codice viene utilizzato all'interno di un flusso Spring Webflow, quindi le classi RequestContext e ExternalContext. Ma la parte più pertinente per te è il metodo doAutoLogin.

public String registerUser(UserRegistrationFormBean userRegistrationFormBean, 
          RequestContext requestContext, 
          ExternalContext externalContext) { 

    try { 
     Locale userLocale = requestContext.getExternalContext().getLocale(); 
     this.userService.createNewUser(userRegistrationFormBean, userLocale, Constants.SYSTEM_USER_ID); 
     String emailAddress = userRegistrationFormBean.getChooseEmailAddressFormBean().getEmailAddress(); 
     String password = userRegistrationFormBean.getChoosePasswordFormBean().getPassword(); 
     doAutoLogin(emailAddress, password, (HttpServletRequest) externalContext.getNativeRequest()); 
     return "success"; 

    } catch (EmailAddressNotUniqueException e) { 
     MessageResolver messageResolvable 
       = new MessageBuilder().error() 
             .source(UserRegistrationFormBean.PROPERTYNAME_EMAIL_ADDRESS) 
             .code("userRegistration.emailAddress.not.unique") 
             .build(); 
     requestContext.getMessageContext().addMessage(messageResolvable); 
     return "error"; 
    } 

} 


private void doAutoLogin(String username, String password, HttpServletRequest request) { 

    try { 
     // Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated 
     UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); 
     token.setDetails(new WebAuthenticationDetails(request)); 
     Authentication authentication = this.authenticationProvider.authenticate(token); 
     logger.debug("Logging in with [{}]", authentication.getPrincipal()); 
     SecurityContextHolder.getContext().setAuthentication(authentication); 
    } catch (Exception e) { 
     SecurityContextHolder.getContext().setAuthentication(null); 
     logger.error("Failure in autoLogin", e); 
    } 

} 
+2

Grazie, il codice è molto utile per aiutarmi a sapere che sto risolvendo i problemi nell'area giusta. Sembra che abbia una pistola fumante, sta creando un nuovo ID di sessione dopo l'autenticazione manuale, ma il vecchio ID di sessione è ancora identificato dal cookie. Devo capire perché ora, ma almeno sono chiaramente sulla buona strada. Grazie! –

+4

Chiunque segua questa guida dovrebbe vedere anche questo problema correlato: http://stackoverflow.com/questions/4824395/spring-security-forward-directive-cant-forward-to-login-form –

+12

Puoi spiegare come sei ottenere authenticationProvider – Vivek

5

Attiva la registrazione di debug per avere una visione migliore di ciò che sta accadendo.

È possibile stabilire se i cookie di sessione vengono impostati utilizzando un debugger sul lato browser per esaminare le intestazioni restituite nelle risposte HTTP. (Ci sono anche altri modi.)

Una possibilità è che SpringSecurity stia impostando cookie di sessione sicuri, e la pagina successiva richiesta ha un URL "http" invece di un URL "https". (Il browser non invierà un cookie sicuro per un URL "http".)

+0

Grazie a tutti questi suggerimenti molto utili e pertinenti! –

15

Alla fine ho trovato la radice del problema.

Quando si crea manualmente il contesto di sicurezza non viene creato alcun oggetto di sessione. Solo quando la richiesta termina l'elaborazione, il meccanismo Spring Security si rende conto che l'oggetto sessione è nullo (quando tenta di archiviare il contesto di sicurezza nella sessione dopo che la richiesta è stata elaborata).

Alla fine della richiesta Spring Security crea un nuovo oggetto sessione e ID sessione.Tuttavia, questo nuovo ID sessione non viene mai inviato al browser perché si verifica alla fine della richiesta, dopo che è stata effettuata la risposta al browser. Ciò causa la perdita del nuovo ID di sessione (e quindi del contesto di sicurezza che contiene il mio utente connesso manualmente) quando la richiesta successiva contiene l'ID di sessione precedente.

+3

Sinceramente questo sembra un difetto di progettazione nella sicurezza di primavera più di ogni altra cosa. Ci sono molti framework scritti in altre lingue che non avrebbero alcun problema con questo, ma Spring Security si rompe. – chubbsondubs

+2

e la soluzione è? – s1moner3d

+1

e qual è la soluzione? – Thiago

5

La nuova funzione di filtro in Servlet 2.4 riduce sostanzialmente la limitazione che i filtri possono operare solo nel flusso di richieste prima e dopo l'effettiva elaborazione delle richieste da parte del server delle applicazioni. Invece, i filtri Servlet 2.4 possono ora interagire con il dispatcher della richiesta in ogni punto di invio. Ciò significa che quando una risorsa Web inoltra una richiesta a un'altra risorsa (ad esempio, un servlet che inoltra la richiesta a una pagina JSP nella stessa applicazione), un filtro può essere operativo prima che la richiesta venga gestita dalla risorsa mirata. Significa anche che se una risorsa Web include l'output o la funzione di altre risorse Web (ad esempio una pagina JSP che include l'output di più pagine JSP), i filtri Servlet 2.4 possono funzionare prima e dopo ciascuna delle risorse incluse. .

Per attivare questa funzione è necessario:

web.xml

<filter> 
    <filter-name>springSecurityFilterChain</filter-name> 
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
</filter> 
<filter-mapping> 
    <filter-name>springSecurityFilterChain</filter-name> 
    <url-pattern>/<strike>*</strike></url-pattern> 
    <dispatcher>REQUEST</dispatcher> 
    <dispatcher>FORWARD</dispatcher> 
</filter-mapping> 

RegistrationController

return "forward:/login?j_username=" + registrationModel.getUserEmail() 
     + "&j_password=" + registrationModel.getPassword(); 
54

non ho trovato nessun altro soluzioni complete così ho pensato Pubblicherei il mio. Questo può essere un po 'di un hack, ma risolto il problema per il problema di cui sopra:

public void login(HttpServletRequest request, String userName, String password) 
{ 

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password); 

    // Authenticate the user 
    Authentication authentication = authenticationManager.authenticate(authRequest); 
    SecurityContext securityContext = SecurityContextHolder.getContext(); 
    securityContext.setAuthentication(authentication); 

    // Create a new session and add the security context. 
    HttpSession session = request.getSession(true); 
    session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext); 
} 
+7

+1 - Questo mi ha aiutato! Mi mancava l'aggiornamento SPRING_SECURITY_CONTEXT. ... Ma quanto è "sporco" questo? – l3dx

+8

da dove si ottiene 'authenticationManager'? – Isaac

+1

authenticationManager viene eseguito automaticamente nella classe come questo @Autowired \t AuthenticationServiceImpl authenticationManager. E ci deve essere anche un'iniezione di bean nella configurazione xml, quindi Spring sa cosa iniettare. – test

1

stavo cercando di testare un'applicazione ExtJS e dopo aver impostato con successo un testingAuthenticationToken questo improvvisamente smesso di funzionare senza alcuna causa evidente.

Non ho potuto ottenere le risposte di cui sopra per funzionare, quindi la mia soluzione era di saltare questo po 'di primavera nell'ambiente di test. Ho introdotto una cucitura intorno alla primavera in questo modo:

public class SpringUserAccessor implements UserAccessor 
{ 
    @Override 
    public User getUser() 
    { 
     SecurityContext context = SecurityContextHolder.getContext(); 
     Authentication authentication = context.getAuthentication(); 
     return (User) authentication.getPrincipal(); 
    } 
} 

L'utente è un tipo personalizzato qui.

Lo sto quindi avvolgendo in una classe che ha solo un'opzione per il codice di test per cambiare molla.

public class CurrentUserAccessor 
{ 
    private static UserAccessor _accessor; 

    public CurrentUserAccessor() 
    { 
     _accessor = new SpringUserAccessor(); 
    } 

    public User getUser() 
    { 
     return _accessor.getUser(); 
    } 

    public static void UseTestingAccessor(User user) 
    { 
     _accessor = new TestUserAccessor(user); 
    } 
} 

La versione di prova proprio aspetto:

public class TestUserAccessor implements UserAccessor 
{ 
    private static User _user; 

    public TestUserAccessor(User user) 
    { 
     _user = user; 
    } 

    @Override 
    public User getUser() 
    { 
     return _user; 
    } 
} 

nel codice chiamante sto ancora usando un utente corretto caricati dal database:

User user = (User) _userService.loadUserByUsername(username); 
    CurrentUserAccessor.UseTestingAccessor(user); 

Ovviamente questo voleva essere adatto se è effettivamente necessario utilizzare la sicurezza, ma sono in esecuzione con una configurazione senza sicurezza per la distribuzione di test. Pensavo che qualcun altro potesse imbattersi in una situazione simile. Questo è uno schema che ho usato per deridere le dipendenze statiche prima. L'altra alternativa è che è possibile mantenere l'staticità della classe wrapper ma io preferisco questa come le dipendenze del codice sono più esplicite dato che devi passare CurrentUserAccessor nelle classi dove è richiesto.

+0

@downvoter cura di commentare ciò che non ti è piaciuto? –

Problemi correlati