Come posso utilizzare Spring Security senza sessioni?


99

Sto creando un'applicazione web con Spring Security che risiederà su Amazon EC2 e utilizzerà gli Elastic Load Balancer di Amazon. Sfortunatamente, ELB non supporta le sessioni permanenti, quindi devo assicurarmi che la mia applicazione funzioni correttamente senza sessioni.

Finora, ho impostato RememberMeServices per assegnare un token tramite un cookie, e questo funziona bene, ma voglio che il cookie scada con la sessione del browser (ad esempio quando il browser si chiude).

Immagino di non essere il primo a voler usare Spring Security senza sessioni ... qualche suggerimento?

Risposte:


124

In Spring Security 3 con Java Config , puoi utilizzare HttpSecurity.sessionManagement () :

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

2
Questa è la risposta corretta per Java config, rispecchiando ciò che @sappenin ha dichiarato correttamente per xml config in un commento sulla risposta accettata. Usiamo questo metodo e infatti la nostra applicazione è senza sessione.
Paul

Questo ha un effetto collaterale. Il contenitore Tomcat aggiungerà "; jsessionid = ..." alle richieste di immagini, fogli di stile, ecc., Perché a Tomcat non piace essere senza stato e Spring Security bloccherà queste risorse al primo caricamento perché "l'URL conteneva un Stringa potenzialmente dannosa ';' ".
workerjoe

@workerjoe Allora, cosa stai cercando di dire con questa configurazione java, le sessioni non sono create da Spring Security ma da Tomcat?
Vishwas Atrey

@VishwasAtrey Nella mia comprensione (il che potrebbe essere sbagliato), Tomcat crea e mantiene le sessioni. La primavera ne approfitta, aggiungendo i propri dati. Ho provato a creare un'applicazione web senza stato e non ha funzionato, come ho detto sopra. Vedi questa risposta alla mia domanda per ulteriori informazioni.
workerjoe

28

Sembra essere ancora più facile con Spring Securitiy 3.0. Se stai utilizzando la configurazione dello spazio dei nomi, puoi semplicemente fare quanto segue:

<http create-session="never">
  <!-- config -->
</http>

Oppure si potrebbe configurare il SecurityContextRepository come nulla, e nulla sarebbe mai salvato in questo modo pure .


5
Non ha funzionato come pensavo. Invece, c'è un commento di seguito che distingue tra "mai" e "apolidi". Utilizzando "mai", la mia app stava ancora creando sessioni. Utilizzando "stateless", la mia app è diventata effettivamente apolide e non ho avuto bisogno di implementare nessuno degli override menzionati in altre risposte. Vedi il numero di JIRA qui: jira.springsource.org/browse/SEC-1424
sappenin

27

Abbiamo lavorato allo stesso problema (inserendo un SecurityContextRepository personalizzato in SecurityContextPersistenceFilter) per 4-5 ore oggi. Alla fine, l'abbiamo capito. Prima di tutto, nella sezione 8.3 di Spring Security rif. doc, esiste una definizione del bean SecurityContextPersistenceFilter

<bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
    <property name='securityContextRepository'>
        <bean class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'>
            <property name='allowSessionCreation' value='false' />
        </bean>
    </property>
</bean>

E dopo questa definizione, c'è questa spiegazione: "In alternativa, potresti fornire un'implementazione nulla dell'interfaccia SecurityContextRepository, che impedirà l'archiviazione del contesto di sicurezza, anche se una sessione è già stata creata durante la richiesta."

Avevamo bisogno di inserire il nostro SecurityContextRepository personalizzato in SecurityContextPersistenceFilter. Quindi abbiamo semplicemente cambiato la definizione di bean sopra con il nostro impl personalizzato e l'abbiamo inserita nel contesto della sicurezza.

Quando abbiamo eseguito l'applicazione, abbiamo tracciato i log e abbiamo visto che SecurityContextPersistenceFilter non utilizzava il nostro impl personalizzato, ma HttpSessionSecurityContextRepository.

Dopo alcune altre cose che abbiamo provato, abbiamo capito che dovevamo dare al nostro impl SecurityContextRepository personalizzato l'attributo "security-context-repository-ref" dello spazio dei nomi "http". Se utilizzi lo spazio dei nomi "http" e desideri inserire il tuo impl SecurityContextRepository, prova l'attributo "security-context-repository-ref".

Quando viene utilizzato lo spazio dei nomi "http", viene ignorata una definizione SecurityContextPersistenceFilter separata. Come ho copiato sopra, il documento di riferimento. non lo afferma.

