🥬
FreshForFresh
  • ✌️Xin chào!
  • Overview
    • 💡Data structures and Algorithms
    • 👀Programming
    • 🧠Database
    • 🦴Back-End
    • ✨Front-End
    • 💐Spring Framework
    • 🛠️Tool
  • Data structures and Algorithms
    • 🎰Data Type
    • 🗑️Garbage collection
    • 🧩Data structures
    • 🎲Algorithms
  • Programming
    • 🧮Compiler
      • 🤖Tại sao phải cần compiler?
      • 📠Java Virtual Machine
  • 📈Thread
  • 🔐Lock & Deadlock
  • 🏇Race condition & Data Race
  • 🧯Synchronized
  • 🗜️Blocking IO và Non Blocking IO Client Server Socket
  • 🌐Languages
    • 🎛️Programming Language
    • 📑Client-Side & Server-Side
    • ⁉️Why java? Why javascript?
  • 🗼Design pattern
    • 🎨Front-End Design Pattern
    • 🏗️Back End Design Pattern
      • 🐴with Java
      • 🦄with Spring Boot
      • 🐖with Modern Backend Development
      • 🐁with Microservice
  • 🍀Clean Code
    • 🌊Chương 1: Code sạch
    • 📐Chương 2: Quy tắc đặt tên rõ nghĩa
    • 🚧Chương 3: Cách viết hàm
    • 👻Chương 4: Comments thế nào cho chuẩn?
    • 🥳Chương 5: Định dạng code.
    • 😈Chương 6: Đối tượng và cấu trúc dữ liệu
    • 🐛Chương 7: Xử lí lỗi
    • ☦️Chương 8: Ranh giới code
    • 🧪Chương 9: Unit test
    • 🥂Chương 10: Lớp đối tượng
    • 🥡Chương 11: Code sạch cấp hệ thống
  • Database
    • 🐔Giới thiệu
    • 📰Các loại cơ sở dữ liệu
    • 🐘Các loại DBMS phổ biến
    • 🕵️‍♀️SQL và cách sử dụng với relational database
      • 👺Lệnh SQL
      • 🛢️JOIN trong SQL
      • 🛕GROUP BY trong SQL
      • 🐼ORDER BY trong SQL
      • 🐣Truy vấn con SUBQUERY trong SQL Server
      • 🏦Transaction Trong SQL
      • 🇲🇰ACID
    • ☢️Thiết kế cơ sở dữ liệu
    • 🕍Consistency and concurrency handling
      • 🔒Database locking
      • 🎮Concurrency Control
      • 🙀Isolation Level
    • 🎨Performance & Scability
      • ☝️Indexing
      • 👯‍♂️Partitioning
      • 💅Các tips tăng hiệu suất SQL
      • 🇲🇬Kiểm tra và Xác thực việc Sao lưu và Phục hồi CSDL
      • 🌠Database sharding
      • 🧘‍♂️REPLICATION
      • 😁Các vấn đề ảnh hưởng đến performance.
  • Back-End
    • 🔫API?
    • 🐕‍🦺Web Services
    • 💾Phân biệt API và web service
    • 🙆‍♂️Tìm hiểu về RestAPI
    • 🧦SOAP
    • ✈️GraphQL API
    • 📊Compared GraphQL & REST
    • 📄Phân biệt HTTP & HTTPS
    • 🚵Client & Server
    • 🇩🇲DOM
  • Front-End
    • 🦑User Interface (UI)
    • 🛰️State Management
  • Spring Framework
    • 🔐Spring Security
      • 🌅Đơn giản hoá Spring Security
      • 🌸Spring Security: Authentication and Authorization In-Depth
      • 🚻OAuth2 Basic
      • 🀄JWT + Spring Security Oauth
      • 📤Logout trong ứng dụng sử dụng bảo mật Oauth
      • 🔂Reset Password
      • 🎗️OAuth2 Remember Me với Refresh Token
      • ⛓️OAuth2 cho một Spring REST API
  • Network
    • 💸Mạng máy tính căn bản
  • Tool
    • 🦏Các câu lệnh Docker cơ bản
    • 🦧Github
      • 🐰Các lệnh Git cơ bản
      • 🐇Một số trường hợp khi sử dụng Git
    • 🏚️WebServer
      • 🪂Tổng quan về kiến trúc hệ thống
      • 🔃Cơ bản về WebServer Nginx
  • Tài nguyên
    • 👨‍🏫Course
    • 📖Docs
