Announcement Announcement Module
Collapse
No announcement yet.
RememberMeProcessingFilter & Concurrent Sessions Page Title Module
Move Remove Collapse
This topic is closed
X
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • RememberMeProcessingFilter & Concurrent Sessions

    Hi all,

    I have a problem with the default concurrent session behaviour. In the scenario where 'batman' attempts to login twice from two different pcs, I want the most recent login attempt to be denied. I have found this is possible by setting <property name="exceptionIfMaximumExceeded" value="true"/> for the ConcurrentSessionController (and using the fixed ProviderManager from the 1.0 cvs).

    I have a problem in the following situation -
    • user 'a' logs in as 'batman', sets a cookie to remember his session.
    • user 'a' turns off his pc, and his session times out. He still has a cookie on his machine though (set for 2 weeks).
    • ... 4 days pass by!
    • user 'b' logs in as 'batman'.
    • user 'a' turns on his pc, and visits the website. He already has a cookie on his machine so he doesnt need to login again.

    I want user 'a' in this situation to be denied access, even though he has a cookie - as the maximum sessions (1) for his username has been reached. The default TokenBasedRememberMeServices - does not check for concurrent sessions.

    I am guessing I have to check the session count from my own RememberMeService, but I am not sure if this is right way to do this - as the RememberMeAuthenticationToken doesnt contain the right properties for ConcurrentSessionController.checkAuthenticationAll owed to fire - it fails on
    Code:
    String sessionId = SessionRegistryUtils
                .obtainSessionIdFromAuthentication(request);
    Overriding autoLogin in TokenBasedRememberMeServices -
    Code:
    private ConcurrentSessionController sessionController;
    
    ...
    
    public Authentication autoLogin(HttpServletRequest request,
    	HttpServletResponse response) {
    	Cookie[] cookies = request.getCookies();
    
    	if ((cookies == null) || (cookies.length == 0)) {
    		return null;
    	}
    
    	Authentication auth = super.autoLogin(request,response);
    
    	try {
    		sessionController.checkAuthenticationAllowed(auth);
    	} catch (AuthenticationException ae) {
    
                    cancelCookie(request, response,
                       "concurrent sessions error");
    
    		return null;
    	} 
    }
    Any ideas anyone ?
    Last edited by ambeth; Dec 2nd, 2005, 12:16 PM.

  • #2
    Please log an issue in JIRA and someone will take a look at this in more detail. At first glance it would seem appropriate to use AuthenticationTrustResolver to identify remembered Authentication objects and cancel their corresponding sessions before cancelling normal Authentication sessions.

    Comment


    • #3
      my filter workaround

      I have written a custom RememberMeProcessingFilter to get cookie + concurrent session support with my application. Its a bit of a hack, so ill try to explain why I have done it this way, and hopefully someone can tell me if its ok or suggest a better way to do it (this is using acegi 1.00 RC1).

      The problem I was having was this : the default RememberMeServices implementation - TokenBasedRememberMeServices does not check for concurrent user sessions, nor does it update the session registry when valid cookies are found. The AuthenticationManager 'ProviderManager' does these things, so I made it available to my own RememberMeProcessingFilter, and passed in the Authentication object (RememberMeAuthenticationToken) resulting from the TokenBasedRememberMeServices autoLogin method.

      The ProviderManager sessionController - ConcurrentSessionControllerImpl - chokes on RememberMeAuthenticationTokens -- assertions fail in the SessionRegistryUtils. So I created a new UsernamePasswordAuthenticationToken and passed it to the ProviderManager instead.

      Lastly I nabbed the unsuccessfulAuthentication method from AbstractProcessingFilter, so I could redirect the user back to the login page with an error if the maximum sessions were exceeded.

      I'd be interested to hear from Ben or anyone if I am on the right track or completely off the rails

      Code:
      package com.informa.mt.lmiu.security;
      
      import org.springframework.beans.factory.InitializingBean;
      import org.springframework.context.ApplicationEventPublisherAware;
      import org.springframework.context.ApplicationEventPublisher;
      import org.springframework.util.Assert;
      import org.apache.commons.logging.Log;
      import org.apache.commons.logging.LogFactory;
      import org.acegisecurity.ui.rememberme.RememberMeServices;
      import org.acegisecurity.ui.rememberme.NullRememberMeServices;
      import org.acegisecurity.ui.WebAuthenticationDetails;
      import org.acegisecurity.ui.webapp.AuthenticationProcessingFilter;
      import org.acegisecurity.context.SecurityContextHolder;
      import org.acegisecurity.Authentication;
      import org.acegisecurity.AuthenticationManager;
      import org.acegisecurity.AuthenticationException;
      import org.acegisecurity.userdetails.User;
      import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
      import org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent;
      
      import javax.servlet.*;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      
      /**
       * <p>
       * </p>
       */
      public class MyRememberMeProcessingFilter  implements Filter, InitializingBean,
              ApplicationEventPublisherAware {
          //~ Static fields/initializers =============================================
      
          private static final Log logger = LogFactory.getLog(MyRememberMeProcessingFilter.class);
      
          //~ Instance fields ========================================================
      
          private ApplicationEventPublisher eventPublisher;
          private RememberMeServices rememberMeServices = new NullRememberMeServices();
          private AuthenticationManager authenticationManager;
          private String authenticationFailureUrl;
          //~ Methods ================================================================
      
          public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
              this.eventPublisher = eventPublisher;
          }
      
          public void setRememberMeServices(RememberMeServices rememberMeServices) {
              this.rememberMeServices = rememberMeServices;
          }
      
          public RememberMeServices getRememberMeServices() {
              return rememberMeServices;
          }
      
          public void setAuthenticationManager(AuthenticationManager authenticationManager) {
              this.authenticationManager = authenticationManager;
          }
      
          public void setAuthenticationFailureUrl(String authenticationFailureUrl) {
              this.authenticationFailureUrl = authenticationFailureUrl;
          }
      
          public void afterPropertiesSet() throws Exception {
              Assert.notNull(rememberMeServices);
          }
      
          /**
           * Does nothing - we rely on IoC lifecycle services instead.
           */
          public void destroy() {}
      
          public void doFilter(ServletRequest request, ServletResponse response,
                               FilterChain chain) throws IOException, ServletException {
              if (!(request instanceof HttpServletRequest)) {
                  throw new ServletException("Can only process HttpServletRequest");
              }
      
              if (!(response instanceof HttpServletResponse)) {
                  throw new ServletException("Can only process HttpServletResponse");
              }
      
              HttpServletRequest httpRequest = (HttpServletRequest) request;
              HttpServletResponse httpResponse = (HttpServletResponse) response;
      
              if (SecurityContextHolder.getContext().getAuthentication() == null) {
                  Authentication rememberMeAuth =
                          rememberMeServices.autoLogin(httpRequest, httpResponse);
      
                  if(rememberMeAuth != null) {
                      SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
      
                      /* check concurrent sessions */
                      /* arrgh */
                      User user = (User) rememberMeAuth.getPrincipal();
                      UsernamePasswordAuthenticationToken userPassAuth = new UsernamePasswordAuthenticationToken(
                              user,user.getPassword() , rememberMeAuth.getAuthorities()
                      );
                      userPassAuth.setDetails(new WebAuthenticationDetails(httpRequest));
      
                     // Place the last username attempted into HttpSession for views
                     httpRequest.getSession().setAttribute(
                             AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY,user.getUsername());
      
                      try {
                          authenticationManager.authenticate(userPassAuth);
                      } catch (AuthenticationException failed) {
                          // Authentication failed
                          unsuccessfulAuthentication(httpRequest, httpResponse, failed);
                          return;
                      }
      
                      if (logger.isDebugEnabled()) {
                          logger.debug(
                              "SecurityContextHolder populated with remember-me token: '"
                              + SecurityContextHolder.getContext().getAuthentication()
                              + "'");
                      }
      
                      // Fire event
                      if (this.eventPublisher != null) {
                          eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                                  SecurityContextHolder.getContext().getAuthentication(),
                                  this.getClass()));
                      }
                  }
              } else {
                  if (logger.isDebugEnabled()) {
                      logger.debug(
                          "SecurityContextHolder not populated with remember-me token, as it already contained: '"
                          + SecurityContextHolder.getContext().getAuthentication()
                          + "'");
                  }
              }
      
              chain.doFilter(request, response);
          }
      
          /**
           * Stripped from AuthenticationProcessingFilter - redirects user to the failed URL
           * @param request
           * @param response
           * @param failed
           * @throws IOException
           */
          private void unsuccessfulAuthentication(HttpServletRequest request,
              HttpServletResponse response, AuthenticationException failed)
              throws IOException {
              SecurityContextHolder.getContext().setAuthentication(null);
              if (logger.isDebugEnabled()) {
                  logger.debug(
                      "Updated SecurityContextHolder to contain null Authentication");
              }
      
              if (logger.isDebugEnabled()) {
                  logger.debug("Authentication request failed: " + failed.toString());
              }
      
              try {
                  request.getSession().setAttribute(
                          AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY, failed);
              } catch (Exception ignored) {}
      
              rememberMeServices.loginFail(request, response);
              response.sendRedirect(response.encodeRedirectURL(request.getContextPath()
                      + authenticationFailureUrl));
          }
      
          /**
           * Does nothing - we rely on IoC lifecycle services instead.
           *
           * @param ignored not used
           *
           */
          public void init(FilterConfig ignored) throws ServletException {}
      }
      Last edited by ambeth; Jan 23rd, 2006, 10:39 AM.

      Comment


      • #4
        Would you please log your changes in JIRA so I (or another developer) can take a closer look at them when we have a bit more time?

        Comment

        Working...
        X