Per favore correggimi se ho frainteso le cose.


Grazie, questa è un'informazione preziosa. Lo proverò nella mia applicazione.
Jeff Evans

Grazie, è quello di cui avevo bisogno con la primavera 3.0
Justin Ludwig

1
Sei abbastanza preciso quando dici che lo spazio dei nomi http non consente un SecurityContextPersistenceFilter personalizzato, mi ci sono volute un paio d'ore di debug per capirlo
Jaime Hablutzel

Grazie mille per aver postato questo! Stavo per strapparmi quei pochi capelli che ho. Mi chiedevo perché il metodo setSecurityContextRepository di SecurityContextPersistenceFilter fosse deprecato (i documenti che dicono di usare l'iniezione del costruttore, che non è neanche giusto).
fool4jesus

10

Dai un'occhiata alla SecurityContextPersistenceFilterlezione. Definisce come SecurityContextHolderviene popolato. Per impostazione predefinita, utilizza HttpSessionSecurityContextRepositoryper memorizzare il contesto di sicurezza nella sessione http.

Ho implementato questo meccanismo abbastanza facilmente, con custom SecurityContextRepository.

Vedi securityContext.xmlsotto:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:sec="http://www.springframework.org/schema/security"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">

    <context:annotation-config/>

    <sec:global-method-security secured-annotations="enabled" pre-post-annotations="enabled"/>

    <bean id="securityContextRepository" class="com.project.server.security.TokenSecurityContextRepository"/>

    <bean id="securityContextFilter" class="com.project.server.security.TokenSecurityContextPersistenceFilter">
        <property name="repository" ref="securityContextRepository"/>
    </bean>

    <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
        <constructor-arg value="/login.jsp"/>
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
            </list>
        </constructor-arg>
    </bean>

    <bean id="formLoginFilter"
          class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationSuccessHandler">
            <bean class="com.project.server.security.TokenAuthenticationSuccessHandler">
                <property name="defaultTargetUrl" value="/index.html"/>
                <property name="passwordExpiredUrl" value="/changePassword.jsp"/>
                <property name="alwaysUseDefaultTargetUrl" value="true"/>
            </bean>
        </property>
        <property name="authenticationFailureHandler">
            <bean class="com.project.server.modules.security.CustomUrlAuthenticationFailureHandler">
                <property name="defaultFailureUrl" value="/login.jsp?failure=1"/>
            </bean>
        </property>
        <property name="filterProcessesUrl" value="/j_spring_security_check"/>
        <property name="allowSessionCreation" value="false"/>
    </bean>

    <bean id="servletApiFilter"
          class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter"/>

    <bean id="anonFilter" class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
        <property name="key" value="ClientApplication"/>
        <property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
    </bean>


    <bean id="exceptionTranslator" class="org.springframework.security.web.access.ExceptionTranslationFilter">
        <property name="authenticationEntryPoint">
            <bean class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
                <property name="loginFormUrl" value="/login.jsp"/>
            </bean>
        </property>
        <property name="accessDeniedHandler">
            <bean class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
                <property name="errorPage" value="/login.jsp?failure=2"/>
            </bean>
        </property>
        <property name="requestCache">
            <bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
        </property>
    </bean>

    <alias name="filterChainProxy" alias="springSecurityFilterChain"/>

    <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
        <sec:filter-chain-map path-type="ant">
            <sec:filter-chain pattern="/**"
                              filters="securityContextFilter, logoutFilter, formLoginFilter,
                                        servletApiFilter, anonFilter, exceptionTranslator, filterSecurityInterceptor"/>
        </sec:filter-chain-map>
    </bean>

    <bean id="filterSecurityInterceptor"
          class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
        <property name="securityMetadataSource">
            <sec:filter-security-metadata-source use-expressions="true">
                <sec:intercept-url pattern="/staticresources/**" access="permitAll"/>
                <sec:intercept-url pattern="/index.html*" access="hasRole('USER_ROLE')"/>
                <sec:intercept-url pattern="/rpc/*" access="hasRole('USER_ROLE')"/>
                <sec:intercept-url pattern="/**" access="permitAll"/>
            </sec:filter-security-metadata-source>
        </property>
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="accessDecisionManager"/>
    </bean>

    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
        <property name="decisionVoters">
            <list>
                <bean class="org.springframework.security.access.vote.RoleVoter"/>
                <bean class="org.springframework.security.web.access.expression.WebExpressionVoter"/>
            </list>
        </property>
    </bean>

    <bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
        <property name="providers">
            <list>
                <bean name="authenticationProvider"
                      class="com.project.server.modules.security.oracle.StoredProcedureBasedAuthenticationProviderImpl">
                    <property name="dataSource" ref="serverDataSource"/>
                    <property name="userDetailsService" ref="userDetailsService"/>
                    <property name="auditLogin" value="true"/>
                    <property name="postAuthenticationChecks" ref="customPostAuthenticationChecks"/>
                </bean>
            </list>
        </property>
    </bean>

    <bean id="customPostAuthenticationChecks" class="com.project.server.modules.security.CustomPostAuthenticationChecks"/>

    <bean name="userDetailsService" class="com.project.server.modules.security.oracle.UserDetailsServiceImpl">
        <property name="dataSource" ref="serverDataSource"/>
    </bean>

</beans>

1
Ciao Lukas, puoi fornire ulteriori dettagli sull'implementazione del tuo repository del contesto di sicurezza?
Jim Downing

1
class TokenSecurityContextRepository contiene HashMap <String, SecurityContext> contextMap. In loadContext (), il metodo verifica se esiste SecurityContext per il codice hash della sessione passato da requestParameter sid, cookie o requestHeader personalizzato o combinazione di uno qualsiasi dei precedenti. Restituisce SecurityContextHolder.createEmptyContext () se non è stato possibile risolvere il contesto. Il metodo saveContext inserisce il contesto risolto in contextMap.
Lukas Herman

8

In realtà create-session="never"non significa essere completamente apolidi. C'è un problema per questo nella gestione dei problemi di Spring Security.


3

Dopo aver lottato con le numerose soluzioni pubblicate in questa risposta, per provare a far funzionare qualcosa quando si utilizza la <http>configurazione dello spazio dei nomi, ho finalmente trovato un approccio che funziona effettivamente per il mio caso d'uso. In realtà non richiedo che Spring Security non avvii una sessione (perché utilizzo la sessione in altre parti dell'applicazione), solo che non "ricorda" affatto l'autenticazione nella sessione (dovrebbe essere ricontrollata ogni richiesta).

