• Spring Security限制登录尝试示例


    通过这个 Spring 安全性教程,我将指导您如何通过实现限制登录尝试功能来加强 Spring Boot 应用程序的安全性,该功能可防止暴力猜测可能被黑客利用的密码。您将学习如何使用以下策略实现限制登录尝试次数功能:

    • 用户最多可以登录失败 3 次。他的帐户将在最后一次失败的尝试中被锁定。
    • 用户帐户在 24 小时内被锁定。这意味着在此持续时间之后,用户帐户将被解锁(在下次登录尝试时)。

    假设您正在开发一个基于 Spring Boot 的 Java Web 应用程序,并且已经使用 Spring 安全性和 MySQL 数据库(存储用户信息)实现了身份验证。作为通用标准,Thymeleaf用作模板引擎,Spring Data JPA和Hibernate用于数据访问层,HTML 5和Bootstrap用于UI。

     

    1. 更新用户表和用户实体类

    假设用户信息存储在名为users 的表中。您需要添加 3 个额外的列才能实现限制登录尝试功能。它们是:

    • failed_attempt:一个小整数,指示失败的登录尝试次数。如果failed_attempt,用户帐户将被锁定> 2.默认值为 0。
    • account_non_locked:一个布尔值,指示用户帐户是否已锁定。 Spring 安全性将拒绝登录锁定的帐户。默认值为 true(MySQL 中的 1)。
    • lock_time:指示用户帐户锁定时间的日期时间值。根据此值,应用程序可以确定锁定何时过期并解锁用户帐户。默认值为空。

    下图显示了包含 3 个新列的用户表的结构:然后更新实体类User,如下所示:在这里,我们添加 3 个新字段,这些字段相应地映射到users表中的 3 个新列。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    import javax.persistence.*;
     
    @Entity
    @Table(name = "users")
    public class User {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
         
        private String email;
         
        private String password;
         
        private String firstName;
         
        private String lastName;
         
        private boolean enabled;
         
        @Column(name = "account_non_locked")
        private boolean accountNonLocked;
         
        @Column(name = "failed_attempt")
        private int failedAttempt;
         
        @Column(name = "lock_time")
        private Date lockTime;
     
        // constructors...
         
        // getters...
         
        // setters...
    }

     

     

    2. 更新用户详细信息类

    使用 Spring 安全性实现身份验证时,您已经创建了一个类型为“用户详细信息”的类来保存经过身份验证的用户的详细信息。因此,请确保isAccountNonLocked() 方法更新如下:这很重要,因为如果此方法返回 false,Spring Security 将拒绝身份验证。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import org.springframework.security.core.userdetails.UserDetails;
     
     
    public class ShopmeUserDetails implements UserDetails {
        private User user;
         
        public ShopmeUserDetails(User user) {
            this.user = user;
        }
     
        @Override
        public boolean isAccountNonLocked() {
            return user.isAccountNonLocked();
        }
         
        // other overridden methods...
    }

     

    3. 更新用户存储库和服务类

    接下来,您需要在UserRepository类中声明一个方法,以根据用户的电子邮件更新用户的失败登录尝试次数,如下所示:如您所见,我们使用自定义查询(JPA 查询)。并通过实现 4 个新方法和几个常量来更新业务类UserServices,如下所示:让我解释一下这个新代码。首先,我们声明允许的最大失败登录尝试次数:以及锁定时间的持续时间(以毫秒为单位):因此很容易配置/更改允许的最大失败登录次数和锁定持续时间。让我们来看看新方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import org.springframework.data.jpa.repository.*;
     
    public interface UserRepository extends JpaRepository {
             
        @Query("UPDATE User u SET u.failedAttempt = ?1 WHERE u.email = ?2")
        @Modifying
        public void updateFailedAttempts(int failAttempts, String email);
         
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    @Service
    @Transactional
    public class UserServices {
     
        public static final int MAX_FAILED_ATTEMPTS = 3;
         
        private static final long LOCK_TIME_DURATION = 24 60 60 1000// 24 hours
         
        @Autowired
        private UserRepository repo;
         
        public void increaseFailedAttempts(User user) {
            int newFailAttempts = user.getFailedAttempt() + 1;
            repo.updateFailedAttempts(newFailAttempts, user.getEmail());
        }
         
        public void resetFailedAttempts(String email) {
            repo.updateFailedAttempts(0, email);
        }
         
        public void lock(User user) {
            user.setAccountNonLocked(false);
            user.setLockTime(new Date());
             
            repo.save(user);
        }
         
        public boolean unlockWhenTimeExpired(User user) {
            long lockTimeInMillis = user.getLockTime().getTime();
            long currentTimeInMillis = System.currentTimeMillis();
             
            if (lockTimeInMillis + LOCK_TIME_DURATION < currentTimeInMillis) {
                user.setAccountNonLocked(true);
                user.setLockTime(null);
                user.setFailedAttempt(0);
                 
                repo.save(user);
                 
                return true;
            }
             
            return false;
        }
    }

    1
    public static final int MAX_FAILED_ATTEMPTS = 3;

    1
    private static final long LOCK_TIME_DURATION = 24 60 60 1000// 24 hours

    • increaseFailedTrys():此方法更新用户的失败尝试次数。每次用户登录失败(例如,提供错误的用户名或密码)时都会调用它。
    • resetFailedTrys():将失败的尝试次数设置为零。当用户成功登录时,将调用此方法。
    • lock():如果登录失败次数达到允许的最大次数,则锁定用户帐户。
    • unlockWhenTimeExpire():在锁定持续时间到期时解锁用户帐户,允许用户照常登录。

     

    4. 更新登录页面

    在自定义登录页面中,确保它包含以下代码片段,以便在登录失败时显示异常消息。在登录页面中包含此代码很重要,因此它将显示 Spring 生成的原始错误消息 Security.To 了解有关使用 Spring 安全性编写自定义登录页面的更多信息,请参阅本文

    1
    2
    3
    <div th:if="${param.error}">
        <p class="text-danger">[[${session.SPRING_SECURITY_LAST_EXCEPTION.message}]]p>
    div>

     

    5. 代码认证失败处理程序

    接下来,我们需要编写一个自定义身份验证失败处理程序类来干预 Spring Security 的身份验证过程,以便更新失败的登录尝试次数,锁定和解锁用户帐户。因此,使用以下代码创建一个新类CustomLoginFailureHandler:让我解释一下这段代码。首先,它根据在登录页面中输入的电子邮件获取一个 User 对象。如果在数据库中找到该用户,并且该用户已启用且未锁定:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    package com.shopme.admin.security;
     
    import java.io.IOException;
     
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.LockedException;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
    import org.springframework.stereotype.Component;
     
    @Component
    public class CustomLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
         
        @Autowired
        private UserServices userService;
         
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                AuthenticationException exception) throws IOException, ServletException {
            String email = request.getParameter("email");
            User user = userService.getByEmail(email);
             
            if (user != null) {
                if (user.isEnabled() && user.isAccountNonLocked()) {
                    if (user.getFailedAttempt() < UserServices.MAX_FAILED_ATTEMPTS - 1) {
                        userService.increaseFailedAttempts(user);
                    else {
                        userService.lock(user);
                        exception = new LockedException("Your account has been locked due to 3 failed attempts."
                                " It will be unlocked after 24 hours.");
                    }
                else if (!user.isAccountNonLocked()) {
                    if (userService.unlockWhenTimeExpired(user)) {
                        exception = new LockedException("Your account has been unlocked. Please try to login again.");
                    }
                }
                 
            }
             
            super.setDefaultFailureUrl("/login?error");
            super.onAuthenticationFailure(request, response, exception);
        }
     
    }

    • 如果未达到允许的最大次数,请增加失败的登录尝试次数,否则:
    • 如果失败的登录时间等于或大于允许的最大时间,请锁定用户帐户。

    如果用户的帐户被锁定,它将在锁定持续时间到期时尝试解锁帐户。请注意,我们抛出LockedException(由 Spring Security 定义)以及自定义错误消息,该错误消息将显示在登录页面中。阅读本文以了解有关 Spring 安全性中的身份验证失败处理程序的更多信息。

     

    6. 代码认证成功处理程序

    在某些情况下,用户第一次(或第二次)登录失败,但下次登录成功。因此,应用程序应在用户成功登录后立即清除失败的登录尝试次数。为此,您需要使用以下代码创建自定义身份验证处理程序类:如您所见,用户成功登录后,应用程序会将失败的登录尝试次数重置为零。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import java.io.IOException;
     
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
    import org.springframework.stereotype.Component;
     
    @Component
    public class CustomLoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
     
        @Autowired
        private UserServices userService;
         
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                Authentication authentication) throws IOException, ServletException {
            ShopmeUserDetails userDetails =  (ShopmeUserDetails) authentication.getPrincipal();
            User user = userDetails.getUser();
            if (user.getFailedAttempt() > 0) {
                userService.resetFailedAttempts(user.getEmail());
            }
             
            super.onAuthenticationSuccess(request, response, authentication);
        }
         
    }

     

    7. 更新 Spring 安全配置类

    要启用上面的自定义身份验证失败和成功处理程序,您需要更新 Spring 安全性配置类,如下所示:这是为编码完成的。接下来,我们准备测试限制登录尝试函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                .antMatchers("/login").permitAll()
                ...
                .anyRequest().authenticated()
                ...
                .formLogin()
                    .loginPage("/login")
                    .usernameParameter("email")
                    .failureHandler(loginFailureHandler)
                    .successHandler(loginSuccessHandler)               
                    .permitAll()
                ...
        }
         
        @Autowired
        private CustomLoginFailureHandler loginFailureHandler;
         
        @Autowired
        private CustomLoginSuccessHandler loginSuccessHandler;
    }

     

    8. 测试限制登录尝试功能

    在测试之前,请确保新列具有默认值:0 表示failed_attempt,true 表示account_non_locked,null 表示lock_time。启动您的 Spring 引导应用程序并转到登录页面。输入正确的用户名但输入错误的密码,您将在第一次失败的登录尝试中看到以下错误:检查数据库,您应该看到failed_attempt= 1。现在尝试再次使用错误的密码登录。您会看到相同的错误,但failed_attempt= 2.接下来,尝试进行第三次失败的登录,它说用户帐户已被锁定:检查数据库,您应该看到failed_attempt= 2,account_non_locked= 0(false)并且lock_time设置为特定时间。然后,用户将无法在 24 小时内登录,因为他的帐户被锁定。等待锁定时间到期(为了快速测试,您可以将锁定持续时间更改为 5 分钟),然后使用正确的凭据再次登录。用户将看到此屏幕:并且用户必须再次登录(使用正确的凭据)。检查数据库,您应该看到新 3 列的值已设置为默认值。这是一些代码示例和参考,供您在 Spring 引导应用程序中实现限制登录尝试功能。要了解实际编码,我建议您观看以下视频:

  • 相关阅读:
    Java框架最全面试攻略,吃透这些问题,面试官奈你不何
    苹果双系统和虚拟机哪个好用?
    部署:端口映射相关问题
    electron应用打包成功纪念一下
    c++入门学习⑦——继承和多态(超级详细版)
    组件之间通过bus中央事件总线进行通信
    openGauss学习笔记-129 openGauss 数据库管理-参数设置-查看参数值
    Mac PS2023/2024储存窗口黑屏不显示 解决方法
    Yukon 学习记录
    企业如何建立全链路数字化体系,才能让卖货更容易?
  • 原文地址:https://blog.csdn.net/allway2/article/details/127713922