«1. Обзор

В этом кратком руководстве мы собираемся показать пример того, как мы можем отслеживать пользователей, вошедших в систему в настоящее время, в приложении с помощью Spring Security.

Для этой цели мы собираемся отслеживать список вошедших в систему пользователей, добавляя пользователя, когда они входят в систему, и удаляя их, когда они выходят из системы.

Мы будем использовать HttpSessionBindingListener для обновления списка вошедших в систему пользователей всякий раз, когда информация о пользователе добавляется в сеанс или удаляется из сеанса на основе входа пользователя в систему или выхода из системы.

2. Хранилище активных пользователей

Для простоты мы определим класс, который действует как хранилище в памяти для зарегистрированных пользователей:

public class ActiveUserStore {

    public List<String> users;

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

    // standard getter and setter
}

Мы определим его как стандартный компонент в Контекст Spring:

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

3. HTTPSessionBindingListener

Теперь мы воспользуемся интерфейсом HTTPSessionBindingListener и создадим класс-оболочку для представления пользователя, который в данный момент вошел в систему.

Это в основном прослушивать события типа HttpSessionBindingEvent, которые запускаются всякий раз, когда значение устанавливается или удаляется, или, другими словами, привязывается или не привязывается к сеансу HTTP:

@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
}

Слушатель имеет два метода, которые необходимо реализовать: valueBound() и valueUnbound() для двух типов действий, запускающих ожидаемое событие. Всякий раз, когда значение типа, реализующего прослушиватель, устанавливается или удаляется из сеанса, или сеанс становится недействительным, эти два метода будут вызываться.

В нашем случае метод valueBound() будет вызываться при входе пользователя в систему, а метод valueUnbound() будет вызываться при выходе пользователя из системы или по истечении срока сеанса.

В каждом из методов мы извлекаем значение, связанное с событием, затем добавляем или удаляем имя пользователя из нашего списка вошедших в систему пользователей, в зависимости от того, было ли значение привязано или не привязано к сеансу.

4. Отслеживание входа и выхода

Теперь нам нужно отслеживать, когда пользователь успешно вошел в систему или вышел из нее, чтобы мы могли добавить или удалить активного пользователя из сеанса. В приложении Spring Security этого можно добиться путем реализации интерфейсов AuthenticationSuccessHandler и LogoutSuccessHandler.

4.1. Реализация AuthenticationSuccessHandler

Для действия входа в систему мы установим имя пользователя, вошедшего в систему, в качестве атрибута сеанса, переопределив метод onAuthenticationSuccess(), который предоставляет нам доступ к сеансу и объектам аутентификации:

@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. Реализация LogoutSuccessHandler

Для действия выхода мы удалим атрибут пользователя, переопределив метод onLogoutSuccess() интерфейса LogoutSuccessHandler:

@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. Контроллер и представление

Чтобы увидеть все вышеперечисленное в действие, мы создадим сопоставление контроллера для URL-адреса «/users», которое будет получать список пользователей, добавлять его в качестве атрибута модели и возвращать представление users.html:

5.1. Контроллер

@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. Альтернативный метод с использованием Sessionregistry

Другой метод получения зарегистрированных в данный момент пользователей заключается в использовании Spring SessionRegistry, класса, который управляет пользователями и сеансами. Этот класс имеет метод getAllPrincipals() для получения списка пользователей.

Для каждого пользователя мы можем увидеть список всех его сеансов, вызвав метод getAllSessions(). Чтобы получить только тех пользователей, которые в данный момент вошли в систему, мы должны исключить сеансы с истекшим сроком действия, установив для второго параметра getAllSessions() значение 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());
}

Чтобы использовать класс SessionRegistry, мы должны определить bean-компонент и примените его к управлению сеансом, как показано ниже:

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

...

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

7. Заключение

В этой статье мы продемонстрировали, как мы можем определить, кто в настоящее время входит в систему в приложении Spring Security.

Реализацию этого руководства можно найти в проекте GitHub — это проект на основе Maven, поэтому его легко импортировать и запускать как есть.