Per cominciare, non sono riuscito a capire come eseguire la tecnica di "implementazione nulla" descritta sopra. Non era chiaro se si supponeva di impostare securityContextRepository su nullo su un'implementazione no-op. Il primo non funziona perché NullPointerExceptionviene buttato dentro SecurityContextPersistenceFilter.doFilter(). Per quanto riguarda l'implementazione no-op, ho provato a implementarla nel modo più semplice che potessi immaginare:

public class NullSpringSecurityContextRepository implements SecurityContextRepository {

    @Override
    public SecurityContext loadContext(final HttpRequestResponseHolder requestResponseHolder_) {
        return SecurityContextHolder.createEmptyContext();
    }

    @Override
    public void saveContext(final SecurityContext context_, final HttpServletRequest request_,
            final HttpServletResponse response_) {
    }

    @Override
    public boolean containsContext(final HttpServletRequest request_) {
        return false;
    }

}

Questo non funziona nella mia applicazione, a causa di alcuni strani ClassCastExceptionproblemi con il response_tipo.

Anche supponendo di essere riuscito a trovare un'implementazione che funziona (semplicemente non archiviando il contesto nella sessione), c'è ancora il problema di come iniettarlo nei filtri costruiti dalla <http>configurazione. Non è possibile sostituire semplicemente il filtro nella SECURITY_CONTEXT_FILTERposizione, come da documenti . L'unico modo che ho trovato per agganciarmi a SecurityContextPersistenceFilterciò che viene creato sotto le coperte è stato scrivere un brutto ApplicationContextAwarefagiolo:

public class SpringSecuritySessionDisabler implements ApplicationContextAware {

    private final Logger logger = LoggerFactory.getLogger(SpringSecuritySessionDisabler.class);

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext_) throws BeansException {
        applicationContext = applicationContext_;
    }

    public void disableSpringSecuritySessions() {
        final Map<String, FilterChainProxy> filterChainProxies = applicationContext
                .getBeansOfType(FilterChainProxy.class);
        for (final Entry<String, FilterChainProxy> filterChainProxyBeanEntry : filterChainProxies.entrySet()) {
            for (final Entry<String, List<Filter>> filterChainMapEntry : filterChainProxyBeanEntry.getValue()
                    .getFilterChainMap().entrySet()) {
                final List<Filter> filterList = filterChainMapEntry.getValue();
                if (filterList.size() > 0) {
                    for (final Filter filter : filterList) {
                        if (filter instanceof SecurityContextPersistenceFilter) {
                            logger.info(
                                    "Found SecurityContextPersistenceFilter, mapped to URL '{}' in the FilterChainProxy bean named '{}', setting its securityContextRepository to the null implementation to disable caching of authentication",
                                    filterChainMapEntry.getKey(), filterChainProxyBeanEntry.getKey());
                            ((SecurityContextPersistenceFilter) filter).setSecurityContextRepository(
                             new NullSpringSecurityContextRepository());
                        }
                    }
                }

            }
        }
    }
}

