概述
在这篇快速教程中,我们将展示如何使用Spring Security来跟踪应用程序中当前已登录的用户。
为此,我们将通过在用户登录时添加用户信息,并在用户退出时移除他们,来维护一个已登录用户的列表。
我们将利用HttpSessionBindingListener
接口,每当用户信息被添加到会话或从会话中移除(基于用户登录或退出系统)时,更新已登录用户列表。
活跃用户存储
为了简化,我们将定义一个类,作为内存中的已登录用户存储:
public class ActiveUserStore {
public List<String> users;
public ActiveUserStore() {
users = new ArrayList<String>();
}
// standard getter and setter
}
我们将在Spring上下文中将其定义为标准bean:
@Bean
public ActiveUserStore activeUserStore(){
return new ActiveUserStore();
}
HttpSessionBindingListener
现在,我们将利用HttpSessionBindingListener
接口,并创建一个代表当前登录用户的包装类。
它基本上会监听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
}
监听器需要实现两个方法:valueBound()
和valueUnbound()
,以处理引发事件的两种操作。当会话中设置或移除实现了监听器的值,或者会话过期时,这两个方法会被调用。
在我们的例子中,当用户登录时,valueBound()
方法会被调用;当用户退出或会话过期时,valueUnbound()
方法会被调用。在每个方法中,我们会根据值是被绑定到会话还是从会话中移除,从我们的已登录用户列表中添加或删除用户名。
跟踪登录和注销
现在我们需要跟踪用户成功登录或注销,以便在会话中添加或移除活跃用户。在Spring Security应用中,这可以通过实现AuthenticationSuccessHandler
和LogoutSuccessHandler
接口来完成。
实现AuthenticationSuccessHandler
对于登录操作,我们将通过覆盖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);
}
}
}
实现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");
}
}
}
控制器和视图
为了查看上述所有功能的实际操作,我们将为URL"/users"
创建一个控制器映射,该映射将获取用户列表,将其作为模型属性并返回users.html
视图:
控制器
@Controller
public class UserController {
@Autowired
ActiveUserStore activeUserStore;
@GetMapping("/loggedUsers")
public String getLoggedUsers(Locale locale, Model model) {
model.addAttribute("users", activeUserStore.getUsers());
return "users";
}
}
users.html
<html>
<body>
<h2>Currently logged in users</h2>
<div th:each="user : ${users}">
<p th:text="${user}">user</p>
</div>
</body>
</html>
使用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();
}
结论
在这篇文章中,我们展示了如何在Spring Security应用中确定当前已登录的用户。
本教程的实现可以在GitHub项目中找到——这是一个基于Maven的项目,因此导入并运行起来应该相当简单。