0. 阅读完本文你将会学会

  • 如何实现一个基本的注册验证过程
  • 如何自定义一个注解

1. 概述

在这篇文章中,我们将使用Spring Boot实现一个基本的邮箱注册账户以及验证的过程。

我们的目标是添加一个完整的注册过程,允许用户注册,验证,并持久化用户数据。

2. 创建User DTO Object

首先,我们需要一个DTO来囊括用户的注册信息。这个对象应该包含我们在注册和验证过程中所需要的基本信息。

例2.1 UserDto的定义

package com.savagegarden.web.dto;import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;public class UserDto {@NotBlankprivate String username;@NotBlankprivate String password;@NotBlankprivate String repeatedPassword;@NotBlankprivate String email;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getRepeatedPassword() {return repeatedPassword;}public void setRepeatedPassword(String repeatedPassword) {this.repeatedPassword = repeatedPassword;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}
}

请注意我们在DTO对象的字段上使用了标准的javax.validation注解——@NotBlank

@NotBlank@NotEmpty@NotNull的区别

@NotNull: 适用于CharSequence, Collection, Map 和 Array 对象,不能是null,但可以是空集(size = 0)。
@NotEmpty: 适用于CharSequence, Collection, Map 和 Array 对象,不能是null并且相关对象的size大于0。
@NotBlank: 该注解只能作用于String类型。String非null且去除两端空白字符后的长度(trimmed length)大于0。

在下面的章节里,我们还将自定义注解来验证电子邮件地址的格式以及确认二次密码。

3. 实现一个注册Controller

登录页面上的注册链接将用户带到注册页面:

例3.1 RegistrationController的定义

package com.savagegarden.web.controller;import com.savagegarden.web.dto.UserDto;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;@Controller
public class RegistrationController {@GetMapping("/user/registration")public String showRegistrationForm(Model model) {model.addAttribute("user", new UserDto());return "registration";}}

RegistrationController收到请求/user/registration时,它创建了新的UserDto对象,将其绑定在Model上,并返回了注册页面registration.html

Model 对象负责在控制器Controller和展现数据的视图View之间传递数据。

实际上,放到 Model 属性中的数据将会复制到 Servlet Response 的属性中,这样视图就能在这里找到它们了。

从广义上来说,Model 指的是 MVC框架 中的 M,即 Model(模型)。从狭义上讲,Model 就是个 key-value 集合。

4. 验证注册数据

接下来,让我们看看控制器在注册新账户时将执行的验证:

  • 所有必须填写的字段都已填写且没有空字段
  • 该电子邮件地址是有效的
  • 密码确认字段与密码字段相符
  • 该账户不存在

4.1 内置的验证

对于简单的检查,我们将使用@NotBlank来验证DTO对象。

为了触发验证过程,我们将在Controller中用@Valid注解来验证对象。

例4.1 registerUserAccount

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

4.2 自定义验证以检查电子邮件的有效性

下一步,让我们验证电子邮件地址,以保证它的格式是正确的。我们将为此建立一个自定义验证器,以及一个自定义验证注解–IsEmailValid

下面是电子邮件验证注解IsEmailValid和自定义验证器EmailValidator:

为什么不使用Hibernate内置的@Email?

因为Hibernate中的@Email会验证通过XXX@XXX之类的邮箱,其实这是不符合规定的。

感兴趣的读者朋友可以移步此处Hibernate validator: @Email accepts ask@stackoverflow as valid?。

例4.2.1 IsEmailVaild注解的定义

package com.savagegarden.validation;import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;import javax.validation.Constraint;
import javax.validation.Payload;@Target({ TYPE, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = EmailValidator.class)
@Documented
public @interface IsEmailVaild {String message() default "Invalid Email";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}

@Target的作用是说明了该注解所修饰的对象范围

@Retention的作用是说明了被它所注解的注解保留多久

@Constraint的作用是说明自定义注解的方法

@Documented的作用是说明了被这个注解修饰的注解可以被例如javadoc此类的工具文档化

关于如何自定义一个Java Annotation,感兴趣的朋友可以看看我的另一篇文章。

例4.2.2 EmailValidator的定义

package com.savagegarden.validation;import java.util.regex.Matcher;
import java.util.regex.Pattern;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;public class EmailValidator implements ConstraintValidator<IsEmailVaild, String> {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,})$";private static final Pattern PATTERN = Pattern.compile(EMAIL_PATTERN);@Overridepublic void initialize(IsEmailVaild constraintAnnotation) {}@Overridepublic boolean isValid(final String username, final ConstraintValidatorContext context) {return (validateEmail(username));}private boolean validateEmail(final String email) {Matcher matcher = PATTERN.matcher(email);return matcher.matches();}
}