Ad ogni modo, alla soluzione che effettivamente funziona, anche se molto hacker. Usa semplicemente un Filterche cancella la voce di sessione che HttpSessionSecurityContextRepositorycerca quando fa la sua cosa:

public class SpringSecuritySessionDeletingFilter extends GenericFilterBean implements Filter {

    @Override
    public void doFilter(final ServletRequest request_, final ServletResponse response_, final FilterChain chain_)
            throws IOException, ServletException {
        final HttpServletRequest servletRequest = (HttpServletRequest) request_;
        final HttpSession session = servletRequest.getSession();
        if (session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY) != null) {
            session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
        }

        chain_.doFilter(request_, response_);
    }
}

Quindi nella configurazione:

<bean id="springSecuritySessionDeletingFilter"
    class="SpringSecuritySessionDeletingFilter" />

<sec:http auto-config="false" create-session="never"
    entry-point-ref="authEntryPoint">
    <sec:intercept-url pattern="/**"
        access="IS_AUTHENTICATED_REMEMBERED" />
    <sec:intercept-url pattern="/static/**" filters="none" />
    <sec:custom-filter ref="myLoginFilterChain"
        position="FORM_LOGIN_FILTER" />

    <sec:custom-filter ref="springSecuritySessionDeletingFilter"
        before="SECURITY_CONTEXT_FILTER" />
</sec:http>

Nove anni dopo, questa è ancora la risposta giusta. Ora possiamo usare la configurazione Java invece di XML. Ho aggiunto il filtro personalizzato nel mio WebSecurityConfigurerAdaptercon " http.addFilterBefore(new SpringSecuritySessionDeletingFilter(), SecurityContextPersistenceFilter.class)"
workerjoe

3

Solo una breve nota: è "crea-sessione" piuttosto che "crea-sessioni"

creare-sessione

Controlla l'entusiasmo con cui viene creata una sessione HTTP.

Se non impostato, il valore predefinito è "ifRequired". Altre opzioni sono "sempre" e "mai".

L'impostazione di questo attributo influisce sulle proprietà allowSessionCreation e forceEagerSessionCreation di HttpSessionContextIntegrationFilter. allowSessionCreation sarà sempre vero a meno che questo attributo non sia impostato su "mai". forceEagerSessionCreation è "false" a meno che non sia impostato su "sempre".

Quindi la configurazione predefinita consente la creazione della sessione ma non la forza. L'eccezione è se il controllo della sessione simultanea è abilitato, quando forceEagerSessionCreation sarà impostato su true, indipendentemente da quale sia l'impostazione qui. L'utilizzo di "mai" provocherebbe quindi un'eccezione durante l'inizializzazione di HttpSessionContextIntegrationFilter.

Per dettagli specifici sull'utilizzo della sessione, è disponibile una buona documentazione nel javadoc HttpSessionSecurityContextRepository.


Queste sono tutte ottime risposte, ma ho battuto la testa contro il muro cercando di capire come ottenerlo quando utilizzo l'elemento di configurazione <http>. Anche con auto-config=false, apparentemente non puoi sostituire ciò che è nella SECURITY_CONTEXT_FILTERposizione con il tuo. Ho cercato di disabilitarlo con un po 'di ApplicationContextAwarebean (usando la riflessione per forzare securityContextRepositoryun'implementazione nulla SessionManagementFilter) ma senza dadi. E purtroppo, non posso passare alla protezione primaverile di 3,1 anni che fornirebbe create-session=stateless.
Jeff Evans

Si prega di visitare questo sito, sempre informativo. Spero che questo aiuti te e anche gli altri " baeldung.com/spring-security-session " • sempre - verrà sempre creata una sessione se non ne esiste già una • se necessario - verrà creata una sessione solo se necessario (impostazione predefinita) • mai - il framework non creerà mai una sessione ma ne userà una se esiste già • stateless - nessuna sessione verrà creata o utilizzata da Spring Security
Java_Fire_Within
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.