没有会话时如何使用Spring Security?


99

我正在使用Spring Security构建一个Web应用程序,该应用程序将驻留在Amazon EC2上并使用Amazon的Elastic Load Balancer。不幸的是,ELB不支持粘性会话,因此我需要确保我的应用程序无需会话即可正常运行。

到目前为止,我已经设置了RememberMeServices来通过cookie分配令牌,并且可以正常工作,但是我希望cookie在浏览器会话中过期(例如,当浏览器关闭时)。

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

Answers:


124

在带有Java Config的 Spring Security 3中,可以使用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包含一个潜在的恶意字符串';'“。
workerjoe

@workerjoe那么,您要通过此Java配置说的是,会话不是由Spring Security而是由tomcat创建的?
Vishwas Atrey

@VishwasAtrey以我的理解(可能是错误的),Tomcat创建并维护了会话。Spring利用它们,添加了自己的数据。如上所述,我试图制作一个无状态的Web应用程序,但该应用程序无法正常工作。有关我自己的问题的更多信息,请参见此答案
workerjoe

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个小时。最后,我们弄清楚了。首先,在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接口的空实现,即使在请求期间已经创建了会话,也将阻止存储安全上下文。”

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

当我们运行应用程序时,我们跟踪日志并看到SecurityContextPersistenceFilter没有使用我们的自定义隐含功能,而是使用HttpSessionSecurityContextRepository。

在尝试了一些其他操作之后,我们发现必须为自定义的SecurityContextRepository隐式代码赋予“ http”名称空间的“ security-context-repository-ref”属性。如果您使用“ http”命名空间,并且想注入自己的SecurityContextRepository隐式命令,请尝试使用“ security-context-repository-ref”属性。

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

如果我误会了这些东西,请纠正我。


谢谢,这是宝贵的信息。我将在我的应用程序中尝试一下。
杰夫·埃文斯

谢谢,这就是我在Spring 3.0中所需要的
Justin Ludwig

1
当您说http名称空间不允许自定义SecurityContextPersistenceFilter时,您非常准确,我花了两个小时的调试才能弄清楚
Jaime Hablutzel

非常感谢您发布此信息!我正要撕掉我的小头发。我想知道为什么不赞成使用SecurityContextPersistenceFilter的setSecurityContextRepository方法(文档说使用构造函数注入,这也不对)。
fool4jesus

10

看一SecurityContextPersistenceFilter堂课。它定义了如何SecurityContextHolder填充。默认情况下,它用于HttpSessionSecurityContextRepository在http会话中存储安全上下文。

我使用custom很容易实现了这种机制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
嗨,卢卡斯,您能提供安全性上下文存储库实现的更多详细信息吗?
Jim Downing 2010年

1
TokenSecurityContextRepository类包含HashMap <String,SecurityContext> contextMap。在loadContext()方法中,检查是否存在由requestParameter sid,cookie或自定义requestHeader或上述任何方法的组合传递的会话哈希码的SecurityContext。如果无法解析上下文,则返回SecurityContextHolder.createEmptyContext()。方法saveContext将已解析的上下文放入contextMap。
卢卡斯·赫尔曼

8

实际上,create-session="never"这并不意味着完全无状态。Spring Security问题管理中存在一个问题


3

在努力解决此答案中发布的众多解决方案之后,尝试在使用<http>名称空间配置时使某些功能正常工作,我终于找到了一种实际适用于我的用例的方法。我实际上并不需要Spring Security不启动会话(因为我在应用程序的其他部分中使用了会话),只是根本不“记住”会话中的身份验证(应该重新检查)每个请求)。

首先,我无法弄清楚如何执行上述“空实现”技术。目前尚不清楚您应该将securityContextRepository设置null为no-op实施还是将其设置为no-op实现。前者不起作用,因为将a 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;
    }

}

这在我的应用程序中不起作用,因为ClassCastExceptionresponse_类型有关。

即使假设我确实设法找到了可行的实现(通过简单地不在会话中存储上下文),仍然存在如何将其注入<http>配置所构建的过滤器中的问题。您不能SECURITY_CONTEXT_FILTER按照docs的位置简单地更换过滤器。我发现挂SecurityContextPersistenceFilter在幕后创建的的唯一方法是编写一个丑陋的ApplicationContextAwarebean:

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

            }
        }
    }
}

无论如何,对于确实有效的解决方案,尽管非常糟糕。只需使用a即可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)
workerjoe

3

请注意:它是“创建会话”而不是“创建会话”

创建会话

控制创建HTTP会话的热情。

如果未设置,则默认为“ ifRequired”。其他选项是“始终”和“从不”。

此属性的设置会影响HttpSessionContextIntegrationFilter的allowSessionCreation和forceEagerSessionCreation属性。除非将此属性设置为“从不”,否则allowSessionCreation始终为true。除非将其设置为“ always”,否则forceEagerSessionCreation为“ false”。

因此,默认配置允许会话创建,但不强制执行。例外是如果启用了并发会话控制,那么无论forceEagerSessionCreation设置为true时,无论此处的设置如何。然后,在初始化HttpSessionContextIntegrationFilter时使用“从不”将导致异常。

有关会话用法的特定详细信息,HttpSessionSecurityContextRepository javadoc中有一些不错的文档。


这些都是很好的答案,但是我一直想尽办法找出使用<http> config元素时如何实现这一目标。即使使用auto-config=false,您显然也无法用自己的SECURITY_CONTEXT_FILTER位置替换当前位置。我一直在尝试使用某些ApplicationContextAwarebean 禁用它(使用反射将强制为securityContextRepository中的null实现SessionManagementFilter),但没有骰子。遗憾的是,我无法切换到提供的春季安全3 create-session=stateless. 1年。
杰夫·埃文斯

请访问此站点,始终提供信息。希望这有助于你和其他还有“ baeldung.com/spring-security-session ”•始终-如果不存在•ifRequired会话将始终创建-如果需要的话(默认),会话将只创建•从不–框架将永远不会创建会话,但会使用一个会话(如果已存在)•无状态– Spring Security不会创建或使用任何会话
Java_Fire_Within
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.