Spring Security密码过期教程

在这个Spring Security教程中,我很乐意与你们分享如何基于Spring Data JPA,Spring Security,Thymeleaf和MySQL数据库等标准技术为现有的Spring Boot应用程序实现密码过期功能。假设您有一个已经实现身份验证的现有 Spring Boot 应用程序,现在需要对其进行更新以实现密码过期功能,具有以下要求:- 用户必须在上次更新密码后的 30 天后更改密码。 - 应用程序将要求用户在发现密码过期时更改其密码, 在他使用网站期间(包括成功登录后)。系统将要求用户以以下形式更改过期的密码:现在,让我们看看如何详细编写代码。

1. 更新数据库表和实体类

假设用户信息存储在名为 customers 的表中,因此您需要更改该表以添加名为password_changed_time 的新列,如下所示:

列的类型是 DATETIME,因此它将存储精确到秒的时间。您的应用程序应以某种方式更新此列的值,例如在用户注册或激活时。然后更新相应的实体类,如下所示:在这里,我们声明一个 long 类型的常量来表示 30 天内的毫秒数(密码过期时间)。 字段passwordChangedTime映射到数据库表中的相应列, isPasswordExpire()方法用于检查用户的密码是否过期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Entity
@Table(name = "customers")
public class Customer {
    private static final long PASSWORD_EXPIRATION_TIME
            = 30L * 24L * 60L * 60L * 1000L;    // 30 days
     
    @Column(name = "password_changed_time")
    private Date passwordChangedTime;  
     
    public boolean isPasswordExpired() {
        if (this.passwordChangedTime == nullreturn false;
         
        long currentTime = System.currentTimeMillis();
        long lastChangedTime = this.passwordChangedTime.getTime();
         
        return currentTime > lastChangedTime + PASSWORD_EXPIRATION_TIME;
    }
 
    // other fields, getters and setters are not shown 
}

2. 更新用户服务类

接下来,更新用户业务类(在我的例子中是客户服务)以实现更新客户密码的方法,如下所示:当用户更改其密码时,控制器将使用changePassword() 方法。正如您注意到的,密码更改时间值设置为当前日期时间,因此用户将有接下来的 30 天时间,直到新更改的密码过期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
@Transactional
public class CustomerServices {
 
    @Autowired CustomerRepository customerRepo;
    @Autowired PasswordEncoder passwordEncoder;
     
    public void changePassword(Customer customer, String newPassword) {
        String encodedPassword = passwordEncoder.encode(newPassword);
        customer.setPassword(encodedPassword);
         
        customer.setPasswordChangedTime(new Date());
         
        customerRepo.save(customer);
    }
}

3. 代码密码过期过滤器

接下来,我们需要编写一个 filter 类来拦截来自应用程序的所有请求,以检查当前登录的用户的密码是否已过期,如下所示:这是确定当前请求是否用于静态资源(images、CSS、JS...)的方法的代码:如果请求用于获取静态资源, 应用程序将继续过滤器链 - 不再处理。下面是返回表示经过身份验证的用户的UserDetails对象的方法的代码:在这里,CustomerDetails是一个实现Spring Security定义的UserDetails接口的类。项目中应该有类似的类。此筛选器类中的最后一种方法是在用户的密码过期时将用户重定向到更改密码页面,如下所示:因此,您可以注意到更改密码页面的相对 URL 是/change_password – 我们将在下一节中编写一个处理此 URL 的控制器类。

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
@Component
public class PasswordExpirationFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
         
        if (isUrlExcluded(httpRequest)) {
            chain.doFilter(request, response);
            return;
        }
         
        System.out.println("PasswordExpirationFilter");
 
        Customer customer = getLoggedInCustomer();
         
        if (customer != null && customer.isPasswordExpired()) {
                showChangePasswordPage(response, httpRequest, customer);           
        else {
            chain.doFilter(httpRequest, response);         
        }
         
    }
     
}

1
2
3
4
5
6
7
8
9
10
11
private boolean isUrlExcluded(HttpServletRequest httpRequest)
        throws IOException, ServletException {
    String url = httpRequest.getRequestURL().toString();
     
    if (url.endsWith(".css") || url.endsWith(".png") || url.endsWith(".js")
            || url.endsWith("/change_password")) {
        return true;
    }
     
    return false;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private Customer getLoggedInCustomer() {
    Authentication authentication
        = SecurityContextHolder.getContext().getAuthentication();
    Object principal = null;
     
    if (authentication != null) {
        principal = authentication.getPrincipal();
    }
     
    if (principal != null && principal instanceof CustomerUserDetails) {
        CustomerUserDetails userDetails = (CustomerUserDetails) principal;
        return userDetails.getCustomer();
    }
     
    return null;
}

1
2
3
4
5
6
7
8
9
10
private void showChangePasswordPage(ServletResponse response,
        HttpServletRequest httpRequest, Customer customer) throws IOException {
    System.out.println("Customer: " + customer.getFullName() + " - Password Expired:");
    System.out.println("Last time password changed: " + customer.getPasswordChangedTime());
    System.out.println("Current time: " new Date());
     
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    String redirectURL = httpRequest.getContextPath() + "/change_password";
    httpResponse.sendRedirect(redirectURL);
}

4. 代码密码控制器类

接下来,创建一个新的Spring MVC控制器类来显示更改密码页面以及处理密码更改,一些初始代码如下:如您所见,第一个处理程序方法仅返回逻辑视图名称change_password该名称将被解析为相应的HTML页面,这将在下面描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Controller
public class PasswordController {
 
    @Autowired
    private CustomerServices customerService;
     
    @Autowired
    private PasswordEncoder passwordEncoder;
     
    @GetMapping("/change_password")
    public String showChangePasswordForm(Model model) {
        model.addAttribute("pageTitle""Change Expired Password");    
        return "change_password";
    }
     
    @PostMapping("/change_password")
    public String processChangePassword() {
        // implement later...
         
    }  
     
}

5. 代码更改密码页面

接下来,在文档正文中使用以下代码创建change_password.html页面:这将显示包含 3 个密码字段的更改密码表单:旧密码、新密码和确认新密码。还要放下面的Javascript代码来验证两个新密码字段的匹配:你也可以注意到我使用了HTML 5,Thymeleaf,Bootstrap和jQuery。并实现第二个处理程序方法,用于在提交更改密码页面时更新用户的密码,如下所示:在这里,它获取一个表示经过身份验证的用户的UserDetails对象。然后,它会检查以确保新密码与旧密码不同,并且旧密码正确。如果同时满足这两个条件,它将使用新密码更新用户的密码并将用户注销 - 然后显示登录页面。接下来,我们已准备好测试密码过期功能。

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
<div>
    <h2>Change Your Expired Password</h2>
</div>
         
<form th:action="@{/change_password}" method="post" style="max-width: 350px; margin: 0 auto;">
<div class="border border-secondary rounded p-3">
    <div th:if="${message != null}" class="m-3">
        <p class="text-danger">[[${message}]]</p>
    </div>       
    <div>
        <p>
            <input type="password" name="oldPassword" class="form-control"
                    placeholder="Old Password" required autofocus />
        </p>     
        <p>
            <input type="password" name="newPassword" id="newPassword" class="form-control"
                    placeholder="New password" required />
        </p>         
        <p>
            <input type="password" class="form-control" placeholder="Confirm new password"
                    required oninput="checkPasswordMatch(this);" />
        </p>         
        <p class="text-center">
            <input type="submit" value="Change Password" class="btn btn-primary" />
        </p>
    </div>
</div>
</form>

1
2
3
4
5
6
7
function checkPasswordMatch(fieldConfirmPassword) {
    if (fieldConfirmPassword.value != $("#newPassword").val()) {
        fieldConfirmPassword.setCustomValidity("Passwords do not match!");
    } else {
        fieldConfirmPassword.setCustomValidity("");
    }
}

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
@PostMapping("/change_password")
public String processChangePassword(HttpServletRequest request, HttpServletResponse response,
        Model model, RedirectAttributes ra,
        @AuthenticationPrincipal Authentication authentication) throws ServletException {
    CustomerUserDetails userDetails = (CustomerUserDetails) authentication.getPrincipal();
    Customer customer = userDetails.getCustomer();
     
    String oldPassword = request.getParameter("oldPassword");
    String newPassword = request.getParameter("newPassword");
     
    model.addAttribute("pageTitle""Change Expired Password");
     
    if (oldPassword.equals(newPassword)) {
        model.addAttribute("message""Your new password must be different than the old one.");
         
        return "change_password";
    }
     
    if (!passwordEncoder.matches(oldPassword, customer.getPassword())) {
        model.addAttribute("message""Your old password is incorrect.");          
        return "change_password";
         
    else {
        customerService.changePassword(customer, newPassword);
        request.logout();
        ra.addFlashAttribute("message""You have changed your password successfully. "
                "Please login again.");
         
        return "redirect:/login";          
    }
     
}

6. 测试密码过期功能

使用像MySQL Workbench这样的数据库工具将用户的更改密码时间更新为从当前日期起超过30天的值。然后尝试使用该用户的电子邮件登录,您应该看到应用程序要求更改密码,如下所示:尝试为 2 个新密码字段输入不同的值,然后单击更改密码按钮,浏览器应立即捕获错误:接下来,尝试输入错误的旧密码但相同的新密码并单击按钮, 您将看到以下错误:现在,使用正确的旧密码和两个相同的新密码提交表单,您应该看到以下屏幕:现在输入新密码登录,您应该能够登录应用程序。还要检查数据库以确保密码更改时间列的值设置为新值,即当前日期和时间。恭喜,您已成功为现有的 Spring 引导应用程序实现密码过期功能。要了解实际编码,我建议您观看以下视频:

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Security是一个功能强大且灵活的身份验证和访问控制框架,用于保护Java应用程序的安全性。它提供了一套细粒度的安全性控制机制,可以轻松地集成到Spring应用程序中。 Spring Security的主要功能包括: 1. 身份验证(Authentication):验证用户的身份,并提供用户凭证(如用户名和密码)的验证机制。Spring Security支持多种身份验证方式,包括基于表单、基于HTTP基本认证、基于LDAP等。 2. 授权(Authorization):控制用户对资源的访问权限。Spring Security提供了基于角色(Role)和权限(Permission)的授权机制,可以通过注解或配置文件来定义访问控制规则。 3. 攻击防护(Attack Protection):提供了一系列的防护机制,用于防止常见的安全攻击,如跨站点请求伪造(CSRF)、会话固定攻击、点击劫持等。 4. 会话管理(Session Management):管理用户会话的生命周期,并提供了一些与会话相关的功能,如并发登录控制、会话过期处理等。 5. 记住我(Remember Me):提供了“记住我”功能,允许用户在下次访问应用程序时自动登录。 6. 安全事件和日志(Security Events and Logging):Spring Security提供了一套事件模型和日志机制,用于记录安全相关的事件和操作。 Spring Security的核心是一组过滤器链(Filter Chain),这些过滤器链按照特定的顺序依次对请求进行处理。每个过滤器负责不同的安全功能,如身份验证、授权、攻击防护等。 使用Spring Security可以轻松地为Spring应用程序添加安全性,保护应用程序的资源和数据不受未经授权的访问。它提供了丰富的配置选项和扩展点,可以根据具体求进行灵活的定制和扩展。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值