Jak mogę używać Spring Security bez sesji?

99

Buduję aplikację internetową z Spring Security, która będzie działać na Amazon EC2 i używać elastycznych równoważników obciążenia Amazon. Niestety ELB nie obsługuje sesji sticky, więc muszę upewnić się, że moja aplikacja działa poprawnie bez sesji.

Do tej pory skonfigurowałem RememberMeServices, aby przypisywać token za pośrednictwem pliku cookie i działa to dobrze, ale chcę, aby plik cookie wygasał wraz z sesją przeglądarki (np. Po zamknięciu przeglądarki).

Muszę sobie wyobrazić, że nie jestem pierwszą osobą, która chce używać Spring Security bez sesji ... jakieś sugestie?

Jarrod Carlson
źródło

Odpowiedzi:

124

W Spring Security 3 z Java Config możesz użyć HttpSecurity.sessionManagement () :

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
Ben Hutchison
źródło
2
To jest poprawna odpowiedź dla konfiguracji Java, odzwierciedlająca to, co @sappenin poprawnie podał dla konfiguracji xml w komentarzu do zaakceptowanej odpowiedzi. Używamy tej metody i rzeczywiście nasza aplikacja jest bezsesyjna.
Paul
Ma to efekt uboczny. Kontener Tomcat doda „; jsessionid = ...” do żądań dotyczących obrazów, arkuszy stylów itp., Ponieważ Tomcat nie lubi być bezstanowy, a Spring Security zablokuje te zasoby przy pierwszym ładowaniu, ponieważ „adres URL zawiera potencjalnie złośliwy String ';' ".
workerjoe
@workerjoe Więc, co próbujesz powiedzieć przez tę konfigurację java, sesje nie są tworzone przez zabezpieczenia wiosenne, a raczej przez tomcat?
Vishwas Atrey
@VishwasAtrey W moim rozumieniu (co może być błędne) Tomcat tworzy i utrzymuje sesje. Wiosna z nich korzysta, dodając własne dane. Próbowałem stworzyć bezstanową aplikację internetową i nie zadziałało, jak wspomniałem powyżej. Zobacz tę odpowiedź na moje własne pytanie, aby uzyskać więcej informacji.
workerjoe
28

Wydaje się, że w Spring Securitiy 3.0 jest jeszcze łatwiej. Jeśli używasz konfiguracji przestrzeni nazw, możesz po prostu wykonać następujące czynności:

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

Lub można skonfigurować SecurityContextRepository za nieważną, a nic by kiedykolwiek zostaną zapisane w ten sposób , jak również .

Jarrod Carlson
źródło
5
To nie zadziałało tak, jak myślałem. Zamiast tego poniżej znajduje się komentarz, który rozróżnia „nigdy” i „bezpaństwowiec”. Używając opcji „nigdy”, moja aplikacja nadal tworzyła sesje. Używając opcji „bezstanowej”, moja aplikacja faktycznie stała się bezpaństwowa i nie musiałem wdrażać żadnego z nadpisań wymienionych w innych odpowiedziach. Zobacz problem JIRA tutaj: jira.springsource.org/browse/SEC-1424
sappenin
27

Pracowaliśmy nad tym samym problemem (wstrzykiwanie niestandardowego SecurityContextRepository do SecurityContextPersistenceFilter) przez 4-5 godzin dzisiaj. W końcu to wymyśliliśmy. Przede wszystkim w sekcji 8.3 Spring Security ref. doc, istnieje definicja fasoli 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>

Po tej definicji znajduje się wyjaśnienie: „Alternatywnie można podać pustą implementację interfejsu SecurityContextRepository, która uniemożliwi przechowywanie kontekstu zabezpieczeń, nawet jeśli sesja została już utworzona podczas żądania”.

Musieliśmy wstrzyknąć nasze niestandardowe SecurityContextRepository do SecurityContextPersistenceFilter. Więc po prostu zmieniliśmy powyższą definicję fasoli za pomocą naszego niestandardowego pliku impl i umieściliśmy go w kontekście bezpieczeństwa.

