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"));
        }
    }

獲取登錄失敗原因

可以通過設置攔截器獲取 保存到當前會話

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());
 
    @Autowired
    private ObjectMapper objectMapper;
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
 
        logger.info("登陸失敗");
 
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(exception));//將異常寫入response中,顯示在頁面上
    }

 

 @Override
    protected void configure(HttpSecurity http) throws Exception {
 
        http.formLogin()
                .loginPage("/authentication/require")
                .loginProcessingUrl("/authentication/form")
                .successHandler(myAuthenticationSuccessHandler)//配置successHandler
                .failureHandler(myAuthenticationFailureHandler)//配置failureHandler
                .and()
                .authorizeRequests()
                .antMatchers(
                        "/loginPage.html",
                        "/myLoginPage.html",
                        "/authentication/require"
 
                ).permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().disable();

 


免責聲明!

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



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