2010-03-24 3 views
74

Я создаю веб-приложение с Spring Security, которое будет жить на Amazon EC2 и использовать эластичные балансировочные устройства Amazon. К сожалению, ELB не поддерживает липкие сеансы, поэтому мне нужно обеспечить правильное выполнение моего приложения без сеансов.Как использовать Spring Security без сеансов?

До сих пор у меня есть настройка RememberMeServices для назначения токена через файл cookie, и это работает нормально, но я хочу, чтобы cookie истекал с сеансом браузера (например, когда браузер закрывается).

Я должен представить себе, что я не первый, кто хочет использовать Spring Security без сеансов ... любые предложения?

ответ

24

Казалось бы, еще проще весной Securitiy 3.0. Если вы используете конфигурацию пространства имен, вы можете просто сделать следующее:

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

Или вы можете настроить SecurityContextRepository в нуль, и ничто никогда не спасаются таким образом as well.

+5

Это не сработало, как я думал. Вместо этого есть комментарий ниже, который различает «никогда» и «без гражданства». Используя «никогда», мое приложение все еще создавало сеансы. Используя «stateless», мое приложение фактически оставалось без гражданства, и мне не нужно было реализовывать какие-либо переопределения, упомянутые в других ответах. См. Вопрос JIRA здесь: https://jira.springsource.org/browse/SEC-1424 – sappenin

9

Посмотрите на SecurityContextPersistenceFilter класс. Он определяет, как заполняется SecurityContextHolder. По умолчанию для хранения контекста безопасности в http-сеансе используется HttpSessionSecurityContextRepository.

Я реализовал этот механизм довольно легко, с пользовательским SecurityContextRepository.

См securityContext.xml ниже:

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

Привет Lukas, вы можете дать какие-либо более подробную информацию о вашей реализации контекста хранилища безопасности? –

+1

класс TokenSecurityContextRepository содержит HashMap contextMap. В методе loadContext() проверяется, существует ли SecurityContext для хэш-кода сеанса, передаваемого либо параметром requestParameter sid, либо cookie, либо пользовательским requestHeader или комбинацией из вышеперечисленного. Возвращает SecurityContextHolder.createEmptyContext(), если контекст не может быть разрешен. Метод saveContext помещает разрешенный контекст в контекстную карту. –

8

Фактически create-session="never" не означает быть полностью без гражданства. Для этого в Spring Security есть an issue.

3

Только быстрое примечание: это «создание-сессии», а не «создать-сессий»

create-session

Управляет рвением, с которым создана HTTP сессии.

Если не установлено, значение по умолчанию «ifRequired». Другие варианты: «всегда» и «никогда».

Настройка этого атрибута влияет на свойства allowSessionCreation и forceEagerSessionCreation объекта HttpSessionContextIntegrationFilter. allowSessionCreation всегда будет истинным, если этот атрибут не установлен как «никогда». forceEagerSessionCreation является «ложным», если он не установлен «всегда».

Таким образом, конфигурация по умолчанию позволяет создавать сеанс, но не заставляет его. Исключение - если включен одновременный контроль сеанса, когда forceEagerSessionCreation будет установлено значение true, независимо от того, что здесь задается. Использование «never» могло бы вызвать исключение во время инициализации HttpSessionContextIntegrationFilter.

Для получения подробной информации об использовании сеанса в HttpSessionSecurityContextRepository javadoc имеется хорошая документация.

+0

спасибо за отзыв! –

+0

Все это отличные ответы, но я бил головой о стену, пытаясь понять, как этого добиться, используя элемент конфигурации . Даже с 'auto-config = false', вы, по-видимому, не можете заменить то, что находится в' SECURITY_CONTEXT_FILTER', своей собственной. Я пытался взломать его с помощью некоторого компонента «ApplicationContextAware» (используя отражение, чтобы заставить «securityContextRepository» выполнить пустую реализацию в SessionManagementFilter), но без кубиков. И, к сожалению, я не могу переключиться на весеннюю безопасность 3,1 года, что обеспечило бы 'create-session = stateless'. –

+0

