Spring Boot реализует весь процесс проверки регистрации пользователя

Spring Boot задняя часть
Spring Boot реализует весь процесс проверки регистрации пользователя

0. Прочитав эту статью, вы узнаете

  • Как реализовать базовый процесс проверки регистрации
  • Как настроить аннотацию

1 Обзор

В этой статье мы будем использовать Spring Boot для реализации простого процесса регистрации и проверки учетной записи электронной почты.

Наша цель — добавить полный процесс регистрации, который позволит пользователям регистрироваться, аутентифицировать и сохранять пользовательские данные.

2. Создайте пользовательский объект DTO

Во-первых, нам нужен 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 {

    @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, не может быть нулевым, но может быть пустым набором (размер = 0).

@NotEmpty:Применяется к объектам CharSequence, Collection, Map и Array, не может быть нулевым, а размер связанного объекта больше 0.
@NotBlank:Эту аннотацию можно использовать только для типов String. Строка не является нулевой, а усеченная длина больше 0 после удаления символов пробела на обоих концах.

В следующих разделах мы также настроим аннотации для проверки формата адреса электронной почты и подтверждения дополнительных паролей.

3. Внедрить зарегистрированный контроллер

Ссылка для регистрации на странице входа ведет пользователя на страницу регистрации:

Пример 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, привязывает его к модели и возвращает страницу регистрации.registration.html.

Объект модели отвечает за передачу данных между контроллером и представлением, которое представляет данные.

На самом деле данные, помещенные в атрибуты модели, будут скопированы в атрибуты ответа сервлета, чтобы представление могло найти их там.

В широком смысле под Моделью понимается M в среде MVC, то есть Модель. В узком смысле Model — это коллекция ключей и значений.

4. Проверьте регистрационные данные

Далее рассмотрим валидацию, которую будет выполнять контроллер при регистрации новой учетной записи:

  • Все обязательные поля заполнены, пустых полей нет.
  • Адрес электронной почты действителен
  • Поле подтверждения пароля совпадает с полем пароля
  • Аккаунт не существует

4.1 Встроенная аутентификация

Для простых проверок мы будем использовать @NotBlank для проверки объектов DTO.

Чтобы запустить процесс проверки, мы проверим объект с помощью аннотации @Valid в контроллере.

Пример 4.1. Регистрация учетной записи пользователя

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

4.2 Пользовательская проверка для проверки действительности электронной почты

Затем давайте проверим адрес электронной почты, чтобы убедиться, что он имеет правильный формат. Для этого мы создадим собственный валидатор вместе с пользовательской аннотацией проверки --IsEmailValid.

Ниже приведены примечания для проверки электронной почты.IsEmailValidи собственный валидаторEmailValidator:

Почему бы не использовать встроенный в Hibernate@Email?

Потому что в спящем режиме@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);

    @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.

@NotBlank
@IsEmailVaild
private String email;

4.3 Используйте пользовательскую аутентификацию для подтверждения пароля

Нам также нужна пользовательская аннотация и валидатор, чтобы гарантироватьUserDtoсерединаpasswordиrepeatedPasswordполя совпадают.

Пример 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Аннотация указывает, что это аннотация уровня типа. Это потому, что нам нужен весь объект 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> {

    @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.

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

4.4 Проверьте, существует ли уже учетная запись

Четвертая проверка, которую мы хотим реализовать, — убедиться, что учетная запись электронной почты уже существует в базе данных.

Это делается после проверки формы, и мы помещаем эту проверку в UserService.

Пример 4.4.1 Пользовательская служба

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Добавлено на уровне службы вместо уровня DAO?

Если наша аннотация транзакции@TransactionalЕсли он добавляется на уровень DAO, пока выполняются добавление, удаление и изменение, транзакция должна быть отправлена, поэтому характеристики транзакции не могут быть задействованы, особенно согласованность транзакции. Когда возникает проблема параллелизма, данные, которые пользователь находит в базе данных, будут необъективными.

В общем, наш сервисный уровень может вызывать несколько уровней DAO, нам нужно только добавить аннотацию транзакции к сервисному уровню.@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, который может хранить данные и возвращать представление.

Три распространенных варианта использования 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Начинать.

Пример 6.1.1 MyUserDetailsService

@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 Включить нового поставщика аутентификации

Затем, для того, чтобы действительно иметь возможность включить пользовательскийMyUserDetailsService, нам тоже нужноSecurityConfigДобавьте следующий код в файл конфигурации:

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

Из-за ограниченности места мы не будем здесь подробно останавливатьсяSecurityConfigконфигурационный файл.

7. Заключение

На данный момент мы завершили базовый процесс регистрации пользователя, реализованный Spring Boot. Страницы и некоторые классы в проекте не отражены в статье.Друзья кому нужно могут подписаться на мой официальный аккаунт花园野人,ОтветитьzhuceПолучите код проекта.