Wie kann ich Spring Security ohne Sitzungen verwenden?

99

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-Sitzungen, daher muss ich sicherstellen, dass meine Anwendung ohne Sitzungen 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 Sitzungen verwenden möchte ... irgendwelche Vorschläge?

Jarrod Carlson
quelle

Antworten:

124

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);
}
Ben Hutchison
quelle
2
Dies ist die richtige Antwort für die Java-Konfiguration und spiegelt wider, was @sappenin für die XML-Konfiguration in einem Kommentar zur akzeptierten Antwort korrekt angegeben hat. Wir verwenden diese Methode und tatsächlich ist unsere Anwendung sitzungslos.
Paul
Dies hat einen Nebeneffekt. Der Tomcat-Container hängt "; jsessionid = ..." an Anforderungen für Bilder, Stylesheets usw. an, da Tomcat nicht gern zustandslos ist, und Spring Security blockiert diese Assets beim ersten Laden, weil "die URL a enthielt potenziell böswilliger String ';' ".
Workerjoe
@workerjoe Also, was versuchen Sie mit dieser Java-Konfiguration zu sagen, die Sitzungen werden nicht von Spring Security erstellt, sondern von Tomcat?
Vishwas Atrey
@VishwasAtrey Nach meinem Verständnis (was möglicherweise falsch ist) erstellt und verwaltet Tomcat die Sitzungen. Spring nutzt sie und fügt seine eigenen Daten hinzu. Ich habe versucht, eine zustandslose Webanwendung zu erstellen, die jedoch nicht funktioniert hat, wie oben erwähnt. Weitere Informationen finden Sie in dieser Antwort auf meine eigene Frage .
Workerjoe
28

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önnen das SecurityContextRepository als null konfigurieren, und auf diese Weise wird auch nie etwas gespeichert .

Jarrod Carlson
quelle
5
Das hat nicht so funktioniert, wie ich es mir vorgestellt hatte. Stattdessen gibt es unten einen Kommentar, der zwischen "nie" und "staatenlos" unterscheidet. Mit "nie" erstellte meine App immer noch Sitzungen. Mit "zustandslos" wurde meine App tatsächlich zustandslos und ich musste keine der in anderen Antworten genannten Überschreibungen implementieren. Siehe die JIRA-Ausgabe hier: jira.springsource.org/browse/SEC-1424
sappenin
27

Wir haben heute 4-5 Stunden an demselben Problem gearbeitet (Einfügen eines benutzerdefinierten SecurityContextRepository in SecurityContextPersistenceFilter). Schließlich haben wir es herausgefunden. Zunächst wird in Abschnitt 8.3 von Spring Security ref. doc, es gibt 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>

Nach dieser Definition gibt es folgende Erklärung: "Alternativ können Sie eine Nullimplementierung 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. Deshalb haben wir die obige Bean-Definition einfach mit unserem benutzerdefinierten Impl geändert und in den Sicherheitskontext gestellt.

Beim Ausführen der Anwendung haben wir die Protokolle nachverfolgt und festgestellt, dass SecurityContextPersistenceFilter nicht unser benutzerdefiniertes Impl verwendet, sondern das HttpSessionSecurityContextRepository.

Nach ein paar anderen Versuchen haben wir herausgefunden, dass wir unserem benutzerdefinierten SecurityContextRepository-Impl das Attribut "security-context-repository-ref" des Namespace "http" geben müssen. Wenn Sie den Namespace "http" verwenden und Ihr eigenes SecurityContextRepository-Impl einfügen möchten, versuchen Sie es mit dem 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 aus.

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

Basri Kahveci
quelle
Danke, das sind wertvolle Informationen. Ich werde es in meiner Bewerbung ausprobieren.
Jeff Evans
Danke, das habe ich mit Spring 3.0 gebraucht
Justin Ludwig
1
Sie sind ziemlich genau, wenn Sie sagen, dass der http-Namespace keinen benutzerdefinierten SecurityContextPersistenceFilter zulässt. Ich habe ein paar Stunden lang
debuggt, um das
Vielen Dank für die Veröffentlichung! Ich wollte gerade die kleinen Haare herausreißen, die ich habe. Ich habe mich gefragt, warum die setSecurityContextRepository-Methode von SecurityContextPersistenceFilter veraltet ist (die Dokumente sagen, dass die Konstruktorinjektion verwendet werden soll, was auch nicht richtig ist).
narr4jesus
10

Schauen Sie sich die SecurityContextPersistenceFilterKlasse an. Es definiert, wie das SecurityContextHoldergefüllt wird. Standardmäßig wird HttpSessionSecurityContextRepositoryder Sicherheitskontext in einer http-Sitzung gespeichert.

Ich habe diesen Mechanismus ganz einfach mit benutzerdefinierten implementiert SecurityContextRepository.

