Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring Boot实现用户注册验证全过程

Spring Boot实现用户注册验证全过程

作者头像
翊君
发布于 2022-03-08 06:45:27
发布于 2022-03-08 06:45:27
1.4K00
代码可运行
举报
文章被收录于专栏:周一电台周一电台
运行总次数:0
代码可运行

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

  • 如何实现一个基本的注册验证过程
  • 如何自定义一个注解1. 概述 在这篇文章中,我们将使用Spring Boot实现一个基本的邮箱注册账户以及验证的过程。

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

2. 创建User DTO Object

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

例2.1 UserDto的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.savagegarden.web.dto;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

public class UserDto {

    @NotBlank
    private String username;

    @NotBlank
    private String password;

    @NotBlank
    private String repeatedPassword;

    @NotBlank
    private 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的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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注解的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);

    @Override
    public void initialize(IsEmailVaild constraintAnnotation) {
    }

    @Override
    public 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实现上使用新注解。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@NotBlank
@IsEmailVaild
private String email;

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

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

例4.3.1 IsPasswordMatching注解的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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> {

    @Override
    public void initialize(final IsPasswordMatching constraintAnnotation) {
        //
    }

    @Override
    public boolean isValid(final Object obj, final ConstraintValidatorContext context) {
        final UserDto user = (UserDto) obj;
        return user.getPassword().equals(user.getRepeatedPassword());
    }

}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@IsPasswordMatching
public class UserDto {
    //...
}

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

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

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

例4.4.1 UserService

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public 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中的持久化逻辑。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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);
  2. 在注册的过程中如果产生任何报错,将会返回到注册页面。

6. 安全登录

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

6.1 自定义UserDetailsService

让我们从自定义UserDetailsService开始。

例6.1.1 MyUserDetailsService

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Service
@Transactional
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    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().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配置文件中加入以下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider());
    }

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

