Làm cách nào để sử dụng Spring Security mà không có phiên?


99

Tôi đang xây dựng một ứng dụng web với Spring Security sẽ hoạt động trên Amazon EC2 và sử dụng Bộ cân bằng tải đàn hồi của Amazon. Rất tiếc, ELB không hỗ trợ các phiên cố định, vì vậy tôi cần đảm bảo ứng dụng của mình hoạt động bình thường mà không có phiên.

Cho đến nay, tôi đã thiết lập RememberMeServices để gán mã thông báo qua cookie và điều này hoạt động tốt, nhưng tôi muốn cookie hết hạn với phiên trình duyệt (ví dụ: khi trình duyệt đóng).

Tôi phải tưởng tượng tôi không phải là người đầu tiên muốn sử dụng Spring Security mà không có phiên ... bất kỳ đề xuất nào?

Câu trả lời:


124

Trong Spring Security 3 với Java Config , bạn có thể sử dụng HttpSecurity.sessionManagement () :

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

2
Đây là câu trả lời chính xác cho cấu hình Java, sao chép những gì @sappenin đã nêu chính xác cho cấu hình xml trong một nhận xét về câu trả lời được chấp nhận. Chúng tôi sử dụng phương pháp này và thực sự ứng dụng của chúng tôi là không phiên.
Paul

Điều này có một tác dụng phụ. Vùng chứa Tomcat sẽ nối thêm "; jsessionid = ..." vào các yêu cầu về hình ảnh, bảng định kiểu, v.v., vì Tomcat không thích không có trạng thái và Spring Security sau đó sẽ chặn các nội dung này trong lần tải đầu tiên vì "URL chứa String độc hại tiềm ẩn ';' ".
workerjoe

@workerjoe Vì vậy, những gì bạn đang cố gắng nói bởi cấu hình java này, các phiên không được tạo bởi bảo mật mùa xuân thay vì tomcat?
Vishwas Atrey

@VishwasAtrey Theo hiểu biết của tôi (có thể sai), Tomcat tạo và duy trì các phiên. Spring tận dụng lợi thế của chúng, thêm dữ liệu của riêng nó. Tôi đã cố gắng tạo một ứng dụng web không trạng thái và nó không hoạt động, như tôi đã đề cập ở trên. Xem câu trả lời này cho câu hỏi của riêng tôi để biết thêm.
workerjoe

28

Nó dường như thậm chí còn dễ dàng hơn trong Spring Securitiy 3.0. Nếu bạn đang sử dụng cấu hình không gian tên, bạn có thể chỉ cần làm như sau:

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

Hoặc bạn có thể cấu hình các SecurityContextRepository như null, và không có gì bao giờ sẽ được lưu như vậy cũng .


5
Điều này không hoạt động như tôi nghĩ. Thay vào đó, có một bình luận bên dưới phân biệt giữa "không bao giờ" và "không trạng thái". Sử dụng "không bao giờ", ứng dụng của tôi vẫn đang tạo phiên. Sử dụng "không trạng thái", ứng dụng của tôi thực sự không có trạng thái và tôi không cần thực hiện bất kỳ ghi đè nào được đề cập trong các câu trả lời khác. Xem sự cố JIRA tại đây: jira.springsource.org/browse/SEC-1424
sappenin

27

Chúng tôi đã giải quyết vấn đề tương tự (đưa SecurityContextRepository tùy chỉnh vào SecurityContextPersistenceFilter) trong 4-5 giờ hôm nay. Cuối cùng, chúng tôi đã tìm ra nó. Trước hết, trong phần 8.3 của Spring Security ref. doc, có một định nghĩa về 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>

Và sau định nghĩa này, có lời giải thích này: "Ngoài ra, bạn có thể cung cấp triển khai rỗng của giao diện SecurityContextRepository, điều này sẽ ngăn không cho lưu trữ ngữ cảnh bảo mật, ngay cả khi một phiên đã được tạo trong khi yêu cầu."

Chúng tôi cần đưa SecurityContextRepository tùy chỉnh của mình vào SecurityContextPersistenceFilter. Vì vậy, chúng tôi chỉ cần thay đổi định nghĩa bean ở trên bằng cách cấy ghép tùy chỉnh của chúng tôi và đặt nó vào ngữ cảnh bảo mật.

Khi chúng tôi chạy ứng dụng, chúng tôi đã theo dõi nhật ký và thấy rằng SecurityContextPersistenceFilter không sử dụng mô hình tùy chỉnh của chúng tôi, nó đang sử dụng HttpSessionSecurityContextRepository.

Sau một vài thao tác khác mà chúng tôi đã thử, chúng tôi phát hiện ra rằng chúng tôi phải cung cấp bộ phận ghép SecurityContextRepository tùy chỉnh của mình với thuộc tính "security-context-repository-ref" của không gian tên "http". Nếu bạn sử dụng không gian tên "http" và muốn đưa vào hệ thống cấy ghép SecurityContextRepository của riêng mình, hãy thử thuộc tính "security-context-repository-ref".

Khi không gian tên "http" được sử dụng, định nghĩa SecurityContextPersistenceFilter riêng biệt sẽ bị bỏ qua. Như tôi đã sao chép ở trên, tài liệu tham khảo. không nêu điều đó.

Xin vui lòng sửa cho tôi nếu tôi hiểu sai những điều.


Cảm ơn, đây là thông tin có giá trị. Tôi sẽ thử nó trong ứng dụng của tôi.
Jeff Evans

Cảm ơn, đó là những gì tôi cần thiết với mùa xuân 3,0
Justin Ludwig

1
Bạn đang khá chính xác khi bạn nói rằng http namespace không cho phép một SecurityContextPersistenceFilter tùy chỉnh, nó đã cho tôi một vài giờ gỡ lỗi để con nó ra
Jaime Hablutzel

Cảm ơn bạn rất nhiều vì đã gửi bài này! Tôi đã định xé ra sợi tóc nhỏ mà tôi có. Tôi đã tự hỏi tại sao phương thức setSecurityContextRepository của SecurityContextPersistenceFilter không được dùng nữa (các tài liệu nói rằng sử dụng phương thức chèn hàm tạo, điều này cũng không đúng).
fool4jesus

10

Hãy nhìn vào SecurityContextPersistenceFilterlớp học. Nó xác định cách SecurityContextHolderđược điền. Theo mặc định, nó sử dụng HttpSessionSecurityContextRepositoryđể lưu trữ ngữ cảnh bảo mật trong phiên http.

Tôi đã thực hiện cơ chế này khá dễ dàng, với tùy chỉnh SecurityContextRepository.

Xem securityContext.xmlbên dưới:

<?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
Xin chào Lukas, bạn có thể cung cấp thêm bất kỳ chi tiết nào về việc triển khai kho ngữ cảnh bảo mật của mình không?
Jim Downing

1
lớp TokenSecurityContextRepository chứa HashMap <String, SecurityContext> contextMap. Trong phương thức loadContext () kiểm tra xem có tồn tại SecurityContext cho mã băm phiên được truyền bởi sid requestParameter hoặc cookie, hoặc requestHeader tùy chỉnh hoặc kết hợp của bất kỳ điều gì ở trên hay không. Trả về SecurityContextHolder.createEmptyContext () nếu không thể giải quyết được ngữ cảnh. Phương thức saveContext đặt ngữ cảnh đã giải quyết vào contextMap.
Lukas Herman

8

Thực ra create-session="never"không có nghĩa là hoàn toàn vô quốc tịch. Có một vấn đề cho điều đó trong quản lý sự cố Spring Security.


3

Sau khi vật lộn với nhiều giải pháp được đăng trong câu trả lời này, để cố gắng làm cho một cái gì đó hoạt động khi sử dụng <http> cấu hình không gian tên, cuối cùng tôi đã tìm thấy một cách tiếp cận thực sự phù hợp với trường hợp sử dụng của tôi. Tôi không thực sự yêu cầu Spring Security không bắt đầu một phiên (vì tôi sử dụng phiên trong các phần khác của ứng dụng), chỉ là nó hoàn toàn không "nhớ" xác thực trong phiên (nó nên được kiểm tra lại mọi yêu cầu).

Để bắt đầu, tôi không thể tìm ra cách thực hiện kỹ thuật "triển khai null" được mô tả ở trên. Không rõ liệu bạn có nên đặt securityContextRepository thành nullhay không. Cái trước không hoạt động vì một cái NullPointerExceptionbị ném vào bên trong SecurityContextPersistenceFilter.doFilter(). Đối với việc triển khai no-op, tôi đã thử triển khai theo cách đơn giản nhất mà tôi có thể tưởng tượng:

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

}

