1. 概述
本快速教程將演示如何使用 Spring Security 在應用程序中跟蹤當前已登錄的用户。
我們將通過在用户登錄時添加用户並在此用户註銷時刪除用户來維護已登錄用户的列表。
我們將利用 <em>HttpSessionBindingListener</em> 在用户登錄系統或註銷系統時,根據用户日誌更新已登錄用户的列表。
2. 活躍用户存儲
為了簡化起見,我們將定義一個類,該類作為已登錄用户的內存中存儲器:
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();
}3. 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() 方法會在用户註銷或會話過期時被調用。
在每個方法中,我們檢索與事件關聯的值,然後根據值是否從會話中綁定或解除綁定,將用户名添加到或從我們的已登錄用户列表中。
4. 跟蹤登錄和登出
現在我們需要跟蹤用户成功登錄或登出時的情況,以便能夠從會話中添加或刪除活動用户。在 Spring Security 應用程序中,可以通過實現 <em >AuthenticationSuccessHandler</em> 和 <em >LogoutSuccessHandler</em> 接口來實現。
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</h3
對於註銷操作,我們將通過覆蓋 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>
使用 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 應用程序中確定當前已登錄用户的身份。