概述

在这篇快速教程中,我们将展示如何使用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应用中,这可以通过实现AuthenticationSuccessHandlerLogoutSuccessHandler接口来完成。

实现AuthenticationSuccessHandler

对于登录操作,我们将通过覆盖onAuthenticationSuccess()方法来将登录用户的用户名设置为会话属性,该方法提供了对sessionauthentication对象的访问:

@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的项目,因此导入并运行起来应该相当简单。


« 上一篇: Java周报,131
» 下一篇: Spring Data Neo4j介绍