Powered by GitBook
On this page
  • 1.Tổng quan
  • 2. Yêu cầu thiết lập lại mật khẩu của bạn
  • 3.Mã thông báo đặt lại mật khẩu
  • 4. forgotPassword.html
  • 5.Khởi tạo PasswordResetToken
  • 6. Kiểm tra PasswordResetToken
  • 7. Thay đổi mật khẩu
  • 8.Tham khảo
  1. Spring Framework
  2. Spring Security

Reset Password

PreviousLogout trong ứng dụng sử dụng bảo mật OauthNextOAuth2 Remember Me với Refresh Token

Last updated 1 year ago

1.Tổng quan

Trong bài viết này – chúng ta đang tiếp tục series Registration with Spring Security đang diễn ra với cái nhìn về tính năng cơ bản ‘Quên mật khẩu’ – để người dùng có thể đặt lại password của chính mình một cách an toàn khi họ cần.

2. Yêu cầu thiết lập lại mật khẩu của bạn

Quy trình đặt lại mật khẩu thường bắt đầu khi người dùng nhấp vào một số loại nút ‘đặt lại’ trên trang Đăng nhập. Sau đó, chúng tôi có thể yêu cầu người dùng cung cấp địa chỉ email của họ hoặc thông tin nhận dạng khác. Sau khi xác nhận, chúng tôi có thể tạo mã thông báo và gửi email cho người dùng. Sơ đồ sau đây trực quan hóa quy trình mà chúng ta sẽ triển khai trong bài viết này:

forgot pass

3.Mã thông báo đặt lại mật khẩu

Hãy bắt đầu bằng cách tạo một thực thể PasswordResetToken để sử dụng nó cho việc đặt lại mật khẩu của người dùng:

@Entity
public class PasswordResetToken {
 
    private static final int EXPIRATION = 60 * 24;
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
 
    private String token;
 
    @OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
    @JoinColumn(nullable = false, name = "user_id")
    private User user;
 
    private Date expiryDate;
}

Khi đặt lại mật khẩu được kích hoạt – một mã thông báo sẽ được tạo và một liên kết đặc biệt có chứa mã thông báo này sẽ được gửi qua email cho người dùng.

Mã thông báo và liên kết sẽ chỉ có hiệu lực trong một khoảng thời gian nhất định (24 giờ trong ví dụ này).

4. forgotPassword.html

Trang đầu tiên trong quá trình này là trang ‘Tôi quên mật khẩu của mình’ - nơi người dùng được nhắc nhập địa chỉ email của họ để quá trình đặt lại thực tế bắt đầu.

Vì vậy - hãy tạo một mật khẩu quên đơn giản.html yêu cầu người dùng cung cấp địa chỉ email:

<html>
<body>
    <h1 th:text="#{message.resetPassword}">reset</h1>

    <label th:text="#{label.user.email}">email</label>
    <input id="email" name="email" type="email" value="" />
    <button type="submit" onclick="resetPass()" 
      th:text="#{message.resetPassword}">reset</button>

<a th:href="@{/registration.html}" th:text="#{label.form.loginSignUp}">
    registration
</a>
<a th:href="@{/login}" th:text="#{label.form.loginLink}">login</a>

<script src="jquery.min.js"></script>
<script th:inline="javascript">
var serverContext = [[@{/}]];
function resetPass(){
    var email = $("#email").val();
    $.post(serverContext + "user/resetPassword",{email: email} ,
      function(data){
          window.location.href = 
           serverContext + "login?message=" + data.message;
    })
    .fail(function(data) {
    	if(data.responseJSON.error.indexOf("MailError") > -1)
        {
            window.location.href = serverContext + "emailError.html";
        }
        else{
            window.location.href = 
              serverContext + "login?message=" + data.responseJSON.message;
        }
    });
}

