2010-03-24 9 views
74

Ich erstelle eine Webanwendung mit Spring Security, die auf Amazon EC2 und Amazon Elastic Load Balancer verwenden wird. Leider unterstützt ELB keine Sticky-Sitzungen. Daher muss ich sicherstellen, dass meine Anwendung ohne Sitzungen ordnungsgemäß funktioniert.Wie kann ich Spring Security ohne Sitzungen verwenden?

Bisher habe ich RememberMeServices eingerichtet, um ein Token über ein Cookie zuzuweisen, und das funktioniert gut, 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?

Antwort

24

es noch einfacher, im Frühjahr Securitiy 3.0 zu sein scheint. Wenn Sie Namespace-Konfiguration verwenden, können Sie einfach wie folgt vor:

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

Oder Sie könnten die SecurityContextRepository als null konfigurieren, und nichts jemals gespeichert as well auf diese Weise erhalten würde.

+5

Das hat nicht funktioniert, wie ich es mir vorgestellt habe. Stattdessen gibt es einen Kommentar, der zwischen "niemals" und "staatenlos" unterscheidet. Mit "nie" erstellte meine App immer noch Sitzungen. Mit "statuslos" wurde meine App tatsächlich zustandslos und ich musste keine der in anderen Antworten erwähnten Überschreibungen implementieren. Siehe das JIRA-Problem hier: https://jira.springsource.org/browse/SEC-1424 – sappenin

9

Werfen Sie einen Blick auf SecurityContextPersistenceFilter Klasse. Es definiert, wie die SecurityContextHolder gefüllt ist. Standardmäßig verwendet es HttpSessionSecurityContextRepository, um den Sicherheitskontext in der http-Sitzung zu speichern.

Ich habe diesen Mechanismus ziemlich einfach implementiert, mit benutzerdefinierten SecurityContextRepository.

Siehe securityContext.xml unter:

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

Hallo Lukas, können Sie weitere Details zu Ihrem Sicherheitskontext Repository Implementierung geben? –

+1

Klasse TokenSecurityContextRepository enthält HashMap contextMap. In der loadContext() -Methode wird überprüft, ob SecurityContext für Session-Hash-Code existiert, der entweder von requestParameter sid oder von cookie oder von custom requestHeader oder einer Kombination von obigem übergeben wird. Gibt SecurityContextHolder.createEmptyContext() zurück, wenn der Kontext nicht aufgelöst werden konnte. Die Methode saveContext setzt den aufgelösten Kontext in contextMap. –

8

Eigentlich create-session="never" bedeutet nicht, komplett staatenlos zu sein. Es gibt an issue dafür in Spring Security Issue Management.

3

Nur eine kurze Anmerkung: es ist "create-Sitzung" statt "create-Sitzungen"

create-session

Steuern 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 wahr, es sei denn, dieses Attribut ist auf "nie" gesetzt. forceEagerSessionCreation ist "false", es sei denn, es ist auf "immer" gesetzt.

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

Für spezifische Details der Sitzungsnutzung gibt es einige gute Dokumentation im HttpSessionSecurityContextRepository javadoc.

+0

danke für den Tipp! –

+0

Das sind alles gute Antworten, aber ich habe meinen Kopf gegen die Wand geschlagen, um herauszufinden, wie man das erreicht, wenn man das Config-Element verwendet. Selbst mit 'auto-config = false' können Sie scheinbar nicht ersetzen, was in der Position' SECURITY_CONTEXT_FILTER' steht. Ich hackte herum und versuchte, es mit einer 'ApplicationContextAware'-Bean zu deaktivieren (indem ich Reflection verwendete, um das' securityContextRepository' zu einer Null-Implementierung in 'SessionManagementFilter' zu zwingen), aber keine Würfel. Und leider kann ich nicht auf 3,1 Jahre Springfeder wechseln, was "create-session = stateless" bedeutet. –

+0

Bitte besuchen Sie diese Seite, immer informativ. Hoffe, das hilft dir und anderen auch "http://www.baeldung.com/spring-security-session" • immer - eine Sitzung wird immer erstellt, wenn sie noch nicht existiert • ifRequired - eine Sitzung wird erstellt nur wenn erforderlich (Standard) • nie - das Framework erstellt nie selbst eine Sitzung, aber es wird eine verwenden, wenn es bereits existiert • statuslos - keine Sitzung wird von Spring Security erstellt oder verwendet –

2

