Announcement Announcement Module
Collapse
No announcement yet.
Spring 3.2 Long polling causing spring security context to be cleared Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • 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: '[email protected]83ee2: 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, 09:50 AM.

  • #2
    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

    Comment


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

      Comment


      • #4
        Opened a bug in jira:
        https://jira.springsource.org/browse/SPR-10027

        Comment


        • #5
          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.

          Comment


          • #6
            Thanks!
            I should have just waited for your reply

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

            Comment


            • #7
              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).

              Comment


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

                Comment


                • #9
                  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).

                  Comment


                  • #10
                    Sounds good.

                    I'll give it a try.

                    Thanks for all your help

                    Comment

                    Working...
                    X