首页 文章

如何在没有会话的情况下使用Spring Security?

提问于
浏览
84

我正在使用Spring Security构建一个Web应用程序,该应用程序将存在于Amazon EC2上并使用Amazon的Elastic Load Balancers . 不幸的是,ELB不支持粘性会话,因此我需要确保我的应用程序在没有会话的情况下正常工作 .

到目前为止,我已经设置了RememberMeServices来通过cookie分配一个令牌,这很好用,但我希望cookie随浏览器会话一起过期(例如当浏览器关闭时) .

我不得不想象我不是第一个想要在没有会话的情况下使用Spring Security的人......有什么建议吗?

7 回答

  • 26

    在带有Java Config的Spring Security 3中,您可以使用HttpSecurity.sessionManagement()

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

    我们今天处理了相同的问题(向SecurityContextPersistenceFilter注入自定义SecurityContextRepository)4-5小时 . 最后,我们弄明白了 . 首先,在Spring Security引用的8.3节中 . doc,有一个SecurityContextPersistenceFilter bean定义

    <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接口的null实现,这将阻止安全上下文的存储,即使在请求期间已经创建了会话 . ”

    我们需要将自定义SecurityContextRepository注入SecurityContextPersistenceFilter . 因此,我们只需使用自定义impl更改上面的bean定义,并将其置于安全上下文中 .

    当我们运行应用程序时,我们跟踪日志并看到SecurityContextPersistenceFilter没有使用我们的自定义impl,它使用的是HttpSessionSecurityContextRepository .

    在我们尝试了一些其他的事情之后,我们发现我们必须使用“http”命名空间的“security-context-repository-ref”属性给我们的自定义SecurityContextRepository impl . 如果您使用“http”命名空间并想要注入自己的SecurityContextRepository impl,请尝试“security-context-repository-ref”属性 .

    使用“http”命名空间时,将忽略单独的SecurityContextPersistenceFilter定义 . 正如我上面复制的那样,参考文档 . 没有说明 .

    如果我误解了这些事情,请纠正我 .

  • 9

    在Spring Securitiy 3.0中,它似乎更容易 . 如果您正在使用命名空间配置,则可以执行以下操作:

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

    或者您可以将SecurityContextRepository配置为null,并且不会以任何方式保存as well .

  • 2

    看看 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>
    
  • 8

    实际上,在Spring Security问题管理中没有't mean being completely stateless. There' s an issue .

  • 26

    快速说明一下:它是“创建会话”而不是“创建会话”

    create-session

    控制创建HTTP会话的渴望程度 .

    如果未设置,则默认为“ifRequired” . 其他选项是“永远”和“从不” .

    此属性的设置会影响HttpSessionContextIntegrationFilter的allowSessionCreation和forceEagerSessionCreation属性 . 除非将此属性设置为“never”,否则allowSessionCreation将始终为true . forceEagerSessionCreation为“false”,除非它设置为“always” .

    因此,默认配置允许创建会话,但不强制它 . 例外情况是,如果启用了并发会话控制,则forceEagerSessionCreation将设置为true,而不管此处的设置是什么 . 使用“never”会在HttpSessionContextIntegrationFilter初始化期间导致异常 .

    有关会话使用的具体细节,HttpSessionSecurityContextRepository javadoc中有一些很好的文档 .

  • 101

    在使用了这个答案中发布的众多解决方案之后,为了在使用 <http> 命名空间配置时尝试获得一些工作,我终于找到了一种实际适用于我的用例的方法 . 我没有't actually require that Spring Security doesn' t启动一个会话(因为我在应用程序的其他部分使用会话),只是它在会话中根本没有"remember"身份验证(应该重新检查每个请求) .

    首先,我不清楚你是否应该将securityContextRepository设置为 null 或者设置为no-op实现 . 前者不起作用,因为 NullPointerExceptionSecurityContextPersistenceFilter.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;
        }
    
    }
    

    这在我的应用程序中不起作用,因为有一些奇怪的 ClassCastExceptionresponse_ 类型有关 .

    即使假设我确实设法找到一个有效的实现(通过简单地不在会话中存储上下文),仍然存在如何将其注入由 <http> 配置构建的过滤器的问题 . 根据docs,您不能简单地在 SECURITY_CONTEXT_FILTER 位置更换过滤器 . 我发现挂钩在封面下创建的 SecurityContextPersistenceFilter 的唯一方法是编写一个丑陋的 ApplicationContextAware bean:

    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>
    

相关问题