Bagaimana saya bisa menggunakan Keamanan Musim Semi tanpa sesi?

99

Saya membuat aplikasi web dengan Spring Security yang akan tersedia di Amazon EC2 dan menggunakan Elastic Load Balancers Amazon. Sayangnya, ELB tidak mendukung sesi lengket, jadi saya perlu memastikan aplikasi saya berfungsi dengan baik tanpa sesi.

Sejauh ini, saya telah menyiapkan RememberMeServices untuk menetapkan token melalui cookie, dan ini berfungsi dengan baik, tetapi saya ingin cookie itu kedaluwarsa dengan sesi browser (mis. Saat browser ditutup).

Saya harus membayangkan saya bukan orang pertama yang ingin menggunakan Keamanan Musim Semi tanpa sesi ... ada saran?

Jarrod Carlson
sumber

Jawaban:

124

Di Spring Security 3 dengan Java Config , Anda dapat menggunakan HttpSecurity.sessionManagement () :

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
Ben Hutchison
sumber
2
Ini adalah jawaban yang benar untuk konfigurasi Java, mencerminkan apa yang @sappenin nyatakan dengan benar untuk konfigurasi xml dalam komentar pada jawaban yang diterima. Kami menggunakan metode ini dan memang aplikasi kami tidak memiliki sesi.
Paul
Ini memiliki efek samping. Penampung Tomcat akan menambahkan "; jsessionid = ..." ke permintaan gambar, stylesheet, dll, karena Tomcat tidak suka stateless, dan Keamanan Musim Semi kemudian akan memblokir aset ini pada pemuatan pertama karena "URL berisi String ';' yang berpotensi berbahaya.
workerjoe
@workerjoe Jadi, apa yang ingin Anda katakan dengan konfigurasi java ini, sesi tidak dibuat oleh keamanan pegas, bukan tomcat?
Vishwas Atrey
@VishwasAtrey Dalam pemahaman saya (yang mungkin salah), Tomcat membuat dan memelihara sesi. Spring memanfaatkannya, menambahkan datanya sendiri. Saya mencoba membuat aplikasi web tanpa negara dan tidak berhasil, seperti yang saya sebutkan di atas. Lihat jawaban atas pertanyaan saya sendiri ini untuk lebih lanjut.
workerjoe
28

Tampaknya menjadi lebih mudah di Spring Securitiy 3.0. Jika Anda menggunakan konfigurasi namespace, Anda cukup melakukan hal berikut:

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

Atau Anda dapat mengonfigurasi SecurityContextRepository sebagai null, dan tidak ada yang bisa disimpan dengan cara itu juga .

Jarrod Carlson
sumber
5
Ini tidak bekerja seperti yang saya kira. Sebaliknya, ada komentar di bawah ini yang membedakan antara "tidak pernah" dan "tanpa negara". Menggunakan "tidak pernah", aplikasi saya masih membuat sesi. Menggunakan "stateless", aplikasi saya benar-benar menjadi tanpa kewarganegaraan, dan saya tidak perlu menerapkan salah satu penggantian yang disebutkan dalam jawaban lain. Lihat masalah JIRA di
sappenin
27

Kami menangani masalah yang sama (memasukkan SecurityContextRepository kustom ke SecurityContextPersistenceFilter) selama 4-5 jam hari ini. Akhirnya, kami menemukan jawabannya. Pertama-tama, di bagian 8.3 dari Keamanan Musim Semi ref. doc, ada definisi kacang 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>

Dan setelah definisi ini, ada penjelasan berikut: "Alternatifnya, Anda dapat memberikan implementasi null dari antarmuka SecurityContextRepository, yang akan mencegah konteks keamanan disimpan, bahkan jika sesi telah dibuat selama permintaan."

Kami perlu memasukkan SecurityContextRepository kustom kami ke dalam SecurityContextPersistenceFilter. Jadi kami hanya mengubah definisi kacang di atas dengan impl kustom kami dan memasukkannya ke dalam konteks keamanan.