Kiedy uruchomiliśmy aplikację, prześledziliśmy dzienniki i zobaczyliśmy, że SecurityContextPersistenceFilter nie korzystał z naszego niestandardowego impl, tylko z HttpSessionSecurityContextRepository.

Po kilku innych próbach stwierdziliśmy, że musimy nadać naszemu niestandardowemu plikowi impl SecurityContextRepository atrybut „security-context-repository-ref” przestrzeni nazw „http”. Jeśli używasz przestrzeni nazw „http” i chcesz wprowadzić własny plik SecurityContextRepository impl, wypróbuj atrybut „security-context-repository-ref”.

Gdy używana jest przestrzeń nazw „http”, oddzielna definicja SecurityContextPersistenceFilter jest ignorowana. Jak skopiowałem powyżej, dokument referencyjny. nie stwierdza tego.

Proszę, popraw mnie, jeśli źle zrozumiałem.

Basri Kahveci
źródło
Dzięki, to cenna informacja. Wypróbuję to w mojej aplikacji.
Jeff Evans,
Dzięki, właśnie tego potrzebowałem w przypadku wiosny 3.0
Justin Ludwig,
1
Mówisz dość precyzyjnie, że przestrzeń nazw http nie zezwala na niestandardowy
filtr
Dziękuję bardzo za opublikowanie tego! Już miałem wyrwać te małe włosy, które mam. Zastanawiałem się, dlaczego metoda setSecurityContextRepository z SecurityContextPersistenceFilter została uznana za przestarzałą (dokumentacja mówi, że należy użyć iniekcji konstruktora, co też nie jest poprawne).
fool4jesus
10

Spójrz na SecurityContextPersistenceFilterklasę. Określa, w jaki sposób SecurityContextHolderjest wypełniony. Domyślnie używa HttpSessionSecurityContextRepositorydo przechowywania kontekstu bezpieczeństwa w sesji http.

Zaimplementowałem ten mechanizm dość łatwo, z niestandardowym SecurityContextRepository.

Zobacz securityContext.xmlponiżej:

<?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>
Lukas Herman
źródło
1
Cześć Lukas, czy możesz podać więcej szczegółów na temat implementacji repozytorium kontekstów bezpieczeństwa?
Jim Downing,
1
class TokenSecurityContextRepository zawiera HashMap <String, SecurityContext> contextMap. W metodzie loadContext () sprawdza, czy istnieje SecurityContext dla kodu skrótu sesji przekazywanego przez sid requestParameter, cookie lub niestandardowy requestHeader lub kombinację dowolnego z powyższych. Zwraca SecurityContextHolder.createEmptyContext (), jeśli nie można rozpoznać kontekstu. Metoda saveContext umieszcza rozstrzygnięty kontekst w contextMap.
Lukas Herman
8

Właściwie create-session="never"nie oznacza to bycia całkowicie bezpaństwowcem. Jest to problem dla że w zarządzaniu emisyjnej Wiosna Bezpieczeństwa.

hleinone
źródło
3

Po zmaganiach z licznymi rozwiązaniami zamieszczonymi w tej odpowiedzi, aby spróbować uzyskać coś działającego podczas korzystania z <http>konfiguracji przestrzeni nazw, w końcu znalazłem podejście, które faktycznie działa w moim przypadku użycia. Właściwie nie wymagam, żeby Spring Security nie rozpoczynał sesji (ponieważ używam sesji w innych częściach aplikacji), tylko żeby w ogóle nie "pamiętał" uwierzytelnienia w sesji (powinno być ponownie sprawdzone każde żądanie).

Przede wszystkim nie byłem w stanie dowiedzieć się, jak wykonać technikę „implementacji zerowej” opisaną powyżej. Nie było jasne, czy masz ustawić securityContextRepository nullna implementację bez operacji . Ten pierwszy nie działa, ponieważ NullPointerExceptionzostaje wrzucony do środka SecurityContextPersistenceFilter.doFilter(). Jeśli chodzi o implementację no-op, próbowałem zaimplementować ją w najprostszy sposób, jaki mogłem sobie wyobrazić:

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

}

