webentwicklung-frage-antwort-db.com.de

Wie kann ich Spring Security ohne Sitzungen verwenden?

Ich erstelle eine Webanwendung mit Spring Security, die auf Amazon EC2 läuft und die Elastic Load Balancers von Amazon verwendet. Leider unterstützt ELB keine Sticky-Sessions. Daher muss sichergestellt werden, dass meine Anwendung auch ohne Sessions ordnungsgemäß funktioniert.

Bisher habe ich RememberMeServices so eingerichtet, dass ein Token über ein Cookie zugewiesen wird. Dies funktioniert einwandfrei, aber ich möchte, dass das Cookie mit der Browsersitzung abläuft (z. B. wenn der Browser geschlossen wird).

Ich muss mir vorstellen, dass ich nicht der erste bin, der Spring Security ohne Sessions nutzen möchte ... irgendwelche Vorschläge?

91
Jarrod Carlson

In Spring Securitiy 3.0 scheint es noch einfacher zu sein. Wenn Sie die Namespace-Konfiguration verwenden, können Sie einfach wie folgt vorgehen:

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

Oder Sie könnten das SecurityContextRepository als null konfigurieren und nichts würde jemals auf diese Weise gespeichert werden auch .

28
Jarrod Carlson

In Spring Security 3 mit Java Config können Sie HttpSecurity.sessionManagement () verwenden:

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

Wir haben heute vier bis fünf Stunden an demselben Problem gearbeitet (Injizieren eines benutzerdefinierten SecurityContextRepository in SecurityContextPersistenceFilter). Schließlich haben wir es herausgefunden. Zunächst wird in Abschnitt 8.3 von Spring Security, Ref. doc gibt es eine SecurityContextPersistenceFilter-Bean-Definition

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

Und nach dieser Definition gibt es folgende Erklärung: "Alternativ können Sie eine Null-Implementierung der SecurityContextRepository-Schnittstelle bereitstellen, die verhindert, dass der Sicherheitskontext gespeichert wird, selbst wenn während der Anforderung bereits eine Sitzung erstellt wurde."

Wir mussten unser benutzerdefiniertes SecurityContextRepository in den SecurityContextPersistenceFilter einfügen. Also haben wir einfach die obige Bean-Definition mit unserem benutzerdefinierten Gerät geändert und in den Sicherheitskontext gestellt.

Als wir die Anwendung ausführten, verfolgten wir die Protokolle und stellten fest, dass SecurityContextPersistenceFilter nicht unser benutzerdefiniertes Impl, sondern das HttpSessionSecurityContextRepository verwendete.

Nach einigen anderen Versuchen stellten wir fest, dass wir unserem benutzerdefinierten SecurityContextRepository-Impl das Attribut "security-context-repository-ref" des Namespace "http" zuweisen mussten. Wenn Sie den Namespace "http" verwenden und Ihr eigenes SecurityContextRepository-Impl einfügen möchten, versuchen Sie das Attribut "security-context-repository-ref".

Wenn der Namespace "http" verwendet wird, wird eine separate SecurityContextPersistenceFilter-Definition ignoriert. Wie ich oben kopiert habe, ist das Referenzdokument. sagt das nicht.

Bitte korrigieren Sie mich, wenn ich die Dinge falsch verstanden habe.

26
Basri Kahveci

Schauen Sie sich die Klasse SecurityContextPersistenceFilter an. Es definiert, wie das SecurityContextHolder gefüllt wird. Standardmäßig wird HttpSessionSecurityContextRepository verwendet, um den Sicherheitskontext in der http-Sitzung zu speichern.

Ich habe diesen Mechanismus mit custom SecurityContextRepository ziemlich einfach implementiert.

Siehe die securityContext.xml unten:

<?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

Tatsächlich create-session="never" bedeutet nicht, völlig staatenlos zu sein. Es gibt ein Problem für das Problemmanagement in Spring Security.

8
hleinone

Nur eine kurze Bemerkung: Es ist "create-session" anstatt "create-sessions"

Erstellungssitzung

Steuert den Eifer, mit dem eine HTTP-Sitzung erstellt wird.

Wenn nicht festgelegt, wird standardmäßig "ifRequired" verwendet. Andere Optionen sind "immer" und "nie".

Die Einstellung dieses Attributs wirkt sich auf die Eigenschaften allowSessionCreation und forceEagerSessionCreation von HttpSessionContextIntegrationFilter aus. allowSessionCreation ist immer true, sofern dieses Attribut nicht auf "never" gesetzt ist. forceEagerSessionCreation ist "false", sofern nicht "always" festgelegt ist.

Die Standardkonfiguration ermöglicht die Sitzungserstellung, erzwingt sie jedoch nicht. Die Ausnahme ist, wenn die gleichzeitige Sitzungssteuerung aktiviert ist und forceEagerSessionCreation auf true gesetzt wird, unabhängig von der hier vorgenommenen Einstellung. Die Verwendung von "never" würde dann während der Initialisierung von HttpSessionContextIntegrationFilter eine Ausnahme verursachen.

Für spezifische Details der Sitzungsnutzung gibt es eine gute Dokumentation im Java-Archiv HttpSessionSecurityContextRepository.

3
Jon Vaughan

Nachdem ich mit den zahlreichen Lösungen in dieser Antwort zu kämpfen hatte, um zu versuchen, mit der Namespace-Konfiguration <http> Etwas zum Laufen zu bringen, fand ich endlich einen Ansatz, der für meinen Anwendungsfall tatsächlich funktioniert. Ich brauche eigentlich nicht, dass Spring Security eine Sitzung nicht startet (weil ich die Sitzung in anderen Teilen der Anwendung verwende), nur, dass es sich überhaupt nicht an die Authentifizierung in der Sitzung "erinnert" (sie sollte erneut überprüft werden) jede Anfrage).

Zunächst konnte ich nicht herausfinden, wie die oben beschriebene "Null-Implementierung" durchgeführt werden soll. Es war nicht klar, ob Sie das securityContextRepository auf null oder auf eine No-Op-Implementierung setzen sollen. Ersteres funktioniert nicht, da ein NullPointerException in SecurityContextPersistenceFilter.doFilter() geworfen wird. Bei der No-Op-Implementierung habe ich versucht, auf die einfachste Art und Weise zu implementieren, die ich mir vorstellen kann:

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

}

Dies funktioniert in meiner Anwendung nicht, da einige seltsame ClassCastException mit dem Typ response_ Zu tun haben.

Selbst wenn ich es geschafft habe, eine Implementierung zu finden, die funktioniert (indem ich den Kontext einfach nicht in der Sitzung speichere), besteht immer noch das Problem, wie man das in die Filter einfügt, die durch die <http> - Konfiguration erstellt wurden. Sie können den Filter nicht einfach an der Position SECURITY_CONTEXT_FILTER Gemäß docs ersetzen. Die einzige Möglichkeit, mich in die SecurityContextPersistenceFilter einzuklinken, die unter der Decke erstellt wurde, bestand darin, eine hässliche ApplicationContextAware -Bohne zu schreiben:

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

            }
        }
    }
}

Wie auch immer, zu der Lösung, die tatsächlich funktioniert, wenn auch sehr hackisch. Verwenden Sie einfach ein Filter, das den Sitzungseintrag löscht, nach dem das HttpSessionSecurityContextRepository sucht, wenn es seine Sache macht:

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

Dann in der Konfiguration:

<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