7. 结语

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022/01/10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
SpringBoot - 优雅的实现【业务校验】高级进阶
在开发中,为了保证接口的稳定安全,一般需要在接口逻辑中进行校验,比如 上面几篇都是 【参数校验】,一般我们都是使用Bean Validation校验框架。
小小工匠
2022/02/28
1.1K0
SpringBoot - 优雅的实现【业务校验】高级进阶
Spring Boot参数校验-简单有效的数据验证
了不起最近和一个前端实习生联调接口发现,参数校验确实给到前端展示和处理的诸多便利。
灬沙师弟
2023/10/09
5770
Spring Boot参数校验-简单有效的数据验证
Java中的参数验证(非Spring版)
1. Java中的参数验证(非Spring版) 1.1. 前言 为什么我总遇到这种非正常问题,我们知道很多时候我们的参数校验都是放在controller层的传入参数进行校验,我们常用的校验方式就是引入下列的jar包,在参数中添加@Validated,并对Bean对象的参数做不同的注解处理就行,对Spring这种常用做法大家应该比较熟了 但我现在遇到的需求,因为boss追求通用性,我们的controller入口只有一个,是通过传入参数中的不同tradeCode来区分调用哪个服务,这时我校验参数就得放到具体的每
老梁
2019/10/24
2.7K0
_一个简单完整的WEB系统
用户通过表单提交数据,存入MySQL数据库,提交成功后显示数据库中所有数据的列表。
会洗碗的CV工程师
2023/11/24
1710
_一个简单完整的WEB系统
Spring Boot 参数校验
如上图所示,默认会校验完所有属性,然后将错误信息一起返回,但很多时候不需要这样,一个校验失败了,其它就不必校验了
CBeann
2023/12/25
2300
Spring Boot 参数校验
一个简单完整的WEB系统
用户通过表单提交数据,存入MySQL数据库,提交成功后显示数据库中所有数据的列表。
会洗碗的CV工程师
2022/11/29
4700
一个简单完整的WEB系统
Java 21 与 Spring Boot 3.2 微服务开发从入门到精通实操指南
以下是结合Java 21及Spring Boot 3.2的最新特性编写的实操指南,包含完整的代码示例和详细的实现步骤:
啦啦啦191
2025/06/25
1010
Java 21 与 Spring Boot 3.2 微服务开发从入门到精通实操指南
Spring Boot – 使用 ModelMapper 将实体映射到 DTO
在企业应用中,我们使用RESTful服务来建立客户端和服务器之间的通信。总体思路是客户端将请求发送到服务器,服务器用一些响应来响应该请求。一般来说,我们大多数应用程序都具有三个不同的层:Web层、业务层和数据库层。这些层中的对象大多彼此不同。例如,Web层对象与数据库层中的同一对象完全不同。由于数据库对象可能包含 Web 层对象中不需要的字段,例如自动生成的字段、密码字段等。
用户1418987
2023/10/26
1.5K0
Spring Boot – 使用 ModelMapper 将实体映射到 DTO
这么写参数校验(validator)就不会被劝退了~
很痛苦遇到大量的参数进行校验,在业务中还要抛出异常或者不断的返回异常时的校验信息,在代码中相当冗长,充满了if-else这种校验代码,今天我们就来学习spring的javax.validation 注解式参数校验。
良月柒
2019/10/28
1.2K0
这么写参数校验(validator)就不会被劝退了~
Spring Boot 项目从入门到精通实操教程
下面我将基于Spring Boot 3.2和Java 17,使用最新的技术栈和最佳实践,为你提供一个完整的Spring Boot项目实操教程。
啦啦啦191
2025/06/14
5480
Spring Boot 项目从入门到精通实操教程
Spring学习笔记(9)一springMVC/boot全局异常处理和参数校验
我们使用springboot做 Restfull API,希望能全局处理异常,返回自定义错误码。类似:
黄规速
2022/04/14
9310
Spring学习笔记(9)一springMVC/boot全局异常处理和参数校验
Spring Boot 使用 JSR303 实现参数验证
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation。
程序员果果
2020/05/19
7500
Spring Boot 使用 JSR303 实现参数验证
spring boot 参数的过滤注解与实战
在Spring Boot应用中,对于入参的过滤,通常会涉及到对Web层的数据验证和处理。Spring Boot借助Spring框架提供了强大的验证框架支持,主要基于JSR-303/JSR-380(Bean Validation API)规范,以及Spring自身的@Valid或@Validated注解来实现请求参数的验证。以下是一些常见的使用案例来展示如何对参数进行过滤和验证。
小马哥学JAVA
2024/03/22
3530
开撸!SpringBoot-表单验证-统一异常处理-自定义验证信息源
我们都知道前台的验证只是为了满足界面的友好性、客户体验性等等。但是如果仅靠前端进行数据合法性校验,是远远不够的。因为非法用户可能会直接从客户端获取到请求地址进行非法请求,所以后台的校验是必须的;特别是应用如果不允许输入空值,对数据的合法性有要求的情况下。
IT大咖说
2021/09/08
2.6K0
开撸!SpringBoot-表单验证-统一异常处理-自定义验证信息源
使用双向 @OneToOne 注解避免 Spring Boot 中的 StackOverflowError
在使用 Java Spring Boot 开发过程中,实体之间的关系映射是一个非常常见的需求。为了便于理解,我们将介绍双向 @OneToOne 关系映射,以及如何避免由此产生的 StackOverflowError 问题。
繁依Fanyi
2024/08/20
3160
【详解】SpringBootValidator校验相关的注解信息
在开发Web应用时,数据校验是一个非常重要的环节。Spring Boot 提供了强大的校验机制,帮助开发者轻松实现对请求参数的校验。本文将详细介绍Spring Boot中常用的校验注解及其用法。
大盘鸡拌面
2025/01/11
3050
springboot ConstraintValidator的概念与用法
在 Java 中,ConstraintValidator 是用于自定义注解验证的接口,属于 Bean Validation(JSR 303 和 JSR 349)标准的一部分。这个接口定义了如何实施一个特定的约束注解的验证逻辑。
小马哥学JAVA
2024/04/26
1.4K0
基于 Spring Boot 框架开发 REST API 接口实战指南
以下是基于最新技术栈的Spring Boot REST API开发实操指南,涵盖从环境搭建到生产部署的全流程:
啦啦啦191
2025/07/03
1490
基于 Spring Boot 框架开发 REST API 接口实战指南
Springboot 校验器(Validator)
节选自《Netkiller Spring Cloud 手札》 3.7. 校验器(Validator) 常见的校验注解 @Null 被注释的元素必须为 null @NotNull 被注释的元素必须不为 null @AssertTrue 被注释的元素必须为 true @AssertFalse 被注释的元素必须为 false @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @DecimalMin(v
netkiller old
2020/05/07
2K0
SpringBoot项目结构
演示代码地址:kuizuo/spring-boot-demo (github.com)
愧怍
2022/12/27
1.4K1
SpringBoot项目结构
推荐阅读
相关推荐
SpringBoot - 优雅的实现【业务校验】高级进阶
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验