知識庫 / Spring / Spring Security RSS 訂閱

Spring Security 註冊流程

Spring Security
HongKong
5
03:01 PM · Dec 06 ,2025
該文章是系列的一部分
• Spring Security 註冊系列
• 使用 Spring Security 的註冊流程 (當前文章)
• 註冊 – 通過電子郵件激活新賬户
• Spring Security 註冊 – 發送驗證郵件
• Spring Security 註冊 – 密碼編碼
• 註冊 API 變為 RESTful
• Spring Security – 重置您的密碼
• 註冊 – 密碼強度和規則
• 更新您的密碼
• 通知用户從新設備或位置登錄

1. 概述

在本教程中,我們將實現一個基本的註冊流程,並結合 Spring Security。我們將在此基礎上擴展我們之前文章中探討的登錄概念。我們之前文章中,我們探討了登錄過程。

目標是添加一個完整的註冊流程,允許用户註冊,並驗證和持久化用户數據。

2. 註冊頁面

首先,我們將實現一個簡單的註冊頁面,顯示以下字段:

  • 姓名 (名字和姓氏)
  • 郵箱
  • 密碼 (以及密碼確認字段)

以下示例顯示了一個簡單的 registration.html 頁面:

示例 2.1.

<html>
<body>
<h1 th:text="#{label.form.title}">form</h1>
<form action="/" th:object="${user}" method="POST" enctype="utf8">
    <div>
        <label th:text="#{label.user.firstName}">first</label>
        <input th:field="*{firstName}"/>
        <p th:each="error: ${#fields.errors('firstName')}" 
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.lastName}">last</label>
        <input th:field="*{lastName}"/>
        <p th:each="error : ${#fields.errors('lastName')}" 
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.email}">email</label>
        <input type="email" th:field="*{email}"/>
        <p th:each="error : ${#fields.errors('email')}" 
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.password}">password</label>
        <input type="password" th:field="*{password}"/>
        <p th:each="error : ${#fields.errors('password')}" 
          th:text="${error}">Validation error</p>
    </div>
    <div>
        <label th:text="#{label.user.confirmPass}">confirm</label>
        <input type="password" th:field="*{matchingPassword}"/>
    </div>
    <button type="submit" th:text="#{label.form.submit}">submit</button>
</form>

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

3. 用户 DTO 對象

我們需要一個 數據傳輸對象 (DTO) 將所有註冊信息發送到我們的 Spring 後端。該 DTO 對象應包含我們在創建和填充我們的 User 對象時所需的所有信息:

public class UserDto {
    @NotNull
    @NotEmpty
    private String firstName;
    
    @NotNull
    @NotEmpty
    private String lastName;
    
    @NotNull
    @NotEmpty
    private String password;
    private String matchingPassword;
    
    @NotNull
    @NotEmpty
    private String email;
    
    // standard getters and setters
}

請注意,我們使用了標準 javax.validation 聲明在 DTO 對象字段上的註解。稍後,我們還將 實現自定義驗證註解,以驗證電子郵件格式,以及密碼確認(參見 第 5 章)。

4. 註冊控制器

一個登錄頁面的註冊鏈接會將用户引導到註冊頁面。該頁面的後端邏輯位於註冊控制器中,並映射到 “/user/registration”

示例 4.1. showRegistration 方法

@GetMapping("/user/registration")
public String showRegistrationForm(WebRequest request, Model model) {
    UserDto userDto = new UserDto();
    model.addAttribute("user", userDto);
    return "registration";
}

當控制器接收到請求“/user/registration”,它會創建新的 UserDto 對象,該對象支持 registration 表單,將其綁定並返回。

5. 驗證註冊數據

接下來,我們將探討控制器在註冊新帳户時將執行的驗證:

  1. 所有必填字段已填寫(無空或空值字段)。
  2. 電子郵件地址有效(格式正確)。
  3. 密碼確認字段與密碼字段匹配。
  4. 帳户不存在。

5.1. 內置驗證

對於簡單的檢查,我們將使用 DTO 對象上的內置 Bean 驗證註解,例如 @NotNull@NotEmpty 等。

然後,為了觸發驗證過程,我們只需在控制器層使用 @Valid 註解來標記對象:

public ModelAndView registerUserAccount(@ModelAttribute("user") @Valid UserDto userDto,
  HttpServletRequest request, Errors errors) {
    //...
}

5.2. 自定義驗證以檢查電子郵件有效性

接下來,我們將驗證電子郵件地址並確保其格式正確。為此,我們將構建一個自定義驗證器,以及一個自定義驗證註釋,我們將其命名為@ValidEmail

需要注意的是,我們正在自己構建自定義註釋而不是Hibernate的@Email,因為Hibernate認為舊內網地址格式myaddress@myserver有效(參見Stackoverflow文章),這並不理想。

因此,以下是電子郵件驗證註釋和自定義驗證器:

示例 5.2.1. 電子郵件驗證的自定義註釋

@Target({TYPE, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = EmailValidator.class)
@Documented
public @interface ValidEmail {
    String message() default "Invalid email";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

請注意,我們已在 FIELD 級別定義了標註,因為這正是其概念應用的位置。

示例 5.2.2. 自定義 EmailValidato 器:

public class EmailValidator 
  implements ConstraintValidator<ValidEmail, String> {
    
    private Pattern pattern;
    private Matcher matcher;
    private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-+]+
        (.[_A-Za-z0-9-]+)*@" + "[A-Za-z0-9-]+(.[A-Za-z0-9]+)*
        (.[A-Za-z]{2,})$"; 
    @Override
    public void initialize(ValidEmail constraintAnnotation) {
    }
    @Override
    public boolean isValid(String email, ConstraintValidatorContext context){
        return (validateEmail(email));
    } 
    private boolean validateEmail(String email) {
        pattern = Pattern.compile(EMAIL_PATTERN);
        matcher = pattern.matcher(email);
        return matcher.matches();
    }
}

然後,我們將使用新的標註在我們的 UserDto 實現中:

@ValidEmail
@NotNull
@NotEmpty
private String email;

5.3. 使用自定義驗證以進行密碼確認

我們需要自定義標註和驗證器,以確保 密碼matchingPassword 字段相匹配:

示例 5.3.1. 自定義標註以驗證密碼確認

@Target({TYPE,ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordMatchesValidator.class)
@Documented
public @interface PasswordMatches {
    String message() default "Passwords don't match";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

請注意,@Target 註解表明這是一個TYPE 級別的註解。因為我們需要整個UserDto 對象來進行驗證。

以下是該註解調用的自定義驗證器:

示例 5.3.2. PasswordMatchesValidator 自定義驗證器

public class PasswordMatchesValidator
  implements ConstraintValidator<PasswordMatches, Object> {
    
    @Override
    public void initialize(PasswordMatches constraintAnnotation) {
    }
    @Override
    public boolean isValid(Object obj, ConstraintValidatorContext context){
        UserDto user = (UserDto) obj;
        return user.getPassword().equals(user.getMatchingPassword());
    }
}

然後,應將 PasswordMatches 註解應用於我們的 UserDto 對象:

@PasswordMatches
public class UserDto {
    //...
}

所有自定義驗證都將與所有標準註解一同評估,在整個驗證流程運行期間。

5.4. 確認賬户不存在

我們接下來要實現的功能是驗證賬户是否已存在。具體來説,我們驗證郵箱賬户是否已存在於數據庫中。

這在表單驗證完成後執行,並藉助 UserService 對象的實現。

示例 5.4.1. 控制器方法 registerUserAccount 調用 UserService 對象

@PostMapping("/user/registration")
public ModelAndView registerUserAccount(
  @ModelAttribute("user") @Valid UserDto userDto,
  HttpServletRequest request,
  Errors errors) {
    
    try {
        User registered = userService.registerNewUserAccount(userDto);
    } catch (UserAlreadyExistException uaeEx) {
        mav.addObject("message", "An account for that username/email already exists.");
        return mav;
    }

    // rest of the implementation
}

示例 5.4.2. 用户服務 檢查重複的電子郵件

@Service
@Transactional
public class UserService implements IUserService {
    @Autowired
    private UserRepository repository;
    
    @Override
    public User registerNewUserAccount(UserDto userDto) throws UserAlreadyExistException {
        if (emailExists(userDto.getEmail())) {
            throw new UserAlreadyExistException("There is an account with that email address: "
              + userDto.getEmail());
        }

        // the rest of the registration operation
    }
    private boolean emailExists(String email) {
        return userRepository.findByEmail(email) != null;
    }
}

UserService 依賴 UserRepository 類來檢查給定電子郵件地址是否已存在於數據庫中。

持久層中 UserRepository 類的實際實現與本文檔無關;但是,一種快速的方法是使用 Spring Data 生成 Repository 層。

6. persisting data and finishing-up form processing

接下來,我們將實現控制層中的註冊邏輯:

示例 6.1. 控制器中的 RegisterAccount 方法

@PostMapping("/user/registration")
public ModelAndView registerUserAccount(
  @ModelAttribute("user") @Valid UserDto userDto,
  HttpServletRequest request,
  Errors errors) {
    
    try {
        User registered = userService.registerNewUserAccount(userDto);
    } catch (UserAlreadyExistException uaeEx) {
        mav.addObject("message", "An account for that username/email already exists.");
        return mav;
    }

    return new ModelAndView("successRegister", "user", userDto);
}

請注意以上代碼中的事項:

  1. 控制器返回一個 ModelAndView 對象,這是用於將模型數據(user)綁定到視圖的便捷類。
  2. 如果驗證時設置了任何錯誤,控制器將重定向到註冊表單。

7. UserService – 註冊操作

最後,我們將完成 UserService 中註冊操作的實現:

示例 7.1. IUserService 接口

public interface IUserService {
    User registerNewUserAccount(UserDto userDto);
}

示例 7.2:UserService

@Service
@Transactional
public class UserService implements IUserService {
    @Autowired
    private UserRepository repository;
    
    @Override
    public User registerNewUserAccount(UserDto userDto) throws UserAlreadyExistException {
        if (emailExists(userDto.getEmail())) {
            throw new UserAlreadyExistException("There is an account with that email address: "
              + userDto.getEmail());
        }

        User user = new User();
        user.setFirstName(userDto.getFirstName());
        user.setLastName(userDto.getLastName());
        user.setPassword(userDto.getPassword());
        user.setEmail(userDto.getEmail());
        user.setRoles(Arrays.asList("ROLE_USER"));

        return repository.save(user);
    }

    private boolean emailExists(String email) {
        return userRepository.findByEmail(email) != null;
    }
}

8. 為安全登錄加載用户詳情

在之前的文章中,登錄使用了硬編碼的憑據。我們將通過使用新註冊的用户信息和憑據來改變這一點。此外,我們還將實現一個自定義的UserDetailsService,用於從持久化層檢查登錄憑據。

8.1. 自定義 UserDetailsService

我們將從自定義 UserDetailsService 的實現開始:

@Service
@Transactional
public class MyUserDetailsService implements UserDetailsService {
 
    @Autowired
    private UserRepository userRepository;
    
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email);
        if (user == null) {
            throw new UsernameNotFoundException("No user found with username: " + email);
        }
        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;
        
        return new org.springframework.security.core.userdetails.User(
          user.getEmail(), user.getPassword(), enabled, accountNonExpired,
          credentialsNonExpired, accountNonLocked, getAuthorities(user.getRoles()));
    }
    
    private static List<GrantedAuthority> getAuthorities (List<String> roles) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }
}

8.2. 啓用新的身份驗證提供者

要啓用 Spring Security 配置中的新用户服務,只需在 <em authentication-manager</em> 元素中添加對 <em UserDetailsService</em> 的引用,並添加 <em UserDetailsService</em> Bean 即可。

示例 8.2:身份管理器和 <em UserDetailsService

<authentication-manager>
    <authentication-provider user-service-ref="userDetailsService" />
</authentication-manager>
 
<beans:bean id="userDetailsService" class="com.baeldung.security.MyUserDetailsService" />

通過 Java 配置也是一種選擇:

@Autowired
private MyUserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);
}

9. 結論

在本文中,我們演示了使用 Spring Security 和 Spring MVC 實現的完整且接近生產環境的註冊流程。接下來,我們將討論如何通過驗證新用户的電子郵件來激活新註冊的賬户。

下一頁
註冊 – 通過電子郵件激活新賬户
上一頁
Spring Security 註冊系列
user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.