SpringSecurity之sessionManagement設置maximumSessions無效


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 方法,否則第一次存完數據,下次就找不到了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM