Security Top

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security 5:

>> CHECK OUT THE COURSE

1. Overview

In this quick tutorial, we're going to show an example of how we can track the currently logged in users in an application using Spring Security.

For this purpose, we’re going to keep track of a list of logged in users by adding the user when they log in and removing them when they log out.

We'll leverage the HttpSessionBindingListener to update the list of logged in users whenever user information is added to the session or removed from the session based on user logs into the system or logs out from the system.

2. Active User Store

For simplicity, we will define a class that acts as an in-memory store for the logged in users:

public class ActiveUserStore {

    public List<String> users;

    public ActiveUserStore() {
        users = new ArrayList<String>();
    }

    // standard getter and setter
}

We'll define this as a standard bean in the Spring context:

@Bean
public ActiveUserStore activeUserStore(){
    return new ActiveUserStore();
}

3. The HTTPSessionBindingListener

Now, we're going to make use of the HTTPSessionBindingListener interface and create a wrapper class to represent a user that is currently logged in.

This will basically listen to events of type HttpSessionBindingEvent, which are triggered whenever a value is set or removed, or, in other words, bound or unbound, to the HTTP session:

@Component
public class LoggedUser implements HttpSessionBindingListener {

    private String username; 
    private ActiveUserStore activeUserStore;
    
    public LoggedUser(String username, ActiveUserStore activeUserStore) {
        this.username = username;
        this.activeUserStore = activeUserStore;
    }
    
    public LoggedUser() {}

    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        List<String> users = activeUserStore.getUsers();
        LoggedUser user = (LoggedUser) event.getValue();
        if (!users.contains(user.getUsername())) {
            users.add(user.getUsername());
        }
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        List<String> users = activeUserStore.getUsers();
        LoggedUser user = (LoggedUser) event.getValue();
        if (users.contains(user.getUsername())) {
            users.remove(user.getUsername());
        }
    }

    // standard getter and setter
}

The listener has two methods that need to be implemented, valueBound() and valueUnbound() for the two types of actions that trigger the event it is listening for. Whenever a value of the type that implements the listener is set or removed from the session, or the session is invalidated, these two methods will be invoked.

In our case, the valueBound() method will be called when the user logs in and the valueUnbound() method will be called when the user logs out or when the session expires.

In each of the methods we retrieve the value associated with the event, then add or remove the username from our list of logged in users, depending on whether the value was bound or unbound from the session.

4. Tracking Login and Logout

Now we need to keep track of when the user is successfully logged in or logged out so that we can add or remove an active user from the session. In a Spring Security application, this can be achieved by implementing the AuthenticationSuccessHandler and LogoutSuccessHandler interfaces.

4.1. Implementing AuthenticationSuccessHandler

For the login action, we will set the username of the user logging in as an attribute on the session by overriding the onAuthenticationSuccess() method which provides us access to the session and authentication objects:

@Component("myAuthenticationSuccessHandler")
public class MySimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    ActiveUserStore activeUserStore;
    
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, 
      HttpServletResponse response, Authentication authentication) 
      throws IOException {
        HttpSession session = request.getSession(false);
        if (session != null) {
            LoggedUser user = new LoggedUser(authentication.getName(), activeUserStore);
            session.setAttribute("user", user);
        }
    }
}

4.2. Implementing LogoutSuccessHandler

For the logout action, we will remove the user attribute by override the onLogoutSuccess() method of the LogoutSuccessHandler interface:

@Component("myLogoutSuccessHandler")
public class MyLogoutSuccessHandler implements LogoutSuccessHandler{
    @Override
    public void onLogoutSuccess(HttpServletRequest request, 
      HttpServletResponse response, Authentication authentication)
      throws IOException, ServletException {
        HttpSession session = request.getSession();
        if (session != null){
            session.removeAttribute("user");
        }
    }
}

5. Controller and View

In order to see all the above in action, we will create a controller mapping for the URL “/users” that will retrieve the list of users, add it as a model attribute and return the users.html view:

5.1. Controller

@Controller
public class UserController {
    
    @Autowired
    ActiveUserStore activeUserStore;

    @GetMapping("/loggedUsers")
    public String getLoggedUsers(Locale locale, Model model) {
        model.addAttribute("users", activeUserStore.getUsers());
        return "users";
    }
}

5.2. Users.html

<html>
<body>
    <h2>Currently logged in users</h2>
    <div th:each="user : ${users}">
        <p th:text="${user}">user</p>
    </div>
</body>
</html>

6. Alternative Method Using Sessionregistry

Another method of retrieving the currently logged in users is by leveraging Spring's SessionRegistry, which is a class that manages users and sessions. This class has the method getAllPrincipals() to obtain the list of users.

For each user, we can see a list of all their sessions by calling the method getAllSessions(). In order to obtain only the currently logged in users, we have to exclude the expired sessions, by setting the second parameter of getAllSessions() to false:

@Autowired
private SessionRegistry sessionRegistry;

@Override
public List<String> getUsersFromSessionRegistry() {
    return sessionRegistry.getAllPrincipals().stream()
      .filter(u -> !sessionRegistry.getAllSessions(u, false).isEmpty())
      .map(Object::toString)
      .collect(Collectors.toList());
}

In order to use the SessionRegistry class, we have to define the bean and apply it to the session management as shown below:

http
  .sessionManagement()
  .maximumSessions(1).sessionRegistry(sessionRegistry())

...

@Bean
public SessionRegistry sessionRegistry() {
    return new SessionRegistryImpl();
}

7. Conclusion

In this article, we have demonstrated how we can determine who the currently logged in users are in a Spring Security application.

The implementation of this tutorial can be found in the GitHub project – this is a Maven based project, so it should be easy to import and run as it is.

Security bottom

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security 5:

>> CHECK OUT THE COURSE
Generic footer banner
12 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!