Nach dem Kampf mit den zahlreichen Lösungen in dieser Antwort geschrieben, um zu versuchen, etwas funktioniert bei der Verwendung der Namespace-Konfiguration <http>, fand ich endlich einen Ansatz, der tatsächlich für meinen Anwendungsfall funktioniert.Ich benötige eigentlich nicht, dass Spring Security keine Sitzung startet (weil ich Sitzung in anderen Teilen der Anwendung verwende), nur dass es sich nicht an die Authentifizierung in der Sitzung "erinnert" (es sollte erneut überprüft werden) jede Anfrage).

Zu Beginn war ich nicht in der Lage, herauszufinden, wie man die oben beschriebene "Nullimplementierung" -Technik durchführt. Es war nicht klar, ob Sie das securityContextRepository auf null oder auf eine No-Op-Implementierung setzen sollten. Das ehemalige funktioniert nicht, weil ein NullPointerException innerhalb SecurityContextPersistenceFilter.doFilter() geworfen wird. Da für die Umsetzung no-op, habe ich versucht, auf einfachste Art und Weise der Umsetzung könnte ich mir vorstellen:

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

} 

Das ist nicht in meiner Anwendung nicht funktioniert, weil einige seltsame ClassCastException mit dem response_ Art zu tun.

Selbst wenn ich eine Implementierung gefunden habe, die funktioniert (indem ich einfach nicht den Kontext in der Sitzung ablege), gibt es immer noch das Problem, wie man das in die von der <http>-Konfiguration erstellten Filter einspeist. Sie können den Filter nicht einfach an der Position SECURITY_CONTEXT_FILTER gemäß der docs ersetzen. Der einzige Weg, ich in die SecurityContextPersistenceFilter Haken gefunden, die unter der Decke geschaffen war 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 hackish. Verwenden Sie einfach einen Filter, der die Sitzung Eintrag löscht, dass die HttpSessionSecurityContextRepository für aussieht, wenn es hat seine Sache:

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

Wir sind auf der gleichen Ausgabe gearbeitet (eine benutzerdefinierte Injektion SecurityContextRepository zu SecurityContextPersistenceFilter) für 4-5 Stunden heute. Endlich haben wir es herausgefunden. Zuallererst, in Abschnitt 8.3 von Spring Security ref. doc, gibt es eine SecurityContextPersistenceFilter Bohne 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 diese Erklärung: „Alternativ Sie eine Null-Implementierung der SecurityContextRepository Schnittstelle zur Verfügung stellen könnten, die den Sicherheitskontext verhindern werden von gespeichert werden, auch wenn während der Anfrage bereits eine Sitzung erstellt wurde. "

Wir mussten unser benutzerdefiniertes SecurityContextRepository in den SecurityContextPersistenceFilter einspeisen. Also haben wir einfach die oben genannte Bean-Definition mit unserem benutzerdefinierten Impl geändert und in den Sicherheitskontext eingefügt.

Beim Ausführen der Anwendung verfolgten wir die Protokolle und sahen, dass SecurityContextPersistenceFilter nicht unser benutzerdefiniertes Impl verwendete, sondern das HttpSessionSecurityContextRepository.

Nach ein paar anderen Dingen, die wir ausprobiert haben, fanden wir heraus, dass wir unser benutzerdefiniertes SecurityContextRepository impl mit dem Attribut "security-context-repository-ref" des Namensraums "http" versehen mussten. Wenn Sie den Namensraum "http" verwenden und Ihr eigenes SecurityContextRepository-Impl injizieren möchten, versuchen Sie das Attribut "security-context-repository-ref".

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

Bitte korrigieren Sie mich, wenn ich die Dinge missverstanden habe.

+0

Danke, das sind wertvolle Informationen. Ich werde es in meiner Bewerbung ausprobieren. –

+0

Danke, das ist, was ich mit Frühjahr 3.0 brauchte –

+1

Sie sind ziemlich genau, wenn Sie sagen, dass der http-Namespace nicht für eine benutzerdefinierte SecurityContextPersistenceFilter erlaubt, dauerte es ein paar Stunden Debugging, um es herauszufinden –

86

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); 
} 
+2

Dies ist die korrekte Antwort für die Java-Konfiguration, die spiegelt, was @sappenin korrekt für xml config in einem Kommentar zu der akzeptierten Antwort angegeben hat. Wir verwenden diese Methode und unsere Anwendung ist in der Tat sessionless. – Paul

Verwandte Themen