這篇文章是系列的一部分:
• 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}">表單</h1>
<form action="/" th:object="${user}" method="POST" enctype="utf8">
<div>
<label th:text="#{label.user.firstName}">名</label>
<input th:field="*{firstName}"/>
<p th:each="error: ${#fields.errors('firstName')}"
th:text="${error}">驗證錯誤</p>
</div>
<div>
<label th:text="#{label.user.lastName}">姓</label>
<input th:field="*{lastName}"/>
<p th:each="error : ${#fields.errors('lastName')}"
th:text="${error}">驗證錯誤</p>
</div>
<div>
<label th:text="#{label.user.email}">郵箱</label>
<input type="email" th:field="*{email}"/>
<p th:each="error : ${#fields.errors('email')}"
th:text="${error}">驗證錯誤</p>
</div>
<div>
<label th:text="#{label.user.password}">密碼</label>
<input type="password" th:field="*{password}"/>
<p th:each="error : ${#fields.errors('password')}"
th:text="${error}">驗證錯誤</p>
</div>
<div>
<label th:text="#{label.user.confirmPass}">確認</label>
<input type="password" th:field="*{matchingPassword}"/>
</div>
<button type="submit" th:text="#{label.form.submit}">提交</button>
</form>
<a th:href="@{/login.html}" th:text="#{label.form.loginLink}">登錄</a>
</body>
</html>
3. 用户 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;
// 標準的 getter 和 setter
}
請注意,我們使用了標準的 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 對象,該對象支持 註冊 表單,將其綁定並返回。
接下來,我們將研究控制器在註冊新帳户時執行的驗證:
所有必需字段已填寫(沒有空或空字段)。
電子郵件地址有效(格式正確)。
密碼確認字段與密碼字段匹配。
帳户尚不存在。
對於簡單的檢查,我們將使用 DTO 對象上的內置 Bean 驗證註解,例如 、 等。
然後,為了觸發驗證過程,我們將使用 標註對象在控制器層。
public ModelAndView registerUserAccount(@ModelAttribute("user") @Valid UserDto userDto,
HttpServletRequest request, Errors errors) {
//...
}
然後我們將驗證電子郵件地址並確保其格式正確。為此,我們將構建一個 ,以及一個 我們將其稱為 。
重要的是要注意,我們正在自己構建自定義註解,而不是使用 Hibernate 的 ,因為 Hibernate 認為舊的內部地址格式 是有效的(參見 Stackoverflow 文章),這並不好。
因此,以下是電子郵件驗證註解和自定義驗證器:
@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 {};
}
請注意,我們已將註解定義為 級別,因為這是它在概念上適用的位置。
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();
}
}
然後我們將 在我們的 實現上:
@ValidEmail
@NotNull
@NotEmpty
private String email;
我們還需要一個自定義註解和驗證器,以確保 和 字段匹配:
@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 {};
}
請注意, 註解指示這是一個 級別的註解。這是因為我們需要整個 對象來執行驗證。
將調用此註解的自定義驗證器如下所示:
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
public class UserDto {
//...
}
所有自定義驗證都將與所有標準註解一起評估,在整個驗證過程運行期間。
第四個檢查將是驗證 帳户是否已經在數據庫中存在。
此操作在表單驗證後執行,並且藉助 對象。
@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
}
@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 依賴 類來檢查給定電子郵件地址是否已存在於數據庫中的。
類的實際實現在持久層中並不相關,但有一種快速的方法是使用 Spring Data 生成存儲庫層。
6. 保持數據持久化與流程完成 接下來,我們將實現控制層中的註冊邏輯:
示例 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", "一個用户名/郵箱已經存在。");
return mav;
}
return new ModelAndView("successRegister", "user", userDto);
}
代碼中需要注意的事項:
控制器返回 ModelAndView 對象,這是方便的類,用於將模型數據(user )與視圖關聯。
如果驗證時間設置了任何錯誤,控制器將重定向到註冊表單。
7. UserService 中的 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
我們將從自定義的用户詳細信息服務實現開始:
@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 配置中啓用新的用户服務,我們只需要在UserDetailsService 內部添加對用户詳細信息服務的引用,並添加UserDetailsService bean:
示例 8.2. 身份管理器和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 註冊系列
0 位用戶收藏了這個故事!