现在让我们在我们的UserDto实现上使用新注解。

@NotBlank
@IsEmailVaild
private String email;

4.3 使用自定义验证来确认密码

我们还需要一个自定义注解和验证器,以确保UserDto中的passwordrepeatedPassword字段相匹配。

例4.3.1 IsPasswordMatching注解的定义

package com.savagegarden.validation;import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;import javax.validation.Constraint;
import javax.validation.Payload;@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordMatchingValidator.class)
@Documented
public @interface IsPasswordMatching {String message() default "Passwords don't match";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}

请注意,@Target注解表明这是一个Type级别的注解。这是因为我们需要整个UserDto对象来执行验证。

例4.3.2 PasswordMatchingValidator的定义

package com.savagegarden.validation;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;import com.savagegarden.web.dto.UserDto;public class PasswordMatchingValidator implements ConstraintValidator<IsPasswordMatching, Object> {@Overridepublic void initialize(final IsPasswordMatching constraintAnnotation) {//}@Overridepublic boolean isValid(final Object obj, final ConstraintValidatorContext context) {final UserDto user = (UserDto) obj;return user.getPassword().equals(user.getRepeatedPassword());}}

现在,将@IsPasswordMatching注解应用到我们的UserDto对象。

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

4.4 检查该账户是否已经存在

我们要实现的第四个检查是验证该电子邮件帐户在数据库中是否已经存在。

这是在表单被验证后进行的,我们把这项验证放在了UserService。

例4.4.1 UserService

package com.savagegarden.service.impl;import com.savagegarden.error.user.UserExistException;
import com.savagegarden.persistence.dao.UserRepository;
import com.savagegarden.persistence.model.User;
import com.savagegarden.service.IUserService;
import com.savagegarden.web.dto.UserDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;import javax.transaction.Transactional;@Service
@Transactional
public class UserService implements IUserService {@Autowiredprivate UserRepository userRepository;@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic User registerNewUserAccount(UserDto userDto) throws UserExistException {if (hasEmailExisted(userDto.getEmail())) {throw new UserExistException("The email has already existed: "+ userDto.getEmail());}User user = new User();user.setUsername(userDto.getUsername());user.setPassword(passwordEncoder.encode(userDto.getPassword()));user.setEmail(userDto.getEmail());return userRepository.save(user);}private boolean hasEmailExisted(String email) {return userRepository.findByEmail(email) != null;}
}

使用@Transactional开启事务注解,至于为什么@Transactional加在Service层而不是DAO层?

如果我们的事务注解@Transactional加在DAO层,那么只要做增删改,就要提交一次事务,那么事务的特性就发挥不出来,尤其是事务的一致性。当出现并发问题的时候,用户从数据库查到的数据都会有所偏差。

一般的时候,我们的Service层可以调用多个DAO层,我们只需要在Service层加一个事务注解@Transactional,这样我们就可以一个事务处理多个请求,事务的特性也会充分地发挥出来。

UserService依靠UserRepository类来检查数据库中是否已存在拥有相同邮箱的用户账户。当然在本文中我们不会涉及到UserRepository的实现。

5. 持久化处理

然后我们继续实现RegistrationController中的持久化逻辑。

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

在上面的代码中我们可以发现:

  1. 我们创建了ModelAndView对象,该对象既可以保存数据也可以返回一个View。

常见的ModelAndView的三种用法

(1) new ModelAndView(String viewName, String attributeName, Object attributeValue);

(2) mav.setViewName(String viewName);

mav.addObejct(String attributeName, Object attributeValue);

(3) new ModelAndView(String viewName);

  1. 在注册的过程中如果产生任何报错,将会返回到注册页面。

6. 安全登录

在本节内容中,我们将实现一个自定义的UserDetailsService,从持久层检查登录的凭证。

6.1 自定义UserDetailsService

让我们从自定义UserDetailsService开始。

例6.1.1 MyUserDetailsService

@Service
@Transactional
public class MyUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Overridepublic 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().toLowerCase(), 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;}
}

6.2 开启New Authentication Provider

然后,为了真正地能够开启自定义的MyUserDetailsService,我们还需要在SecurityConfig配置文件中加入以下代码:

@Overrideprotected void configure(final AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(authProvider());}

限于篇幅,我们就不在这里详细展开SecurityConfig配置文件。

7. 结语

至此我们完成了一个由Spring Boot实现的基本的用户注册过程。项目中的页面以及部分类没有在文章中体现,需要的小伙伴可以关注我的公众号花园野人,回复zhuce获取项目代码。

