spring-security使用-同一个账号只允许登录一次(五) 自动挤掉前一个用户 禁止新的账号登录 源码 

1.配置一个用户只允许一个会话

    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .rememberMe()
                .key("system")
                .and()
                .formLogin()
                .authenticationDetailsSource(new MyWebAuthenticationDetailsSource())
                .usernameParameter("loginName")
                .passwordParameter("loginPassword")
                .defaultSuccessUrl("/hello")
                .failureForwardUrl("/loginFail")
                .failureUrl("/login.html")
                .permitAll()//不拦截
                .and()
                .csrf()//记得关闭
                .disable()
                .sessionManagement()
                .maximumSessions(1);
    }

2.重写userDetail的hashCode和quals

public class UserInfoDto implements UserDetails {
   
    //....省略部分代码
    @Override
    public String toString() {
        return this.username;
    }

    @Override
    public int hashCode() {
        return username.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return this.toString().equals(obj.toString());
    }
}

3.分别用同一个账号2个浏览器登录。然后再访问第一次登录成功的用户则出现提示

spring-security使用-同一个账号只允许登录一次(五)
自动挤掉前一个用户
禁止新的账号登录
源码 

禁止新的账号登录

1.配置

  protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .rememberMe()
                .key("system")
                .and()
                .formLogin()
                .authenticationDetailsSource(new MyWebAuthenticationDetailsSource())
                .usernameParameter("loginName")
                .passwordParameter("loginPassword")
                .defaultSuccessUrl("/hello")
                .failureForwardUrl("/loginFail")
                .failureUrl("/login.html")
                .permitAll()//不拦截
                .and()
                .csrf()//记得关闭
                .disable()
                .sessionManagement()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true);
    }

2.增加一个监听的bean

spring事件使用参考<spring源码阅读(一)-附录例子>

 @Bean
    HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
public class HttpSessionEventPublisher implements HttpSessionListener {
    private static final String LOGGER_NAME = org.springframework.security.web.session.HttpSessionEventPublisher.class.getName();

    public HttpSessionEventPublisher() {
    }

    /**
     * 获得当前ServletContext的spring容器
     * @param servletContext
     * @return
     */
    ApplicationContext getContext(ServletContext servletContext) {
        return SecurityWebApplicationContextUtils.findRequiredWebApplicationContext(servletContext);
    }

    /**
     * 创建session的 spring事件发送
     * @param event
     */
    public void sessionCreated(HttpSessionEvent event) {
        HttpSessionCreatedEvent e = new HttpSessionCreatedEvent(event.getSession());
        Log log = LogFactory.getLog(LOGGER_NAME);
        if (log.isDebugEnabled()) {
            log.debug("Publishing event: " + e);
        }

        this.getContext(event.getSession().getServletContext()).publishEvent(e);
    }

    /**
     * session销毁的spring事件发送
     * @param event
     */
    public void sessionDestroyed(HttpSessionEvent event) {
        HttpSessionDestroyedEvent e = new HttpSessionDestroyedEvent(event.getSession());
        Log log = LogFactory.getLog(LOGGER_NAME);
        if (log.isDebugEnabled()) {
            log.debug("Publishing event: " + e);
        }
        this.getContext(event.getSession().getServletContext()).publishEvent(e);
    }
}

3.如果有账号登录另外一个账号登录则会提示

spring-security使用-同一个账号只允许登录一次(五)
自动挤掉前一个用户
禁止新的账号登录
源码 

源码 

1.UsernamePasswordAuthenticationFilter的父类AbstractAuthenticationProcessingFilter

sessionStrategy默认为org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy 

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        
               //省略部分代码......
             // 调用子类的attemptAuthentication 处理登录认证
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    return;
                }

                //认证成功走session校验
                this.sessionStrategy.onAuthentication(authResult, request, response);
                 //省略部分代码......

    }

2.org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#onAuthentication

 public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException {
        SessionAuthenticationStrategy delegate;
        //遍历delegateStrategies 调用onAuthentication方法
        for(Iterator var4 = this.delegateStrategies.iterator(); var4.hasNext(); delegate.onAuthentication(authentication, request, response)) {
            delegate = (SessionAuthenticationStrategy)var4.next();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Delegating to " + delegate);
            }
        }

    }

3.真正处理登录剔除和拦截的是SessionAuthenticationStrategy的实现类

org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy

  public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
        //获得指定用户名所有的session authentication.getPrincipal()就是登陆名
        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) {
            sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
            int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
            List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
            Iterator var6 = sessionsToBeExpired.iterator();
            //将登录的剔除
            while(var6.hasNext()) {
                SessionInformation session = (SessionInformation)var6.next();
                session.expireNow();
            }

        } else {
            //抛出异常 禁止多端登陆
            throw new SessionAuthenticationException(this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed", new Object[]{allowableSessions}, "Maximum sessions of {0} for this principal exceeded"));
        }
    }