Saat kami menjalankan aplikasi, kami menelusuri log dan melihat bahwa SecurityContextPersistenceFilter tidak menggunakan impl kustom kami, melainkan menggunakan HttpSessionSecurityContextRepository.

Setelah beberapa hal lain yang kami coba, kami menemukan bahwa kami harus memberikan custom SecurityContextRepository impl kami dengan atribut "security-context-repository-ref" dari namespace "http". Jika Anda menggunakan namespace "http" dan ingin memasukkan impl SecurityContextRepository Anda sendiri, coba atribut "security-context-repository-ref".

Ketika namespace "http" digunakan, definisi SecurityContextPersistenceFilter yang terpisah diabaikan. Seperti yang saya salin di atas, dokumen referensi. tidak menyatakan itu.

Harap perbaiki saya jika saya salah paham.

Basri Kahveci
sumber
Terima kasih, ini adalah informasi yang berharga. Saya akan mencobanya di aplikasi saya.
Jeff Evans
Terima kasih, itulah yang saya butuhkan dengan musim semi 3.0
Justin Ludwig
1
Anda cukup akurat ketika Anda mengatakan bahwa namespace http tidak mengizinkan SecurityContextPersistenceFilter kustom, saya butuh beberapa jam debugging untuk mengetahuinya
Jaime Hablutzel
Terimakasih banyak sudah mem-posting ini! Aku akan mencabut sedikit rambut yang kumiliki. Saya bertanya-tanya mengapa metode setSecurityContextRepository dari SecurityContextPersistenceFilter tidak digunakan lagi (dokumen mengatakan untuk menggunakan injeksi konstruktor, yang juga tidak benar).
Fool4jesus
10

Lihat SecurityContextPersistenceFilterkelas. Ini mendefinisikan bagaimana SecurityContextHolderpenduduknya. Secara default digunakan HttpSessionSecurityContextRepositoryuntuk menyimpan konteks keamanan di sesi http.

Mekanisme ini sudah saya implementasikan dengan cukup mudah, dengan custom SecurityContextRepository.

Lihat di securityContext.xmlbawah ini:

<?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
sumber
1
Halo Lukas, dapatkah Anda memberikan detail lebih lanjut tentang implementasi repositori konteks keamanan Anda?
Jim Downing
1
class TokenSecurityContextRepository berisi HashMap <String, SecurityContext> contextMap. Dalam metode loadContext (), periksa apakah ada SecurityContext untuk kode hash sesi yang diteruskan oleh sid requestParameter, atau cookie, atau requestHeader khusus atau kombinasi dari semua di atas. Mengembalikan SecurityContextHolder.createEmptyContext () jika konteks tidak dapat diselesaikan. Metode saveContext menempatkan konteks yang diselesaikan ke contextMap.
Lukas Herman
8

Sebenarnya create-session="never"tidak berarti sepenuhnya tanpa kewarganegaraan. Ada masalah untuk itu dalam manajemen masalah Keamanan Musim Semi.

hleinone
sumber
3

Setelah berjuang dengan banyak solusi yang diposting dalam jawaban ini, untuk mencoba mendapatkan sesuatu yang berfungsi saat menggunakan <http>konfigurasi namespace, saya akhirnya menemukan pendekatan yang benar-benar berfungsi untuk kasus penggunaan saya. Saya sebenarnya tidak mengharuskan Spring Security tidak memulai sesi (karena saya menggunakan sesi di bagian lain aplikasi), hanya saja Spring Security tidak "mengingat" otentikasi dalam sesi sama sekali (harus diperiksa ulang setiap permintaan).

Untuk memulainya, saya tidak dapat menemukan cara untuk melakukan teknik "implementasi nol" yang dijelaskan di atas. Tidak jelas apakah Anda harus menyetel securityContextRepository ke nullatau ke implementasi tanpa operasi. Yang pertama tidak berfungsi karena a NullPointerExceptionterlempar ke dalam SecurityContextPersistenceFilter.doFilter(). Sedangkan untuk implementasi tanpa operasi, saya mencoba menerapkan dengan cara yang paling sederhana yang dapat saya bayangkan:

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

}