Spring Boot实现注册验证全过程相关推荐

  1. Spring Boot 2.x 启动全过程源码分析(全)

    上篇<Spring Boot 2.x 启动全过程源码分析(一)入口类剖析>我们分析了 Spring Boot 入口类 SpringApplication 的源码,并知道了其构造原理,这篇我 ...

  2. Spring Boot 2.x 启动全过程源码分析(上)入口类剖析

    转载自   Spring Boot 2.x 启动全过程源码分析(上)入口类剖析 Spring Boot 的应用教程我们已经分享过很多了,今天来通过源码来分析下它的启动过程,探究下 Spring Boo ...

  3. Spring Boot SSL证书验证的问题

    在Spring Boot应用用启动时调用: private static void disableSslVerification() {try {SSLContext sslcontext = SSL ...

  4. Spring Boot 极验验证滑动验证码

    概要 基于极验验证官网 java版gt3-java-sdk改编,使用Spring Boot 整合的极验滑动验证,包含form表单登录和ajax登录两种情况. 目录 注册账户获取ID和KEY Demo源 ...

  5. spring boot + zookeeper 注册中心

    依赖配置 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-b ...

  6. Spring Boot之注册servlet三大组件

    由于Spring Boot默认是以jar包的形式启动嵌入式的Servlet容器来启动Spring Boot的web应用是,没有web.xml配置文件 注册三大组件用以下方式 ServletRegist ...

  7. Spring Boot快速注册服务脚本

    前言 Spring Boot项目通过JAR打包部署的时候,一般我们所采取的措施是将其注册为服务,并通过service命令管理项目.但注册服务的过程相对繁琐,不如写一个脚本来快速注册(入门Shell). ...

  8. spring boot+ geetest滑动验证

    geetest滑动验证 最近公司为了安全,在登录注册,发送短信等需要验证的地方改为用滑动验证,再此记录一下 一.注册账号极验官网 进去以后找到 ID和key值需要记住,代码中需要配置 二.下载demo ...

  9. spring boot 登录滑动验证

    1.配置pom.xml坐标 cloud.tianai.captcha 2.配置Listener package com.lw.utils;import cloud.tianai.captcha.sli ...

最新文章

  1. 获取Sql服务器列表 (C#)
  2. 免oracle客户端下载,Oracle免安装客户端
  3. 构造函数、实例、原型对象、继承
  4. [转载] 大型网站的 HTTPS 实践(一)—— HTTPS 协议和原理
  5. MySQL的NULL值
  6. SQL 批量修改订单号
  7. win7日历加入农历_还是农历更亲切,春节制作一个带农历的日历,欢欢喜喜过新年...
  8. 用Excel拟合imu温度趋势线
  9. 学习某一门技术的步骤(韩顺平老师提供)
  10. 用函数调用编写程序,函数的功能是:根据以下公式计算s,计算结果作为函数值返回;n通过形参传入。s=1+1/(1+2)+...+1/(1+2+3+...+n).)
  11. 社群发现算法--强连通和连通在关联图谱中的应用
  12. Python 防止死锁的方法
  13. 手撕promise.all以及promise.race
  14. MySQL条件查询练习题(含答案)
  15. Ristretto:面向硬件的卷积神经网络逼近
  16. 计算机网络化的例子,《计算机网络基础及典型案例》.pdf
  17. 【方案PCBA设计】LCD显示的蓝牙厨房电子秤方案
  18. 操作系统第六章笔记---CPU调度
  19. 奋斗吧,程序员——第三十三章 今朝此为别,何处还相遇
  20. 快速制作U盘启动盘和U盘安装盘的方法

热门文章

  1. 虚拟化之——内存虚拟化
  2. 自然资源部关于全面开展矿产资源规划(2021-2025年)编制工作的通知 自然资发〔2020〕43号
  3. C++包扩展_键盘中国免费提供下载:雅马哈PSRSX900、SX700第三版1.3G海量整合扩展音色、节奏包(含音频节奏)...
  4. 跳蚱蜢,青蛙跳杯子(python)
  5. 用x264和ffmpeg将YUV编码为.h264(1)
  6. 深度学习参数对模型的影响:Loss(损失)、方差、Precision(精确度)、Recall(召回率
  7. 完整过程解决 ERROR 1045 (28000): Access denied for user 'mysql'@'localhost' (using password: NO)
  8. Mysql聚集索引和非聚集索引(堆组织表和索引组织表)
  9. jquery点击获取当前图片地址
  10. 万网m3 windows linux切换,discuz 论坛 绑定万网m3虚拟主机子目录教程