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



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



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



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

679   7  

7 ответов:

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

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

мы работали над одной и той же проблемой (вводя пользовательский 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. Поэтому мы просто изменили определение bean выше с помощью нашего пользовательского impl и поместили его в контекст безопасности.

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

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

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

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

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

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

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

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

я реализовал этот механизм довольно легко, с custom 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>

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

просто краткое Примечание: это "create-session", а не"create-sessions"

create-session

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

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

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

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

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

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

для начала, я не удалось выяснить, как сделать метод "нулевой реализации", описанный выше. Не было ясно, должны ли вы установить securityContextRepository в null или к реализации no-op. Бывший не работает, потому что 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 Боб:

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

            }
        }
    }
}

в любом случае, к решению, которое на самом деле работает, хотя и очень hackish. Просто используйте 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>

Comments

    Ничего не найдено.