maximumSessions配置session最大的數量,可以實現常見的,一個賬號同一時間只能在一台設備登錄,類似qq,實現方式有兩種,一種是后登錄的人會把先登錄的人擠下去,還有一種一旦賬號被登錄,其他人就不能在登陸了,具體使用那種還是得看業務
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.successHandler((req,resp,authentication) -> {
Object principal = authentication.getPrincipal();
PrintWriter out = resp.getWriter();
out.println(JSON.toJSON(principal));
out.flush();
out.close();
})
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated()
.and()
.csrf().disable()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true); // 配置為true就是一旦用戶登錄,則不允許其他人登錄,使用這個需要配置一個Bean(HttpSessionEventPublisher)
}
@Bean
HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
分析:如題所示,在使用Security的sessionManagement配置maximumSessions無效,這是因為Spring Security 中通過 SessionRegistryImpl 類來實現對會話信息的統一管理,看看這個類的源碼
public class SessionRegistryImpl implements SessionRegistry,
ApplicationListener<SessionDestroyedEvent> {
/** <principal:Object,SessionIdSet> */
private final ConcurrentMap<Object, Set<String>> principals;
/** <sessionId:Object,SessionInformation> */
private final Map<String, SessionInformation> sessionIds;
public void registerNewSession(String sessionId, Object principal) {
if (getSessionInformation(sessionId) != null) {
removeSessionInformation(sessionId);
}
sessionIds.put(sessionId,
new SessionInformation(principal, sessionId, new Date()));
principals.compute(principal, (key, sessionsUsedByPrincipal) -> {
if (sessionsUsedByPrincipal == null) {
sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
}
sessionsUsedByPrincipal.add(sessionId);
return sessionsUsedByPrincipal;
});
}
public void removeSessionInformation(String sessionId) {
SessionInformation info = getSessionInformation(sessionId);
if (info == null) {
return;
}
sessionIds.remove(sessionId);
principals.computeIfPresent(info.getPrincipal(), (key, sessionsUsedByPrincipal) -> {
sessionsUsedByPrincipal.remove(sessionId);
if (sessionsUsedByPrincipal.isEmpty()) {
sessionsUsedByPrincipal = null;
}
return sessionsUsedByPrincipal;
});
}
}
1.首先一上來聲明了一個 principals 對象,這是一個支持並發訪問的 map 集合,集合的 key 就是用戶的主體(principal),正常來說,用戶的 principal 其實就是用戶對象
2.如有新的 session 需要添加,就在 registerNewSession 方法中進行添加,具體是調用 principals.compute 方法進行添加,key 就是 principal。
3.如果用戶注銷登錄,sessionid 需要移除,相關操作在 removeSessionInformation 方法中完成,具體也是調用 principals.computeIfPresent 方法,這些關於集合的基本操作我就不再贅述了。
其中的ConcurrentMap 集合的 key 是 principal 對象,用對象做 key,一定要重寫 equals 方法和 hashCode 方法,否則第一次存完數據,下次就找不到了。