Điều này không hoạt động trong ứng dụng của tôi, vì có một số điều kỳ lạ liên ClassCastExceptionquan đến response_loại.

Ngay cả khi giả sử tôi đã quản lý để tìm thấy một triển khai hoạt động (đơn giản là không lưu trữ ngữ cảnh trong phiên), vẫn còn vấn đề về cách đưa nó vào các bộ lọc được xây dựng bởi <http>cấu hình. Bạn không thể chỉ cần thay thế bộ lọc tại SECURITY_CONTEXT_FILTERvị trí, theo tài liệu . Cách duy nhất tôi tìm thấy để móc vào SecurityContextPersistenceFiltercái được tạo dưới bìa là viết một ApplicationContextAwarehạt đậu xấu xí :

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

            }
        }
    }
}

Dù sao, đối với giải pháp thực sự hoạt động, mặc dù rất khó. Chỉ cần sử dụng một lệnh Filterxóa mục nhập phiên mà mục này HttpSessionSecurityContextRepositorysẽ tìm kiếm khi nó thực hiện nhiệm vụ của mình:

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

Sau đó, trong cấu hình:

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

9 năm sau, đây vẫn là câu trả lời đúng. Bây giờ chúng ta có thể sử dụng cấu hình Java thay vì XML. Tôi đã thêm bộ lọc tùy chỉnh trong của mình WebSecurityConfigurerAdaptervới " http.addFilterBefore(new SpringSecuritySessionDeletingFilter(), SecurityContextPersistenceFilter.class)"
workerjoe

3

Chỉ cần lưu ý nhanh: đó là "tạo phiên" chứ không phải "phiên tạo"

tạo phiên

Kiểm soát mức độ háo hức mà phiên HTTP được tạo.

Nếu không được đặt, mặc định là "ifRequired". Các tùy chọn khác là "luôn luôn" và "không bao giờ".

Việc thiết lập thuộc tính này ảnh hưởng đến các thuộc tính allowSessionCreation và forceEagerSessionCreation của HttpSessionContextIntegrationFilter. allowSessionCreation sẽ luôn đúng trừ khi thuộc tính này được đặt thành "never". forceEagerSessionCreation là "false" trừ khi nó được đặt thành "always".

Vì vậy cấu hình mặc định cho phép tạo phiên nhưng không bắt buộc. Ngoại lệ là nếu điều khiển phiên đồng thời được bật, khi forceEagerSessionCreation sẽ được đặt thành true, bất kể cài đặt ở đây là gì. Việc sử dụng "never" sau đó sẽ gây ra một ngoại lệ trong quá trình khởi tạo HttpSessionContextIntegrationFilter.

Để biết chi tiết cụ thể về việc sử dụng phiên, có một số tài liệu tốt trong javadoc HttpSessionSecurityContextRepository.


Đây đều là những câu trả lời tuyệt vời, nhưng tôi đã đập đầu vào tường khi cố gắng tìm cách đạt được điều này khi sử dụng phần tử cấu hình <http>. Ngay cả với auto-config=false, bạn dường như không thể thay thế những gì ở SECURITY_CONTEXT_FILTERvị trí của riêng bạn. Tôi đã hack xung quanh để cố gắng vô hiệu hóa nó bằng một số ApplicationContextAwarebean (sử dụng phản chiếu để buộc securityContextRepositorythực hiện thành null SessionManagementFilter) nhưng không có xúc xắc. Và thật đáng buồn, tôi không thể chuyển sang Spring-security 3.1 năm sẽ cung cấp create-session=stateless.
Jeff Evans

Vui lòng truy cập trang web này, luôn có thông tin. Hy vọng điều này sẽ giúp ích cho bạn và những người khác " baeldung.com/spring-security-session " • luôn luôn - một phiên sẽ luôn được tạo nếu chưa tồn tại • ifRequired - một phiên sẽ chỉ được tạo nếu được yêu cầu (mặc định) • không bao giờ - khuôn khổ sẽ không bao giờ tạo ra một phiên bản thân nhưng nó sẽ sử dụng một nếu nó đã tồn tại • stateless - không có phiên sẽ được tạo ra hoặc sử dụng bởi xuân an
Java_Fire_Within
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.