To nie działa w mojej aplikacji z powodu dziwnego ClassCastExceptionzwiązku z response_typem.

Nawet zakładając, że udało mi się znaleźć implementację, która działa (po prostu nie przechowując kontekstu w sesji), nadal istnieje problem, jak wstrzyknąć ją do filtrów zbudowanych przez <http>konfigurację. Nie można po prostu wymienić filtra na SECURITY_CONTEXT_FILTERmiejscu, zgodnie z dokumentacją . Jedynym sposobem, w jaki znalazłem zaczepienie w tym, SecurityContextPersistenceFilterco powstaje pod okładkami, było napisanie brzydkiej ApplicationContextAwarefasoli:

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

            }
        }
    }
}

W każdym razie do rozwiązania, które faktycznie działa, choć jest bardzo hakerskie. Po prostu użyj a, Filterktóry usuwa wpis sesji, którego HttpSessionSecurityContextRepositoryszuka, gdy robi swoje:

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

Następnie w konfiguracji:

<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>
Jeff Evans
źródło
Dziewięć lat później nadal jest to właściwa odpowiedź. Teraz możemy użyć konfiguracji Java zamiast XML. Dodałem niestandardowy filtr do mojego WebSecurityConfigurerAdapterz „ http.addFilterBefore(new SpringSecuritySessionDeletingFilter(), SecurityContextPersistenceFilter.class)
workerjoe
3

Krótka uwaga: jest to „tworzenie sesji”, a nie „tworzenie sesji”

tworzenie sesji

Kontroluje zapał, z jakim tworzona jest sesja HTTP.

Jeśli nie jest ustawione, przyjmuje wartość domyślną „ifRequired”. Inne opcje to „zawsze” i „nigdy”.

Ustawienie tego atrybutu wpływa na właściwości allowSessionCreation i forceEagerSessionCreation HttpSessionContextIntegrationFilter. allowSessionCreation zawsze będzie mieć wartość true, chyba że ten atrybut ma wartość „never”. forceEagerSessionCreation ma wartość „false”, chyba że jest ustawiona na „always”.

Zatem domyślna konfiguracja umożliwia tworzenie sesji, ale nie wymusza tego. Wyjątkiem jest sytuacja, w której kontrola sesji współbieżnej jest włączona, gdy forceEagerSessionCreation zostanie ustawiona na true, niezależnie od tego, jakie jest tutaj ustawienie. Użycie „never” spowodowałoby wówczas wyjątek podczas inicjowania HttpSessionContextIntegrationFilter.

Szczegółowe informacje na temat użycia sesji można znaleźć w dobrej dokumentacji w pliku javadoc HttpSessionSecurityContextRepository.

Jon Vaughan
źródło
To wszystko są świetne odpowiedzi, ale waliłem głową w ścianę, próbując wymyślić, jak to osiągnąć, używając elementu <http> config. Nawet z auto-config=falsenajwyraźniej nie możesz zastąpić tego, co jest na SECURITY_CONTEXT_FILTERmiejscu, swoim własnym. Hakowałem wokół, próbując go wyłączyć za pomocą jakiejś ApplicationContextAwarefasoli (używając odbicia, aby wymusić securityContextRepositoryimplementację zerową SessionManagementFilter), ale bez kości. I niestety nie mogę przejść na zabezpieczenie wiosenne 3,1 roku, które by mi zapewniło create-session=stateless.
Jeff Evans
Odwiedź tę stronę, zawsze zawierającą informacje. Mam nadzieję, że pomoże to Tobie i innym „ baeldung.com/spring-security-session ” • zawsze - sesja zostanie utworzona zawsze, jeśli jeszcze nie istnieje • ifRequired - sesja zostanie utworzona tylko w razie potrzeby (domyślnie) • nigdy - framework nigdy nie utworzy sesji samodzielnie, ale użyje jednej, jeśli już istnieje • bezstanowa - żadna sesja nie zostanie utworzona ani używana przez Spring Security
Java_Fire_Within