Siehe securityContext.xmlunten:

<?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>
Lukas Herman
quelle
1
Hallo Lukas, können Sie weitere Details zu Ihrer Implementierung des Sicherheitskontext-Repositorys angeben?
Jim Downing
1
Die Klasse TokenSecurityContextRepository enthält HashMap <String, SecurityContext> contextMap. In der loadContext () -Methode wird geprüft, ob SecurityContext für Sitzungs-Hashcode vorhanden ist, der entweder von requestParameter sid oder cookie oder von custom requestHeader oder einer Kombination der oben genannten übergeben wird. Gibt SecurityContextHolder.createEmptyContext () zurück, wenn der Kontext nicht aufgelöst werden konnte. Die Methode saveContext fügt aufgelösten Kontext in contextMap ein.
Lukas Herman
8

Eigentlich create-session="never"heißt das nicht, völlig staatenlos zu sein. In Spring Security Issue Management gibt es dafür ein Problem .

Hleinon
quelle
3

Nachdem Sie mit den zahlreichen Lösungen in dieser Antwort zu kämpfen haben, versuchen Sie, etwas zum Laufen zu bringen, wenn Sie das verwenden <http> Namespace-Konfiguration Laufen zu bringen, fand ich endlich einen Ansatz, der tatsächlich für meinen Anwendungsfall funktioniert. Ich benötige eigentlich nicht, dass Spring Security keine Sitzung startet (da ich die Sitzung in anderen Teilen der Anwendung verwende), sondern 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" -Technik durchgeführt wird. Es war nicht klar, ob Sie das securityContextRepository auf nulloder auf eine No-Op-Implementierung setzen sollen. Ersteres funktioniert nicht, weil ein NullPointerExceptionnach innen geworfen wird SecurityContextPersistenceFilter.doFilter(). Bei der No-Op-Implementierung habe ich versucht, sie so einfach wie möglich zu implementieren:

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 etwas Seltsames ClassCastExceptionmit dem response_Typ zu tun hat .

Selbst wenn ich es geschafft habe, eine Implementierung zu finden, die funktioniert (indem ich den Kontext einfach nicht in der Sitzung speichere), gibt es immer noch das Problem, wie ich das in die von der <http>Konfiguration erstellten Filter einfügen kann. Sie können den Filter nicht einfach an der SECURITY_CONTEXT_FILTERPosition gemäß den Dokumenten ersetzen . Der einzige Weg, wie ich mich in das einhaken konnte SecurityContextPersistenceFilter, was unter der Decke entsteht, war, eine hässliche ApplicationContextAwareBohne 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 a Filter, das den Sitzungseintrag löscht, nach dem HttpSessionSecurityContextRepositorygesucht wird, 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>
Jeff Evans
quelle
Neun Jahre später ist dies immer noch die richtige Antwort. Jetzt können wir die Java-Konfiguration anstelle von XML verwenden. Ich habe den benutzerdefinierten Filter in meinem WebSecurityConfigurerAdaptermit " http.addFilterBefore(new SpringSecuritySessionDeletingFilter(), SecurityContextPersistenceFilter.class)"
workerjoe
3

Nur eine kurze Anmerkung: Es ist "Sitzung erstellen" statt "Sitzungen erstellen".

create-session

Steuert die Bereitschaft, mit der 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 wahr, es sei denn, dieses Attribut ist auf "nie" gesetzt. forceEagerSessionCreation ist "false", sofern es nicht auf "always" gesetzt ist.

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

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

Jon Vaughan
quelle
Dies sind alles gute Antworten, aber ich habe meinen Kopf gegen die Wand geschlagen, um herauszufinden, wie dies mit dem Konfigurationselement <http> erreicht werden kann. Selbst mit auto-config=falsekönnen Sie anscheinend nicht ersetzen, was sich in der SECURITY_CONTEXT_FILTERPosition befindet, durch Ihr eigenes. Ich habe versucht, es mit einer ApplicationContextAwareBohne zu deaktivieren (mit Reflektion, um securityContextRepositoryeine Null-Implementierung zu erzwingen SessionManagementFilter), aber ohne Würfel. Und leider kann ich nicht auf Frühjahrssicherheit 3.1 Jahre umsteigen, was bieten würde create-session=stateless.
Jeff Evans
Bitte besuchen Sie diese Seite, immer informativ. Hoffe das hilft dir und anderen auch " baeldung.com/spring-security-session " • immer - eine Sitzung wird immer erstellt, wenn noch keine vorhanden ist • ifRequired - eine Sitzung wird nur bei Bedarf erstellt (Standard) • Niemals - das Framework wird niemals eine Sitzung selbst erstellen, aber es wird eine verwenden, wenn es bereits vorhanden ist. • Statuslos - Keine Sitzung wird von Spring Security erstellt oder verwendet
Java_Fire_Within