zoukankan      html  css  js  c++  java
  • 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个浏览器登录。然后再访问第一次登录成功的用户则出现提示

    禁止新的账号登录

    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.如果有账号登录另外一个账号登录则会提示

    源码 

    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"));
            }
        }
  • 相关阅读:
    tars framework 源码解读(五) framework 部分章节。PropertyServer,StatServer上报统计服务
    tars framework 源码解读(五) framework 部分章节。NotifyServer 通知服务
    mysql之 percona-xtrabackup 2.4.7安装(热备工具)
    年轻
    C语言整型数据(整数)
    【转】想象5年后的你
    MyBatis学习总结(二)——MyBatis核心配置文件与输入输出映射
    CSS3——前端预处理技术(Less、Sass、CoffeeScript、TypeScript)
    HTML(三)——本地存储
    HTML5(二)——特殊符号、新增属性、表单重写属性、
  • 原文地址:https://www.cnblogs.com/LQBlog/p/14241787.html
Copyright © 2011-2022 走看看