Ini tidak berfungsi di aplikasi saya, karena ada yang aneh ClassCastExceptiondengan response_jenisnya.

Bahkan dengan asumsi saya berhasil menemukan implementasi yang berfungsi (dengan tidak menyimpan konteks dalam sesi), masih ada masalah tentang bagaimana menyuntikkannya ke dalam filter yang dibangun oleh <http>konfigurasi. Anda tidak bisa begitu saja mengganti filter pada SECURITY_CONTEXT_FILTERposisi tersebut, sesuai dengan dokumen . Satu-satunya cara saya menemukan untuk mengaitkan SecurityContextPersistenceFilteryang dibuat di bawah sampul adalah dengan menulis ApplicationContextAwarekacang jelek :

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

            }
        }
    }
}

Bagaimanapun, ke solusi yang benar-benar berfungsi, meskipun sangat hackish. Cukup gunakan Filteryang menghapus entri sesi yang HttpSessionSecurityContextRepositorydicari saat melakukan tugasnya:

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

Kemudian di konfigurasi:

<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
sumber
Sembilan tahun kemudian, ini masih merupakan jawaban yang benar. Sekarang kita bisa menggunakan konfigurasi Java sebagai pengganti XML. Saya menambahkan filter ubahsuaian di saya WebSecurityConfigurerAdapterdengan " http.addFilterBefore(new SpringSecuritySessionDeletingFilter(), SecurityContextPersistenceFilter.class)"
workerjoe
3

Hanya catatan singkat: ini "sesi-buat" daripada "sesi-buat"

buat-sesi

Mengontrol keinginan pembuatan sesi HTTP.

Jika tidak disetel, defaultnya adalah "ifRequired". Pilihan lainnya adalah "selalu" dan "tidak pernah".

Setelan atribut ini memengaruhi properti allowSessionCreation dan forceEagerSessionCreation dari HttpSessionContextIntegrationFilter. allowSessionCreation akan selalu benar kecuali atribut ini disetel ke "tidak pernah". forceEagerSessionCreation adalah "false" kecuali jika disetel ke "selalu".

Jadi konfigurasi default memungkinkan pembuatan sesi tetapi tidak memaksanya. Pengecualiannya adalah jika kontrol sesi serentak diaktifkan, saat forceEagerSessionCreation akan disetel ke true, apa pun setelannya di sini. Menggunakan "tidak pernah" akan menyebabkan pengecualian selama inisialisasi HttpSessionContextIntegrationFilter.

Untuk detail spesifik dari penggunaan sesi, ada beberapa dokumentasi bagus di javadoc HttpSessionSecurityContextRepository.

Jon Vaughan
sumber
Ini semua adalah jawaban yang bagus, tetapi saya telah membenturkan kepala saya ke dinding mencoba mencari cara untuk mencapai ini ketika menggunakan elemen konfigurasi <htt”. Bahkan dengan auto-config=false, Anda tampaknya tidak dapat mengganti apa yang ada di SECURITY_CONTEXT_FILTERposisi dengan milik Anda. Saya telah meretas mencoba untuk menonaktifkannya dengan beberapa ApplicationContextAwarekacang (menggunakan refleksi untuk memaksa securityContextRepositoryimplementasi nol SessionManagementFilter) tetapi tidak ada dadu. Dan sayangnya, saya tidak bisa beralih ke keamanan musim semi 3,1 tahun yang akan memberikan create-session=stateless.
Jeff Evans
Silakan kunjungi situs ini, selalu informatif. Semoga ini bisa membantu Anda dan orang lain juga " baeldung.com/spring-security-session " • always - sesi akan selalu dibuat jika belum ada • ifRequired - sesi hanya akan dibuat jika diperlukan (default) • never - framework tidak akan pernah membuat sesi itu sendiri tetapi akan menggunakannya jika sudah ada • stateless - tidak ada sesi yang akan dibuat atau digunakan oleh Spring Security
Java_Fire_Within