Як можна використовувати Spring Security без сеансів?


99

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

Поки що я маю налаштування RememberMeServices, щоб призначити маркер за допомогою файлу cookie, і це працює чудово, але я хочу, щоб печиво закінчувалося з сеансом роботи в браузері (наприклад, коли браузер закривається).

Я мушу уявити, що я не перший, хто захотів використовувати Spring Security без сесій ... будь-які пропозиції?

Відповіді:


124

У 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 у коментарі до прийнятої відповіді. Ми використовуємо цей метод, і дійсно наша програма без сеансу.
Пол

Це має побічний ефект. Контейнер Tomcat додасть "; jsessionid = ..." до запитів на зображення, таблиці стилів тощо, тому що Tomcat не любить бути без громадянства, і Spring Security буде блокувати ці активи при першому завантаженні, оскільки "URL-адреса містила потенційно шкідливий рядок ';' ".
delavjoe

@workerjoe Отже, що ви намагаєтеся сказати за допомогою цієї конфігурації java, сеанси не створюються весняною безпекою, а не tomcat?
Vishwas Atrey

@VishwasAtrey Наскільки я розумію (що може бути неправильним), Tomcat створює та підтримує сеанси. Весна користується ними, додаючи власні дані. Я спробував зробити веб-додаток без громадянства, і це не спрацювало, як я вже згадував вище. Дивіться цю відповідь на моє власне питання докладніше.
delavjoe

28

Здається, це ще простіше у Spring Securitiy 3.0. Якщо ви використовуєте конфігурацію простору імен, ви можете просто зробити наступне:

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

Або ви можете налаштувати SecurityContextRepository як нуль, і ніщо ніколи не рятуються таким чином , як добре .


5
Це не спрацювало так, як я думав, що це буде. Натомість, нижче є коментар, який розрізняє "ніколи" та "без громадянства". За допомогою програми "ніколи" мій додаток все ще створював сеанси. Використовуючи функцію "без громадянства", моя програма фактично перебуває у стані без громадянства, і мені не потрібно було реалізовувати жодне з перелічень, зазначених в інших відповідях. Дивіться випуск JIRA тут: jira.springsource.org/browse/SEC-1424
sappenin

27

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

Нам потрібно було ввести наш спеціальний сховище SecurityContextRestory в SecurityContextPersistenceFilter. Таким чином, ми просто змінили вищезгадане визначення bean за допомогою нашого користувальницького impl та поставимо його в контекст безпеки.

Під час запуску програми ми простежили журнали та побачили, що SecurityContextPersistenceFilter не використовує наш спеціальний імпл, він використовує HttpSessionSecurityContextRepository.

Після кількох речей, які ми спробували, ми з’ясували, що нам потрібно надати нашому користувальницькому SecurityContextRepository імпульс з атрибутом "security-context-repository-ref" в просторі імен "http". Якщо ви використовуєте простір імен "http" і хочете ввести свій власний імпульт SecurityContextRepository, спробуйте атрибут "security-context-repository-ref".

Коли використовується "http" простір імен, окреме визначення SecurityContextPersistenceFilter ігнорується. Як я скопіював вище, довідковий документ. не стверджує, що.

Будь ласка, виправте мене, якщо я неправильно зрозумів речі.


Дякую, це цінна інформація. Я спробую це у своїй заявці.
Джефф Еванс

Дякую, ось що мені було потрібно весною 3.0
Джастін Людвіг

1
Ви досить точні, говорячи про те, що простір імен http не дозволяє використовувати спеціальний SecurityContextPersistenceFilter, мені знадобилося кілька годин налагодження, щоб зрозуміти це
Jaime Hablutzel

Дуже дякую вам за публікацію! Я збирався вирвати, які у мене маленькі волосся. Мені було цікаво, чому метод setSecurityContextRepository SecurityContextPersistenceFilter був застарілим (документи, які говорять про використання конструкторської ін'єкції, що також не вірно).
crazy4jesus

10

Погляньте на 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>

1
Привіт Лукасе, чи можете ви детальніше розповісти про свою сховище контексту безпеки?
Джим Даунінг

1
клас TokenSecurityContextRepository містить HashMap <String, SecurityContext> contextMap. Метод loadContext () перевіряє, чи існує SecurityContext для хеш-коду сеансу, переданий або sid requestParameter sid, або cookie, або спеціальним запитомHeader, або комбінацією будь-якого вище. Повертає SecurityContextHolder.createEmptyContext (), якщо контекст не вдалося вирішити. Метод saveContext ставить вирішений контекст у контекстну карту.
Лукас Герман


3

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

Через дев'ять років це все ще правильна відповідь. Тепер ми можемо використовувати конфігурацію Java замість XML. Я додав спеціальний фільтр до свого WebSecurityConfigurerAdapter" http.addFilterBefore(new SpringSecuritySessionDeletingFilter(), SecurityContextPersistenceFilter.class)"
delavjoe

3

Просто швидка примітка: це "create-session", а не "create-session"

create-session

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

Якщо не встановлено, за замовчуванням використовується "ifRequired". Інші варіанти - "завжди" і "ніколи".

Налаштування цього атрибута впливають на властивості enableSessionCreation та forceEagerSessionCreation HttpSessionContextIntegrationFilter. enableSessionCreation завжди буде істинним, якщо цей атрибут не встановлений на "ніколи". forceEagerSessionCreation є "хибним", якщо для нього не встановлено "завжди".

Таким чином, конфігурація за замовчуванням дозволяє створювати сеанси, але не примушує її. Виняток - якщо паралельний контроль сеансу ввімкнено, коли forceEagerSessionCreation буде встановлено на істинне, незалежно від того, що тут встановлено. Використання "never" тоді призведе до виключення під час ініціалізації HttpSessionContextIntegrationFilter.

Для отримання детальної інформації про використання сеансу, у javadoc HttpSessionSecurityContextRepository є хороша документація.


Це все чудові відповіді, але я бив головою об стіну, намагаючись зрозуміти, як цього досягти, використовуючи елемент конфігурації <http>. Навіть при цьому auto-config=falseви, мабуть, не можете замінити те, що знаходиться в SECURITY_CONTEXT_FILTERположенні, своїм. Я хакнувся навколо намагаючись відключити його за допомогою якогось ApplicationContextAwareквасолі (використовуючи відображення, щоб примусити securityContextRepositoryдо нульової реалізації SessionManagementFilter), але без кісток. І, на жаль, я не можу перейти на безпеку весни 3,1 року, яка б забезпечила create-session=stateless.
Джефф Еванс

Будь ласка, відвідайте цей сайт, завжди інформативний. Сподіваємось, це допоможе вам і іншим " baeldung.com/spring-security-session " • завжди - сеанс завжди буде створений, якщо його вже не існує • ifRequired - сеанс буде створений лише за потреби (за замовчуванням) • ніколи - фреймворк ніколи не створить сам сеанс, але він використовуватиме його, якщо він уже існує • без стану - жодна сесія не буде створена або використана Spring Security
Java_Fire_Within
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.