Посетите этот сайт, всегда информативный. Надеюсь, это поможет вам и другим, а также «http://www.baeldung.com/spring-security-session» • всегда - сеанс всегда будет создан, если он еще не существует • ifRequired - будет создан сеанс только если требуется (по умолчанию) • никогда - структура никогда не создаст сам сеанс, но он будет использовать один, если он уже существует • stateless - сеанс не будет создан или использован Spring Security –

2

После того, как вы столкнулись с многочисленными решениями, опубликованными в этом ответе, чтобы попытаться получить что-то, работающее при использовании конфигурации пространства имен <http>, я наконец нашел подход, который действительно работает для моего варианта использования.На самом деле я не требую, чтобы Spring Security не запускала сеанс (потому что я использую сеанс в других частях приложения), так как он вообще не «запоминает» аутентификацию в сеансе (он должен быть повторно проверен каждый запрос).

Для начала я не смог понять, как выполнить описанную выше технику «нулевой реализации». Неясно, следует ли установить securityContextRepository в null или в не-операционную реализацию. Первый не работает, потому что NullPointerException получает бросок в пределах SecurityContextPersistenceFilter.doFilter(). Что касается реализации не-оп, я пытался реализации самым простым способом я мог себе представить:

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

} 

Это не работает в моем приложении, из-за какого-то странного ClassCastException, имеющего дело с response_ типа.

Даже если предположить, что мне удалось найти реализацию, которая работает (просто не сохраняя контекст в сеансе), все еще существует проблема с тем, как вводить это в фильтры, созданные конфигурацией <http>. Вы не можете просто заменить фильтр на позицию SECURITY_CONTEXT_FILTER, согласно docs. Единственный способ я нашел, чтобы вклиниться в SecurityContextPersistenceFilter, который создается под одеялом было писать уродливый ApplicationContextAware боб:

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

      } 
     } 
    } 
} 

Во всяком случае, к решению, что на самом деле делает работу, хотя и очень хаком. Просто используйте Filter, которая удаляет запись сеанса, что HttpSessionSecurityContextRepository выглядит, когда он делает свое дело:

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

Тогда в конфигурации:

<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

Мы работали по тому же вопросу (впрыскивание обычай SecurityContextRepository к SecurityContextPersistenceFilter) в течение 4-5 часов. Наконец, мы поняли это. Прежде всего, в разделе 8.3 Spring Security ref. док, есть определение 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> 

И после этого определения, есть такое объяснение: «Или вы могли бы обеспечить нулевую реализацию SecurityContextRepository интерфейса, который позволит предотвратить контекст безопасности от хранятся, даже если сеанс уже создан во время запроса ».

Нам необходимо было ввести наш специальный SecurityContextRepository в SecurityContextPersistenceFilter. Таким образом, мы просто изменили определение компонента выше с помощью нашего пользовательского impl и поместили его в контекст безопасности.

Когда мы запускаем приложение, мы отслеживаем журналы и видим, что SecurityContextPersistenceFilter не использует наш пользовательский имп, он использовал HttpSessionSecurityContextRepository.

После нескольких других вещей, которые мы пробовали, мы выяснили, что нам пришлось предоставить наш пользовательский SecurityContextRepository impl с атрибутом «security-context-repository-ref» пространства имен «http». Если вы используете пространство имен «http» и хотите ввести свой собственный уязвимость SecurityContextRepository, попробуйте атрибут «security-context-repository-ref».

Когда используется пространство имен «http», отдельное определение SecurityContextPersistenceFilter игнорируется. Когда я скопировал выше, ссылка doc. не заявляет, что.

Пожалуйста, исправьте меня, если я неправильно понял вещи.

+0

Спасибо, это ценная информация. Я попробую это в своем приложении. –

+0

Спасибо, это то, что мне нужно с весной 3.0 –

+1

Вы достаточно точны, когда говорите, что пространство имен http не позволяет использовать специальный SecurityContextPersistenceFilter, мне понадобилось пару часов отладки, чтобы выяснить это. –

86

В Spring Security 3 с Java Config, вы можете использовать HttpSecurity.sessionManagement():

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

Это правильный ответ для конфигурации Java, отражающий то, что @sappenin правильно указано для xml config в комментарии к принятому ответу. Мы используем этот метод, и наше приложение без сеанса. – Paul