Как я могу использовать Spring Security без сессий? - PullRequest
91 голосов
/ 24 марта 2010

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

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

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

Ответы [ 7 ]

108 голосов
/ 03 июня 2014

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

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
27 голосов
/ 27 марта 2010

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

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

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

26 голосов
/ 05 октября 2011

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

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

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

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

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

9 голосов
/ 26 марта 2010

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

Я реализовал этот механизм довольно легко, с пользовательским 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>
8 голосов
/ 30 марта 2010

На самом деле create-session="never" не означает, что вы полностью лишены состояния. проблема для этого есть в управлении проблемами Spring Security.

3 голосов
/ 07 апреля 2010

Просто краткое замечание: это «create-session», а не «create-session»

create-session

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

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

Установка этого атрибута влияет на свойства allowSessionCreation и forceEagerSessionCreation объекта HttpSessionContextIntegrationFilter. allowSessionCreation всегда будет иметь значение true, если для этого атрибута не установлено значение «never». forceEagerSessionCreation имеет значение «false», если не установлено «всегда».

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

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

2 голосов
/ 01 октября 2011

Поработав с многочисленными решениями, опубликованными в этом ответе, чтобы попытаться заставить что-то работать при использовании конфигурации пространства имен <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 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...