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

我正在使用Spring Security构build一个Web应用程序,该应用程序将位于Amazon EC2上,并使用Amazon的Elastic Load Balancer。 不幸的是,ELB不支持粘性会话,所以我需要确保我的应用程序没有会话正常工作。

到目前为止,我已经设置了RememberMeServices通过一个cookie分配一个令牌,这工作正常,但我希望Cookie与浏览器会话过期(例如,当浏览器closures)。

我必须想象,我不是第一个不想使用Spring Security而没有会话的人…有什么build议吗?

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

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

我们在同一个问题上工作了4个小时(注入一个自定义的SecurityContextRepository到SecurityContextPersistenceFilter)。 最后,我们知道了。 首先,在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实现,这将阻止安全上下文被存储,即使在请求期间已经创build了一个会话。

我们需要将我们自定义的SecurityContextRepository注入到SecurityContextPersistenceFilter中。 所以我们简单地用我们自定义的impl改变了上面的bean定义,并把它放到安全上下文中。

当我们运行应用程序时,我们追踪了日志,发现SecurityContextPersistenceFilter没有使用我们自定义的impl,而是使用了HttpSessionSecurityContextRepository。

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

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

请纠正我,如果我误解了事情。

Spring Securitiy 3.0似乎更容易。 如果您正在使用命名空间configuration,则可以简单地执行如下操作:

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

或者你可以configurationSecurityContextRepository为空,也不会有任何保存的方式。

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

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

只是一个简单的说明:它是“创build会话”而不是“创build会话”

创build会话

控制创buildHTTP会话的渴望。

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

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

所以默认configuration允许创build会话,但不会强制它。 如果启用了并发会话控制,则​​forceEagerSessionCreation将设置为true,而不pipe此处设置如何。 使用“从不”会在HttpSessionContextIntegrationFilter的初始化过程中导致exception。

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

在用这个答案发布的众多解决scheme之后,为了在使用<http>命名空间configuration时尝试使用某些东西,我终于find了一个实际适用于我的用例的方法。 我实际上并不要求Spring Security没有启动会话(因为我在会话的其他部分使用会话),只是它根本不会“记住”会话中的身份validation(应该重新检查每个请求)。

首先,我无法弄清楚如何执行上述“空实现”技术。 不清楚你是否应该将securityContextRepository设置为null或者设置为no-op实现。 前者不起作用,因为在SecurityContextPersistenceFilter.doFilter()抛出了NullPointerExceptionexception。 至于无操作的实现,我试图用我能想到的最简单的方式来实现:

 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_types有关。

即使假设我设法find一个可行的实现(通过简单地不存储会话中的上下文),仍然存在如何将其注入由<http>configuration构build的filter的问题。 根据文档 ,您不能简单地在SECURITY_CONTEXT_FILTER位置replacefilter。 我发现钩入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()); } } } } } } } 

无论如何,实际上确实有效的解决scheme,尽pipe非常黑客。 只需使用一个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_); } } 

然后在configuration中:

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