web-dev-qa-db-fra.com

Comment puis-je utiliser Spring Security sans sessions?

Je suis en train de créer une application Web avec Spring Security qui sera installée sur Amazon EC2 et utilisera les élasticités de charge équilibrées d'Amazon. Malheureusement, ELB ne prend pas en charge les sessions persistantes, je dois donc m'assurer que mon application fonctionne correctement sans sessions.

Jusqu'ici, j’ai configuré RememberMeServices pour attribuer un jeton via un cookie, et cela fonctionne bien, mais je souhaite que le cookie expire avec la session du navigateur (par exemple, lorsque le navigateur se ferme).

J'imagine que je ne suis pas le premier à vouloir utiliser Spring Security sans sessions ... des suggestions?

91
Jarrod Carlson

Cela semble être encore plus facile avec Spring Securitiy 3.0. Si vous utilisez la configuration d'espaces de noms, vous pouvez simplement procéder comme suit:

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

Ou vous pouvez configurer le SecurityContextRepository comme null, et rien ne sera jamais enregistré de cette façon aussi .

28
Jarrod Carlson

Dans Spring Security 3 avec Java Config , vous pouvez utiliser HttpSecurity.sessionManagement () :

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

Nous avons travaillé sur le même problème (injection d'un SecurityContextRepository personnalisé dans SecurityContextPersistenceFilter) pendant 4 à 5 heures aujourd'hui. Enfin, nous avons compris. Tout d’abord, dans la section 8.3 de Spring Security réf. doc, il existe une définition du 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>

Et après cette définition, il y a cette explication: "Vous pouvez également fournir une implémentation null de l'interface SecurityContextRepository, ce qui empêchera le stockage du contexte de sécurité, même si une session a déjà été créée lors de la demande."

Nous devions injecter notre SecurityContextRepository personnalisé dans SecurityContextPersistenceFilter. Nous avons donc simplement changé la définition du bean ci-dessus avec notre impl. Personnalisé et l'avons placée dans le contexte de la sécurité.

Lorsque nous exécutons l'application, nous avons tracé les journaux et avons constaté que SecurityContextPersistenceFilter n'utilisait pas notre implant personnalisé, il utilisait le HttpSessionSecurityContextRepository.

Après quelques essais, nous avons compris que nous devions attribuer à notre implémentation SecurityContextRepository personnalisée l’attribut "security-context-repository-ref" de l’espace de nommage "http". Si vous utilisez un espace de noms "http" et souhaitez injecter votre propre implémentation SecurityContextRepository, essayez l'attribut "security-context-repository-ref-ref".

Lorsque l'espace de noms "http" est utilisé, une définition distincte SecurityContextPersistenceFilter est ignorée. Comme je l'ai copié ci-dessus, le document de référence. ne dit pas cela.

S'il vous plaît corrigez-moi si j'ai mal compris les choses.

26
Basri Kahveci

Jetez un coup d'œil à SecurityContextPersistenceFilter class. Il définit comment SecurityContextHolder est rempli. Par défaut, il utilise HttpSessionSecurityContextRepository pour stocker le contexte de sécurité dans une session http.

J'ai implémenté ce mécanisme assez facilement, avec la coutume SecurityContextRepository.

Voir le securityContext.xml au dessous de:

<?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>
10
Lukas Herman

Réellement create-session="never" ne signifie pas être complètement apatride. Il y a n problème dans la gestion des problèmes de Spring Security.

8
hleinone

Juste une note rapide: c'est "create-session" plutôt que "create-sessions"

create-session

Contrôle l’empressement avec lequel une session HTTP est créée.

Si non défini, la valeur par défaut est "ifRequired". Les autres options sont "toujours" et "jamais".

La définition de cet attribut affecte les propriétés allowSessionCreation et forceEagerSessionCreation de HttpSessionContextIntegrationFilter. allowSessionCreation sera toujours vrai sauf si cet attribut est défini sur "jamais". forceEagerSessionCreation est "false" sauf si sa valeur est "toujours".

La configuration par défaut autorise donc la création de session mais ne la force pas. L'exception est que si le contrôle de session simultanée est activé, lorsque forceEagerSessionCreation sera défini sur true, quel que soit le paramètre défini ici. L'utilisation de "jamais" provoquerait alors une exception lors de l'initialisation de HttpSessionContextIntegrationFilter.

Pour plus de détails sur l'utilisation de la session, il existe une bonne documentation dans le fichier javadoc HttpSessionSecurityContextRepository.

3
Jon Vaughan

Après avoir lutté avec les nombreuses solutions affichées dans cette réponse pour essayer de faire fonctionner quelque chose lors de l'utilisation de la configuration d'espace de noms <http>, J'ai finalement trouvé une approche qui fonctionne réellement pour mon cas d'utilisation. En fait, je n’exige pas que Spring Security ne démarre pas de session (car j’utilise la session dans d’autres parties de l’application), mais ne se souvient pas du tout de l’authentification dans la session (il convient de la revérifier). chaque demande).

Pour commencer, je n'ai pas été capable de comprendre comment utiliser la technique de "mise en oeuvre nulle" décrite ci-dessus. Il n'était pas clair si vous êtes censé définir le securityContextRepository sur null ou sur une implémentation no-op. Le premier ne fonctionne pas car un NullPointerException est jeté dans SecurityContextPersistenceFilter.doFilter(). En ce qui concerne l'implémentation no-op, j'ai essayé d'implémenter de la manière la plus simple que je pouvais imaginer:

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;
    }

}

Cela ne fonctionne pas dans mon application, à cause d'un étrange ClassCastException lié au type response_.

Même en supposant que j’ai réussi à trouver une implémentation qui fonctionne (en ne stockant tout simplement pas le contexte en session), le problème est de savoir comment l’injecter dans les filtres construits avec la configuration <http>. Vous ne pouvez pas simplement remplacer le filtre à la position SECURITY_CONTEXT_FILTER, Comme indiqué dans docs . Le seul moyen que j’ai trouvé d’accrocher au SecurityContextPersistenceFilter créé sous les couvertures était d’écrire un vilain ApplicationContextAware moche:

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());
                        }
                    }
                }

            }
        }
    }
}

Quoi qu'il en soit, la solution qui fonctionne réellement, même si elle est très féroce. Utilisez simplement un Filter qui supprime l'entrée de session que le HttpSessionSecurityContextRepository recherche lorsqu'il fait son travail:

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_);
    }
}

Puis dans la configuration:

<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>
2
Jeff Evans