1. 程式人生 > >Spring Boot + Spring Security 防止使用者在多處同時登入(一個使用者同時只能登入一次)及原始碼分析

Spring Boot + Spring Security 防止使用者在多處同時登入(一個使用者同時只能登入一次)及原始碼分析

網上很多文章的實現方法寫得比較複雜
這裡介紹一個簡單的方法。

實現

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/user/**"
).access("hasRole('ADMIN') or hasRole('USER')") .and().formLogin().permitAll(); //以下這句就可以控制單個使用者只能建立一個session,也就只能在伺服器登入一次 http.sessionManagement().maximumSessions(1).expiredUrl("/login"); }

原理

下面介紹下Spring Security的session數量控制的工作原理。

在org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy包下的onAuthentication方法(每次使用者登入都會觸發),會依據使用者登入的authentication取出改使用者在伺服器的所有session,並計算該使用者在伺服器建立了多少個session,如果session多於設定的數量,會使用排序演算法,得到最早的session,並將其設定為過期(刪除)。

    public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
        List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
        int sessionCount = sessions.size();
        int
allowedSessions = this.getMaximumSessionsForThisUser(authentication); if (sessionCount >= allowedSessions) { if (allowedSessions != -1) { if (sessionCount == allowedSessions) { HttpSession session = request.getSession(false); if (session != null) { Iterator var8 = sessions.iterator(); while(var8.hasNext()) { SessionInformation si = (SessionInformation)var8.next(); if (si.getSessionId().equals(session.getId())) { return; } } } } this.allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry); } } } protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions, SessionRegistry registry) throws SessionAuthenticationException { if (!this.exceptionIfMaximumExceeded && sessions != null) { SessionInformation leastRecentlyUsed = null; Iterator var5 = sessions.iterator(); while(true) { SessionInformation session; do { if (!var5.hasNext()) { leastRecentlyUsed.expireNow(); return; } session = (SessionInformation)var5.next(); } while(leastRecentlyUsed != null && !session.getLastRequest().before(leastRecentlyUsed.getLastRequest())); leastRecentlyUsed = session; } } else { throw new SessionAuthenticationException(this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed", new Object[]{allowableSessions}, "Maximum sessions of {0} for this principal exceeded")); } }

Spring Security 是使用org.springframework.security.core.userdetails.User類作為使用者登入憑據( Principal )的。該類中重寫了equals()和hashCode(),使用username屬性作為唯一憑據。

    public boolean equals(Object rhs) {
        return rhs instanceof User ? this.username.equals(((User)rhs).username) : false;
    }

    public int hashCode() {
        return this.username.hashCode();
    }