</script>
</body>
</html>

Bây giờ chúng ta cần liên kết đến trang ‘đặt lại mật khẩu’ mới này từ trang đăng nhập:

<a th:href="@{/forgetPassword.html}" 
  th:text="#{message.resetPassword}">reset</a>

5.Khởi tạo PasswordResetToken

Hãy bắt đầu bằng cách tạo PasswordResetToken mới và gửi nó qua email cho người dùng:

@PostMapping("/user/resetPassword")
public GenericResponse resetPassword(HttpServletRequest request, 
  @RequestParam("email") String userEmail) {
    User user = userService.findUserByEmail(userEmail);
    if (user == null) {
        throw new UserNotFoundException();
    }
    String token = UUID.randomUUID().toString();
    userService.createPasswordResetTokenForUser(user, token);
    mailSender.send(constructResetTokenEmail(getAppUrl(request), 
      request.getLocale(), token, user));
    return new GenericResponse(
      messages.getMessage("message.resetPasswordEmail", null, 
      request.getLocale()));
}

Và đây là phương thức createPasswordResetTokenForUser():

public void createPasswordResetTokenForUser(User user, String token) {
    PasswordResetToken myToken = new PasswordResetToken(token, user);
    passwordTokenRepository.save(myToken);
}

Và đây là phương thức constructResetTokenEmail() – dùng để gửi email với reset token:

private SimpleMailMessage constructResetTokenEmail(
  String contextPath, Locale locale, String token, User user) {
    String url = contextPath + "/user/changePassword?token=" + token;
    String message = messages.getMessage("message.resetPassword", 
      null, locale);
    return constructEmail("Reset Password", message + " \r\n" + url, user);
}

private SimpleMailMessage constructEmail(String subject, String body, 
  User user) {
    SimpleMailMessage email = new SimpleMailMessage();
    email.setSubject(subject);
    email.setText(body);
    email.setTo(user.getEmail());
    email.setFrom(env.getProperty("support.email"));
    return email;
}

Lưu ý cách chúng tôi sử dụng một đối tượng đơn giản GenericResponse để thể hiện phản hồi của chúng tôi với khách hàng:

public class GenericResponse {
    private String message;
    private String error;
 
    public GenericResponse(String message) {
        super();
        this.message = message;
    }
 
    public GenericResponse(String message, String error) {
        super();
        this.message = message;
        this.error = error;
    }
}

6. Kiểm tra PasswordResetToken

Khi người dùng nhấp vào liên kết trong email của họ user/changePassword:

  • xác minh rằng mã thông báo là hợp lệ

  • giới thiệu cho người dùng trang updatePassword, nơi họ có thể nhập mật khẩu mới

Mật khẩu mới và mã thông báo sau đó được chuyển đến user/savePassword:

Người dùng nhận được email có liên kết duy nhất để đặt lại mật khẩu của họ và nhấp vào liên kết:

@GetMapping("/user/changePassword")
public String showChangePasswordPage(Locale locale, Model model, 
  @RequestParam("token") String token) {
    String result = securityService.validatePasswordResetToken(token);
    if(result != null) {
        String message = messages.getMessage("auth.message." + result, null, locale);
        return "redirect:/login.html?lang=" 
            + locale.getLanguage() + "&message=" + message;
    } else {
        model.addAttribute("token", token);
        return "redirect:/updatePassword.html?lang=" + locale.getLanguage();
    }
}

Và đây là phương thức validatePasswordResetToken():

public String validatePasswordResetToken(String token) {
    final PasswordResetToken passToken = passwordTokenRepository.findByToken(token);

    return !isTokenFound(passToken) ? "invalidToken"
            : isTokenExpired(passToken) ? "expired"
            : null;
}

private boolean isTokenFound(PasswordResetToken passToken) {
    return passToken != null;
}

private boolean isTokenExpired(PasswordResetToken passToken) {
    final Calendar cal = Calendar.getInstance();
    return passToken.getExpiryDate().before(cal.getTime());
}

7. Thay đổi mật khẩu

Tại thời điểm này, người dùng thấy trang Đặt lại mật khẩu đơn giản - trong đó tùy chọn duy nhất có thể là cung cấp mật khẩu mới:

7.1. updatePassword.html

<html>
<body>
<div sec:authorize="hasAuthority('CHANGE_PASSWORD_PRIVILEGE')">
    <h1 th:text="#{message.resetYourPassword}">reset</h1>
    <form>
        <label th:text="#{label.user.password}">password</label>
        <input id="password" name="newPassword" type="password" value="" />

        <label th:text="#{label.user.confirmPass}">confirm</label>
        <input id="matchPassword" type="password" value="" />

        <label th:text="#{token.message}">token</label>
        <input id="token" name="token" value="" />

        <div id="globalError" style="display:none" 
          th:text="#{PasswordMatches.user}">error</div>
        <button type="submit" onclick="savePass()" 
          th:text="#{message.updatePassword}">submit</button>
    </form>
               
<script th:inline="javascript">
var serverContext = [[@{/}]];
$(document).ready(function () {
    $('form').submit(function(event) {
        savePass(event);
    });
    
    $(":password").keyup(function(){
        if($("#password").val() != $("#matchPassword").val()){
            $("#globalError").show().html(/*[[#{PasswordMatches.user}]]*/);
        }else{
            $("#globalError").html("").hide();
        }
    });
});

function savePass(event){
    event.preventDefault();
    if($("#password").val() != $("#matchPassword").val()){
        $("#globalError").show().html(/*[[#{PasswordMatches.user}]]*/);
        return;
    }
    var formData= $('form').serialize();
    $.post(serverContext + "user/savePassword",formData ,function(data){
        window.location.href = serverContext + "login?message="+data.message;
    })
    .fail(function(data) {
        if(data.responseJSON.error.indexOf("InternalError") > -1){
            window.location.href = serverContext + "login?message=" + data.responseJSON.message;
        }
        else{
            var errors = $.parseJSON(data.responseJSON.message);
            $.each( errors, function( index,item ){
                $("#globalError").show().html(item.defaultMessage);
            });
            errors = $.parseJSON(data.responseJSON.error);
            $.each( errors, function( index,item ){
                $("#globalError").show().append(item.defaultMessage+"<br/>");
            });
        }
    });
}
</script>    
</div>
</body>
</html>

Lưu ý rằng chúng tôi hiển thị mã thông báo đặt lại và chuyển nó dưới dạng tham số POST trong lệnh gọi sau để lưu mật khẩu.

7.2. Lưu mật khẩu

Cuối cùng, khi yêu cầu bài viết trước được gửi - mật khẩu người dùng mới được lưu:

@PostMapping("/user/savePassword")
public GenericResponse savePassword(final Locale locale, @Valid PasswordDto passwordDto) {

    String result = securityUserService.validatePasswordResetToken(passwordDto.getToken());

    if(result != null) {
        return new GenericResponse(messages.getMessage(
            "auth.message." + result, null, locale));
    }

    Optional user = userService.getUserByPasswordResetToken(passwordDto.getToken());
    if(user.isPresent()) {
        userService.changeUserPassword(user.get(), passwordDto.getNewPassword());
        return new GenericResponse(messages.getMessage(
            "message.resetPasswordSuc", null, locale));
    } else {
        return new GenericResponse(messages.getMessage(
            "auth.message.invalid", null, locale));
    }
}

Và đây là phương thức ChangeUserPassword():

public void changeUserPassword(User user, String password) {
    user.setPassword(passwordEncoder.encode(password));
    repository.save(user);
}

và PasswordDto:

public class PasswordDto {

    private String oldPassword;

    private  String token;

    @ValidPassword
    private String newPassword;
}

8.Tham khảo

reset

🔐
🔂
Spring Security – Reset Your Password