Results 1 to 10 of 10

Thread: Spring 3.2 Long polling causing spring security context to be cleared

  1. #1
    Join Date
    Jul 2011
    Posts
    12

    Default Spring 3.2 Long polling causing spring security context to be cleared

    Hi all,

    I am dealing with the wierdest of issues and having trouble to pin point the cause. I am using Spring 3.2 Milestone 1 to implement a service with long polling (long polling blog example). However for some reason Spring Security 3.1.2 that i use clears the SPRING_SECURITY_CONTEXT immediately after the first deffered result either expires (asynctimeout has been reached and tomcat responds with http.200) or some response is send back to the client. Using Spring Security 3.1.0 this only happens under certain circumstances (HTTPS) but with 3.1.2 it happens always (after the first DefferedResult is fulfilled)!

    Here is the debug output of the relevant part of the log

    Code:
    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.
    If you look carefully at the output you will see the first long poll request "/update" is processed correctly - granted access but after that the spring security context gets cleared as you may see from the line "HttpSession returned null object for SPRING_SECURITY_CONTEXT".
    I would like to point out here that i have no custom filters and when i process the long poll request i just store the DefferedResult to a Map with the sessionId as a key for accessing it and sending a result to the client in case a JMS message is received.

    Edit: The problem is present for Spring framework 3.2 M1 and the latest 3.2 snapshot build in combination with Spring Security 3.1.2 and its latest snapshot
    Last edited by nvrs; Sep 2nd, 2012 at 09:50 AM.

  2. #2
    Join Date
    Jul 2011
    Posts
    12

    Default

    Ok, after some debugging i have concluded the following:

    After the DefferedResult is set the method flush() of org.springframework.security.web.context.SaveConte xtOnUpdateOrErrorResponseWrapper gets called which via a proxy calls saveContext() of org.springframework.security.web.context.HttpSessi onSecurityContextRepository.

    Code:
    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;
    }
    Since the authentication object is null (due to the fact that the spring security context has been cleared) the line
    httpSession.removeAttribute(springSecurityContextK ey) removes the SPRING_SECURITY_CONTEXT from the session and the next request that the user makes results in a session with no security context and thus user is redirected to login.
    Unless i am missing something obvious here, this is a deal breaker for async requests, is the Spring Security team aware of the issue and do they plan to fix it before 3.2 gets released (presumably with the release of Spring Security 3.2?). In the meantime what would be the proper way to resolve this?

    Thanks

  3. #3
    Join Date
    Nov 2012
    Posts
    9

    Default

    Happens to me as well.
    Maybe a bug needs to be opened in the Spring JIRA?

  4. #4
    Join Date
    Nov 2012
    Posts
    9

  5. #5
    Join Date
    Jul 2011
    Posts
    12

    Default

    Quote Originally Posted by lirany View Post
    Happens to me as well.
    Maybe a bug needs to be opened in the Spring JIRA?

    It's not a bug, Spring Security does not support long polling.

    Check my question/answer on SO:
    http://stackoverflow.com/questions/1...-to-be-cleared

    The relevant issue on spring jira is:
    https://jira.springsource.org/browse/SEC-1998

    Support for async has been scheduled for 3.2.M1, which is due on early December. As a temporary fix you may override the method that causes the session to be cleared when an async response is sent as i note above.

  6. #6
    Join Date
    Nov 2012
    Posts
    9

    Default

    Thanks!
    I should have just waited for your reply

    Can you explain your workaround? I don't quite understand how/what to implement.

  7. #7
    Join Date
    Jul 2011
    Posts
    12

    Default

    Quote Originally Posted by lirany View Post
    Thanks!
    I should have just waited for your reply

    Can you explain your workaround? I don't quite understand how/what to implement.
    Sure, as i stated above i have modified saveContext() of org.springframework.security.web.context.HttpSessi onSecurityContextRepository so as not to flush the session if the authentication context is null but the call to saveContext() has been trigerred by responding to an async request.

    To do that that i changed saveContext() as follows:

    Code:
     @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) && this.request.getAttribute("javax.servlet.async.request_uri") == null) {
                        httpSession.removeAttribute(springSecurityContextKey);
                    }
                    return;
                }
    
                if (httpSession == null) {
                    httpSession = createNewSessionIfAllowed(context);
                }
    
                // If HttpSession exists, store current SecurityContext but only if it has
                // actually changed in this thread (see SEC-37, SEC-1307, SEC-1528)
                if (httpSession != null) {
                    // We may have a new session, so check also whether the context attribute is set SEC-1561
                    if (contextChanged(context) || httpSession.getAttribute(springSecurityContextKey) == null) {
                        httpSession.setAttribute(springSecurityContextKey, context);
    
                        if (logger.isDebugEnabled()) {
                            logger.debug("SecurityContext stored to HttpSession: '" + context + "'");
                        }
                    }
                }
            }
    The difference is in the line:
    Code:
    if (httpSession != null && !contextObject.equals(contextBeforeExecution) && this.request.getAttribute("javax.servlet.async.request_uri") == null)
    By adding the check of "&& this.request.getAttribute("javax.servlet.async.req uest_uri") == null" i just ensure that in order for the securitycontext to be flushed, not only the httpSession needs to be null but also the request must not be an asnyc one. This is a bit of a messy fix, please understand what you are doing (how SpringSecurity works before applying this).

  8. #8
    Join Date
    Nov 2012
    Posts
    9

    Default

    Thanks for the quick reply!

    Maybe I'm missing something very basic, but how did you change the saveContext function?
    Did you implement your own securityContextRepository and set securityContextPersistenceFilter to use it?

    Like this:
    <bean id="securityContextPersistenceFilter"
    class="org.springframework.security.web.context.Se curityContextPersistenceFilter">
    <property name='securityContextRepository'>
    <bean class='MyContextRepository'>
    </bean>
    </property>
    </bean>

  9. #9
    Join Date
    Jul 2011
    Posts
    12

    Default

    You could do it like that, instead i just built the modified spring security and use that instead. I did that for two reasons, a) i placed the fix while debugging the actual src code, b) cause i wanted to use that version of spring security to a few different services and i am too bored to implement a securityContextRepository for each one (and avoid forgetting it).

  10. #10
    Join Date
    Nov 2012
    Posts
    9

    Default

    Sounds good.

    I'll give it a try.

    Thanks for all your help

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •