Как я могу использовать Spring Безопасность без сеансов?

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

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

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

Ответ 1

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

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

Или вы можете настроить SecurityContextRepository как null, и ничто никогда не будет сохранено таким образом .

Ответ 2

В Spring Security 3 с Конфигурация Java вы можете использовать HttpSecurity.sessionManagement():

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

Ответ 3

Мы работали над одной и той же проблемой (ввод пользовательского SecurityContextRepository в SecurityContextPersistenceFilter) в течение 4-5 часов. Наконец, мы поняли это. Прежде всего, в разделе 8.3 of Spring Security ref. doc, существует определение SecurityContextPersistenceFilter bean

<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. Таким образом, мы просто изменили определение bean выше с помощью нашего пользовательского impl и поместили его в контекст безопасности.

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

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

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

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

Ответ 4

Взгляните на класс 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>

Ответ 5

Фактически create-session="never" не означает, что он полностью не имеет гражданства. Там проблема для этого в Spring управлении проблемами безопасности.

Ответ 6

Простое примечание: это "создать сеанс", а не "создавать сеансы"

create-session

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

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

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

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

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

Ответ 7

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

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

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

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>