Come impostare manualmente un utente autenticato in Spring Security / SpringMVC


107

Dopo che un nuovo utente ha inviato un modulo "Nuovo account", desidero accedere manualmente a tale utente in modo che non debba accedere alla pagina successiva.

La normale pagina di login del modulo che passa attraverso l'intercettatore di sicurezza di primavera funziona perfettamente.

Nel controller del nuovo modulo di account sto creando un UsernamePasswordAuthenticationToken e impostandolo manualmente nel SecurityContext:

SecurityContextHolder.getContext().setAuthentication(authentication);

In quella stessa pagina controllo successivamente che l'utente abbia effettuato l'accesso con:

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

Ciò restituisce le autorità che ho impostato in precedenza nell'autenticazione. Tutto bene.

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

Non mi è chiaro il motivo per cui non ha mantenuto l'autenticazione che avevo impostato nella richiesta precedente. qualche idea?

  • Potrebbe essere dovuto al fatto che l'ID di sessione non è stato impostato correttamente?
  • C'è qualcosa che potrebbe sovrascrivere in qualche modo la mia autenticazione?
  • Forse ho solo bisogno di un altro passaggio per salvare l'autenticazione?
  • O c'è qualcosa che devo fare per dichiarare l'autenticazione per l'intera sessione piuttosto che una singola richiesta in qualche modo?

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


1
È possibile seguire la mia risposta a stackoverflow.com/questions/4824395/...
AlexK

2
I lettori, attenzione delle risposte a questa domanda, se ti dicono di fare: SecurityContextHolder.getContext().setAuthentication(authentication). Funziona ed è comune, ma ci sono gravi carenze di funzionalità che incontrerai se lo fai. Per maggiori informazioni, vedi la mia domanda e la risposta: stackoverflow.com/questions/47233187/…
goat

Risposte:


62

Ho avuto lo stesso problema di te tempo fa. Non ricordo i dettagli ma il codice seguente ha funzionato per me. Questo codice viene utilizzato all'interno di un flusso Webflow Spring, da cui le classi RequestContext e ExternalContext. Ma la parte che è più rilevante 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 io abbia una pistola fumante, sta creando un nuovo ID di sessione dopo l'autenticazione manuale, ma il vecchio ID di sessione viene ancora identificato dal cookie. Devo capire perché adesso, ma almeno sono chiaramente sulla buona strada. Grazie!
David Parks

4
Chiunque segua questa guida dovrebbe vedere anche questo problema correlato: stackoverflow.com/questions/4824395/…
David Parks

14
Puoi spiegare come stai ottenendo il
provider di

1
@ s1moner3d dovresti essere in grado di iniettarlo tramite IoC -> \ @Autowired
Hartmut

1
@Configuration public class WebConfig extends WebSecurityConfigurerAdapter { @Bean @Override public AuthenticationManager authenticationProvider() throws Exception { return super.authenticationManagerBean(); } }
slisnychyi

66

Non sono riuscito a trovare altre soluzioni complete, quindi ho pensato di pubblicare la mia. Questo potrebbe essere un trucco, ma ha risolto il problema al problema precedente:

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

12
da dove vieni authenticationManager?
Isaac

2
authenticationManager è autowired nella tua classe come questo @Autowired AuthenticationServiceImpl authenticationManager. E deve esserci anche un'iniezione di bean nella configurazione xml, quindi Spring sa cosa iniettare.

1
dov'è l'implementazione di AuthenticationServiceImpl? Cosa contiene questa classe?
Pra_A

3
Perché è necessario creare una nuova sessione? SecurityContext non lo gestisce?
Vlad Manuel Mureșan

17

Alla fine ho scoperto la radice del problema.

Quando creo 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 memorizzare 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 di sessione non arriva mai 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 contenente il mio utente connesso manualmente) quando la richiesta successiva contiene l'ID di sessione precedente.


4
Onestamente questo sembra più che altro un difetto di progettazione nella sicurezza primaverile. Ci sono molti framework scritti in altre lingue che non avrebbero problemi con questo, ma Spring Security si rompe.
chubbsondubs

3
e la soluzione è?
s1moner3d

2
e qual è la soluzione?
Thiago

6

Attiva la registrazione del debug per ottenere un'immagine migliore di ciò che sta accadendo.

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

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


Grazie, questi sono stati tutti suggerimenti molto utili e pertinenti!
David Parks

5

La nuova funzione di filtraggio in Servlet 2.4 allevia sostanzialmente la restrizione che i filtri possono operare solo nel flusso di richieste prima e dopo l'effettiva elaborazione della richiesta da parte del server delle applicazioni. Invece, i filtri Servlet 2.4 possono ora interagire con il dispatcher di richieste 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 di destinazione. Significa anche che se una risorsa Web include l'output o la funzione da altre risorse Web (ad esempio, una pagina JSP che include l'output da più altre pagine JSP), i filtri Servlet 2.4 possono funzionare prima e dopo ciascuna delle risorse incluse. .

Per attivare questa funzionalità è 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();

Buone informazioni, ma inserire nome utente e password nell'URL non è corretto. 1) non viene eseguito alcun escape, quindi è probabile che un nome utente o una password con un carattere speciale non funzionino o, peggio ancora, vengano utilizzati come vettore di exploit di sicurezza. 2) le password negli URL sono cattive perché gli URL vengono spesso registrati sul disco, il che è piuttosto negativo per la sicurezza: tutte le tue password in chiaro sono lì.
capra

1

Stavo cercando di testare un'applicazione extjs e dopo aver impostato con successo un testingAuthenticationToken questo ha improvvisamente smesso di funzionare senza una causa evidente.

Non sono riuscito a far funzionare le risposte di cui sopra, quindi la mia soluzione è stata di saltare questo pezzetto di primavera nell'ambiente di test. Ho introdotto una cucitura intorno alla primavera come questa:

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 inserendo in una classe che ha solo un'opzione per il codice di test per cambiare la 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 ha questo 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 utilizzando un utente corretto caricato dal database:

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

Ovviamente questo non sarà adatto se è effettivamente necessario utilizzare la sicurezza, ma sto eseguendo una configurazione senza sicurezza per la distribuzione di test. Ho pensato che qualcun altro potrebbe incappare in una situazione simile. Questo è un modello che ho usato prima per deridere le dipendenze statiche. L'altra alternativa è che puoi mantenere la staticità della classe wrapper, ma preferisco questa poiché le dipendenze del codice sono più esplicite poiché devi passare CurrentUserAccessor nelle classi in cui è richiesto.

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.