1. Overview
1.概述
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.
在这个快速教程中,我们将展示一个例子,说明我们如何使用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.
我们将利用HttpSessionBindingListener来更新登录用户的列表,每当用户信息被添加到会话或从会话中删除时,基于用户登录到系统或从系统中退出。
2. Active User Store
2.活跃的用户商店
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:
我们将把它定义为Spring上下文中的一个标准Bean。
@Bean
public ActiveUserStore activeUserStore(){
return new ActiveUserStore();
}
3. The HTTPSessionBindingListener
3.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.
现在,我们要利用HTTPSessionBindingListener接口,并创建一个封装类来表示一个当前已登录的用户。
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:
这基本上会监听HttpSessionBindingEvent类型的事件,每当一个值被设置或移除,或者换句话说,被绑定或解除绑定,HTTP会话就会被触发。
@Component
public class LoggedUser implements HttpSessionBindingListener, Serializable {
private static final long serialVersionUID = 1L;
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.
监听器有两个需要实现的方法,valueBound()和valueUnbound(),用于触发它所监听的事件的两类行动。每当实现监听器的类型的值被设置或从会话中移除,或会话被无效,这两个方法将被调用。
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.
在我们的案例中,valueBound()方法将在用户登录时被调用,valueUnbound()方法将在用户退出或会话到期时被调用。
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
4.跟踪登录和注销
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.
现在我们需要跟踪用户何时成功登录或注销,这样我们就可以从会话中添加或删除一个活动用户。在Spring Security应用程序中,这可以通过实现AuthenticationSuccessHandler和LogoutSuccessHandler接口来实现。
4.1. Implementing AuthenticationSuccessHandler
4.1.实现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:
对于登录动作,我们将通过覆盖onAuthenticationSuccess()方法将登录用户的用户名设置为会话的属性,该方法为我们提供了对session和authentication对象的访问。
@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
4.2.实现LogoutSuccessHandler
For the logout action, we will remove the user attribute by override the onLogoutSuccess() method of the LogoutSuccessHandler interface:
对于注销动作,我们将通过覆盖LogoutSuccess()接口的onLogoutSuccessHandler方法来移除用户属性。
@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
5.控制器和视图
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:
为了看到上述所有的行动,我们将为URL “/users”创建一个控制器映射,该映射将检索用户列表,将其作为一个模型属性添加,并返回users.html视图。
5.1. Controller
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
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
6.使用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.
另一种检索当前登录用户的方法是利用Spring的SessionRegistry,它是一个管理用户和会话的类。这个类有getAllPrincipals()方法来获取用户的列表。
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:
对于每个用户,我们可以通过调用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());
}
In order to use the SessionRegistry class, we have to define the bean and apply it to the session management as shown below:
为了使用SessionRegistry类,我们必须定义Bean并将其应用于会话管理,如下所示。
http
.sessionManagement()
.maximumSessions(1).sessionRegistry(sessionRegistry())
...
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
7. Conclusion
7.结论
In this article, we have demonstrated how we can determine who the currently logged in users are in a Spring Security application.
在这篇文章中,我们已经演示了如何在Spring Security应用程序中确定谁是当前登录的用户。
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.
本教程的实现可以在GitHub项目中找到–这是一个基于Maven的项目,所以应该很容易导入并按原样运行。