3

我正在使用 Spring 3.2 Milestone 1 来实现具有长轮询的服务。但是由于某种原因,Spring Security (3.1.2) 在第一个延迟结果过期后立即清除 SPRING_SECURITY_CONTEXT(已达到 asynctimeout 并且 tomcat 以 http.200 响应)或某些响应被发送回客户端。使用 Spring Security 3.1.0,这只发生在某些情况下(HTTPS 和客户端位于某些硬件防火墙后面),但在 3.1.2 中它总是发生(在第一个 DeferedResult 完成之后)!

这是日志相关部分的调试输出

DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/updates/events'; against '/login*'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 at position 1 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG: org.springframework.security.web.context.HttpSessionSecurityContextRepository - Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl@fc783ee2: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@fc783ee2: Principal: org.springframework.security.core.userdetails.User@33ca09: Username: nvrs; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 46EC76439E921FE347EC48ECF71C1258; Granted Authorities: ADMIN'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 at position 2 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 at position 3 of 11 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 at position 4 of 11 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 at position 5 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 at position 6 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 at position 7 of 11 in additional filter chain; firing Filter: 'RememberMeAuthenticationFilter'
DEBUG: org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter - SecurityContextHolder not populated with remember-me token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@fc783ee2: Principal: org.springframework.security.core.userdetails.User@33ca09: Username: nvrs; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 46EC76439E921FE347EC48ECF71C1258; Granted Authorities: ADMIN'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 at position 8 of 11 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
DEBUG: org.springframework.security.web.authentication.AnonymousAuthenticationFilter - SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@fc783ee2: Principal: org.springframework.security.core.userdetails.User@33ca09: Username: nvrs; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 46EC76439E921FE347EC48ECF71C1258; Granted Authorities: ADMIN'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/updates/events'; against '/updates/**'
DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526; Attributes: [hasAnyRole('ADMIN','MANAGER','INTERNAL')]
DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@fc783ee2: Principal: org.springframework.security.core.userdetails.User@33ca09: Username: nvrs; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 46EC76439E921FE347EC48ECF71C1258; Granted Authorities: ADMIN
DEBUG: org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@52bf21bf, returned: 1
DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Authorization successful
DEBUG: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - RunAsManager did not change Authentication object
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481959526 reached end of additional filter chain; proceeding with original chain
DEBUG: org.springframework.security.web.access.ExceptionTranslationFilter - Chain processed normally
DEBUG: org.springframework.security.web.context.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
DEBUG: org.springframework.security.web.access.ExceptionTranslationFilter - Chain processed normally
DEBUG: org.springframework.security.web.context.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
DEBUG: org.springframework.security.web.access.ExceptionTranslationFilter - Chain processed normally
DEBUG: org.springframework.security.web.context.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/updates/events'; against '/login*'
DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/updates/events'; against '/resources/css/**'
DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/updates/events'; against '/resources/images/**'
DEBUG: org.springframework.security.web.util.AntPathRequestMatcher - Checking match of request : '/updates/events'; against '/resources/*'
DEBUG: org.springframework.security.web.FilterChainProxy - /updates/events?clientId=nvrs1346481959144&timestamp=0&_=1346481985081 at position 1 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG: org.springframework.security.web.context.HttpSessionSecurityContextRepository - HttpSession returned null object for SPRING_SECURITY_CONTEXT
DEBUG: org.springframework.security.web.context.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@61ed10f7. A new one will be created.

如果您仔细查看输出,您将看到第一个长轮询请求“/updates/events?” 被正确处理 - 授予访问权限,但之后弹簧安全上下文被清除,正如您从“HttpSession 返回 SPRING_SECURITY_CONTEXT 的空对象”行中看到的那样,这是由客户端在第一个请求过期后对该 URL 的另一个请求触发的或事件触发非空响应。我想在这里指出,我所有的自定义过滤器都已被禁用,并且在处理长轮询请求时,我将 DeferedResult 存储到带有 sessionId-clientid (每个页面实例浏览器选项卡唯一)作为访问它的键的 Map 和在收到 JMS 消息的情况下向客户端发送结果。

Spring framework 3.2 M1 和最新的 3.2 快照版本与 Spring Security 3.1.2 或其各自的最新快照在 Tomcat 7.0.28 / 7.0.29(默认和 APR 连接器)下存在问题。

4

2 回答 2

3

在调试器的帮助下,我得出了以下结论:

在设置 DeferedResult 之后,通过代理调用的flush()方法org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrappersaveContext()调用org.springframework.security.web.context.HttpSessionSecurityContextRepository

@Override
protected void saveContext(SecurityContext context) {
    final Authentication authentication = context.getAuthentication();
    HttpSession httpSession = request.getSession(false);

    // See SEC-776
    if (authentication == null || authenticationTrustResolver.isAnonymous(authentication)) {
        if (logger.isDebugEnabled()) {
            logger.debug("SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.");
        }

        if (httpSession != null && !contextObject.equals(contextBeforeExecution)) {
            // SEC-1587 A non-anonymous context may still be in the session
            // SEC-1735 remove if the contextBeforeExecution was not anonymous
            httpSession.removeAttribute(springSecurityContextKey);
        }
        return;
    }

由于身份验证对象为空(由于 spring 安全上下文已被清除), httpSession.removeAttribute(springSecurityContextKey) 行从会话中删除 SPRING_SECURITY_CONTEXT 并且用户发出的下一个请求导致会话没有安全上下文因此用户被重定向到登录。除非我在这里遗漏了一些明显的东西,否则这是异步请求的交易破坏者。我想知道 Spring Security 团队是否意识到了这个问题,以及他们是否计划在 3.2 发布之前修复它。与此同时,有人对适当的解决方法有任何建议吗?

编辑:目前作为临时解决方案,我通过在异步请求的情况下不编辑会话来处理该问题。具体来说,我修改了检查何时从以下位置刷新 securityContext:

if (httpSession != null && !contextObject.equals(contextBeforeExecution))

if (httpSession != null && !contextObject.equals(contextBeforeExecution) && this.request.getAttribute("javax.servlet.async.request_uri") == null)

谢谢

于 2012-09-03T09:29:53.227 回答
0

您是否在服务器端使用分叉线程?

我们有一个类似的安全上下文被清除的问题,因为我们forked threads与响应进行了交互。安全上下文是一个thread local变量,默认情况下不与分叉线程共享(请参阅doc)。

我们通过将 of 设置strategySecurityContextHolder来解决了这个问题MODE_INHERITABLETHREADLOCAL

弹簧安全配置xml:

<beans:bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetClass" value="org.springframework.security.core.context.SecurityContextHolder" />
    <property name="targetMethod" value="setStrategyName" />
    <property name="arguments" value="MODE_INHERITABLETHREADLOCAL" />
</beans:bean>

2016 年 10 月 31 日更新

在使用线程池(例如 Tomcat)的环境中使用INHERITABLETHREADLOCAL可能会导致 ThreadLocal 泄漏。还有其他方法(例如使用DelegatingSecurityContextRunnable)来确保分叉线程获得正确的 Spring 安全上下文。请参阅并发支持

于 2015-12-22T07:31:50.180 回答