3

I'm having a hard time implementing a feature that our customer requests. In short they want to be able to logout any customer of their choosing out of the application via the admin side. The application is using Flex as a front end technology and accessing server via AMF. Server side is using Spring Security and Spring BlazeDS Integration.

Basically the question is: does Spring Security and/or Spring BlazeDS Integration offer any centralized system for session management (and killing) out-of-the-box?

For proof-of-concept purposes I have tried to logout all users and kill all sessions with following code:

package xxx.xxx.xxx;

import java.util.List;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.User;

import flex.messaging.MessageBroker;
import flex.messaging.security.LoginCommand;

public class SessionServiceImpl {
    private static final Log log = LogFactory.getLog(SessionServiceImpl.class);

    private SessionRegistry sessionRegistry;
    private MessageBroker messageBroker;

    public SessionRegistry getSessionRegistry() {
        return sessionRegistry;
    }

    @Autowired
    public void setSessionRegistry(SessionRegistry sessionRegistry) {
        log.debug("sessionregistry set");
        this.sessionRegistry = sessionRegistry;
    }    

    public MessageBroker getMessageBroker() {
        return messageBroker;
    }

    @Autowired
    public void setMessageBroker(MessageBroker messageBroker) {
        log.debug("messagebroker set");
        this.messageBroker = messageBroker;
    }

    public void logoutUser(String userName) {
        log.debug("Logging out user by username: "+userName);
        List<Object> principals = null;
        if(sessionRegistry != null){
            principals = sessionRegistry.getAllPrincipals();
        }else{
            log.debug("sessionRegistry null");
        }

        if(principals != null){
            for (Object object : principals) {
                User user = (User)object;

                // get single users all sessions
                List<SessionInformation> sessions = sessionRegistry.getAllSessions(user, false);
                log.debug("Sessions list size: "+sessions.size());


                if(messageBroker != null){
                    LoginCommand command = messageBroker.getLoginManager().getLoginCommand();
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, user.getPassword());
                    command.logout(usernamePasswordAuthenticationToken);

                    for (SessionInformation sessionInformation : sessions) {
                        log.debug(ReflectionToStringBuilder.toString(sessionInformation));
                        sessionInformation.expireNow();
                        sessionRegistry.removeSessionInformation(sessionInformation.getSessionId());
                    }

                }else{
                    log.debug("messageBroker null");
                }

                if(object != null){
                    log.debug(ReflectionToStringBuilder.toString(object));
                }else{
                    log.debug("object null");
                }

            }
        }else{
            log.debug("principals null");
        }
    }
}

Unfortunately the above code does not work. As far as I can tell this is because two things:

A) LoginCommand is not "application wide" but tied to the current session, therefore it will try to logout only current session (the session the admin is using) and is oblivious of other sessions

B) sessionInformation.expireNow() tries to expire the session but if user manages to make a request before session gets invalidated, the session is not destroyed

From the documentation I can see that session could be directly invalidated by session.invalidate(), but it seems I have no way to access all session objects.

What is the fastest or smartest way to implement this kind of feature?

Best regards, Jukka

Jukka Hämäläinen
  • 647
  • 1
  • 6
  • 17

2 Answers2

0

My approach would be to have an indirect session invalidation.

Use the ConcurrentSessionControl security option to limit 1 session per user. Write a custom SessionAuthenticationStrategy which checks if a user has been marked and invalidates the session if needed. Note that the session strategy should be executed before for example the usernamepassword filter creates a new session.

You can user either a database or something like a static class to hold the usernames. Furthermore you probably you also would want to have some kind of timestamp, which only invalidats sessions x minutes after they have been marked.

A whole another approach is to implement a servlet session listener, which records all login sessions in a user->session map and can invalidate them if necessary

You can take a look at the reference manual on how to wire up the beans http://docs.spring.io/spring-security/site/docs/3.0.x/reference/session-mgmt.html

Nils
  • 1,750
  • 14
  • 10
  • Unless I misunderstand, this doesn't work because `SessionAuthenticationStrategy` is only used at login, not on subsequent requests. – Rob Pridham Nov 29 '13 at 11:23
0

I've spent a long time trying to achieve the same thing.

In the end, I think I've resolved it. First remove the LoginCommand section of your code, because as you surmise, it relates to the user initiating the delete - in your case, the admin - and not the session of the target user.

Then, try removing this:

sessionRegistry.removeSessionInformation(sessionInformation.getSessionId());

For some reason this appears to cancel out the expiry without actually stopping further requests being granted. Unimpressive!

If it doesn't work, then in the midsts of meddling with this, I had another idea which I didn't bring to fruition. This is to add a filter for every request, and check for session expiry in there.

So, in your security XML:

<beans:bean id="sessionFilter" class="my.package.CustomSessionFilter"/>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"/>

<http use-expressions="true" auto-config="true">
    <custom-filter after="CONCURRENT_SESSION_FILTER" ref="sessionFilter"/>
    <session-management>
        <concurrency-control session-registry-ref="sessionRegistry"/>
    </session-management>
...

and then the class:

@Component
public class CustomSessionFilter extends OncePerRequestFilter
{
/**The session registry.*/
@Autowired
private SessionRegistry sessionRegistry;

@Override
protected void doFilterInternal( HttpServletRequest request,
                                 HttpServletResponse response,
                                 FilterChain filterChain )
    throws ServletException,
        IOException
{

    //get session for the user, and if expired, do something (e.g. redirect)

    //else...
    filterChain.doFilter(request, response);
}
}

I found this to be successfully called on a per-request basis. Hopefully you don't need the above, as it's an inelegant way to achieve something the framework ought to be doing for you.

Rob Pridham
  • 4,780
  • 1
  • 26
  • 38