Процесс регистрации в Spring Security

Процесс регистрации в Spring Security

1. обзор

В этой статье мы реализуем базовый процесс регистрации в Spring Security. Это построено на основе концепций, рассмотренных вprevious article, где мы рассмотрели вход в систему.

Цель здесь - добавитьa full registration process, который позволяет пользователю регистрироваться, проверять и сохранять данные пользователя.

Дальнейшее чтение:

Поддержка Servlet 3 Async с Spring MVC и Spring Security

Краткое введение в поддержку Spring Security для асинхронных запросов в Spring MVC.

Read more

Весенняя безопасность с Thymeleaf

Краткое руководство по интеграции Spring Security и Thymeleaf

Read more

Spring Security - Заголовки контроля кэша

Руководство по управлению заголовками контроля HTTP-кэша с помощью Spring Security.

Read more

2. Страница регистрации

Во-первых, давайте реализуем простую страницу регистрации, отображающуюthe following fields:

  • name (имя и фамилия)

  • Эл. адрес

  • password (и поле подтверждения пароля)

В следующем примере показана простая страницаregistration.html:

Пример 2.1.



form

Validation error

Validation error

Validation error

Validation error

login

3. Пользовательский объект DTO

Нам нуженData Transfer Object для отправки всей регистрационной информации в наш бэкэнд 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. Позже мы также собираемся использоватьimplement our own custom validation annotations для проверки формата адреса электронной почты, а также для подтверждения пароля. (см.Section 5)

4. Контроллер регистрации

СсылкаSign-Up на страницеlogin приведет пользователя на страницуregistration. Этот бэкэнд для этой страницы находится в контроллере регистрации и отображается на“/user/registration”:

Пример 4.1. - МетодshowRegistration

@RequestMapping(value = "/user/registration", method = RequestMethod.GET)
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. Встроенная проверка

Для простых проверок мы будем использовать готовые аннотации проверки bean-компонентов для объекта DTO - такие аннотации, как@NotNull,@NotEmpty и т. Д.

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

public ModelAndView registerUserAccount(
  @ModelAttribute("user") @Valid UserDto accountDto,
  BindingResult result, WebRequest request, Errors errors) {
    ...
}

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

Далее - давайте проверим адрес электронной почты и убедимся, что он правильно сформирован. Мы собираемся построить для этогоcustom validator, а такжеcustom validation annotation - назовем это@ValidEmail.

Небольшое примечание: мы запускаем нашу собственную аннотациюinstead of Hibernate’s@Email, потому что Hibernate считает допустимым старый формат адресов внутренней сети:[email protected] (см. Статью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[] payload() default {};
}

Обратите внимание, что мы определили аннотацию на уровнеFIELD, поскольку именно здесь она применяется концептуально.

Example 5.2.2. – The Custom EmailValidator:

public class EmailValidator
  implements ConstraintValidator {

    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();
    }
}

Давайте теперьuse the new annotation в нашей реализацииUserDto:

@ValidEmail
@NotNull
@NotEmpty
private String email;

5.3. Использование пользовательской проверки для подтверждения пароля

Нам также нужны настраиваемые аннотации и валидатор, чтобы убедиться, что поляpassword и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[] payload() default {};
}

Обратите внимание, что аннотация@Target указывает, что это аннотация уровняTYPE. Это потому, что нам нужен весь объектUserDto для выполнения проверки.

Пользовательский валидатор, который будет вызываться этой аннотацией, показан ниже:

Пример 5.3.2. Пользовательский валидаторPasswordMatchesValidator

public class PasswordMatchesValidator
  implements ConstraintValidator {

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

Теперь к нашему объектуUserDto нужно применить аннотацию@PasswordMatches:

@PasswordMatches
public class UserDto {
   ...
}

Все пользовательские проверки, конечно, оцениваются вместе со всеми стандартными аннотациями при выполнении всего процесса проверки.

5.4. Убедитесь, что учетная запись уже не существует

Четвертая проверка, которую мы проведем, - это проверка того, что учетная записьemail еще не существует в базе данных.

Это выполняется после того, как форма была проверена, и это делается с помощью реализацииUserService.

Пример 5.4.1. - Метод контроллераcreateUserAccount вызывает объектUserService Object

@RequestMapping(value = "/user/registration", method = RequestMethod.POST)
public ModelAndView registerUserAccount
      (@ModelAttribute("user") @Valid UserDto accountDto,
      BindingResult result, WebRequest request, Errors errors) {
    User registered = new User();
    if (!result.hasErrors()) {
        registered = createUserAccount(accountDto, result);
    }
    if (registered == null) {
        result.rejectValue("email", "message.regError");
    }
    // rest of the implementation
}
private User createUserAccount(UserDto accountDto, BindingResult result) {
    User registered = null;
    try {
        registered = service.registerNewUserAccount(accountDto);
    } catch (EmailExistsException e) {
        return null;
    }
    return registered;
}

Пример 5.4.2. -UserService проверяет наличие повторяющихся писем

@Service
public class UserService implements IUserService {
    @Autowired
    private UserRepository repository;

    @Transactional
    @Override
    public User registerNewUserAccount(UserDto accountDto)
      throws EmailExistsException {

        if (emailExist(accountDto.getEmail())) {
            throw new EmailExistsException(
              "There is an account with that email adress: "
              +  accountDto.getEmail());
        }
        ...
        // the rest of the registration operation
    }
    private boolean emailExist(String email) {
        User user = repository.findByEmail(email);
        if (user != null) {
            return true;
        }
        return false;
    }
}

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

Теперь - фактическая реализацияUserRepository на уровне персистентности не имеет отношения к текущей статье. Один из быстрых способов - это, конечно,use Spring Data to generate the repository layer. __

6. Сохранение данных и завершающая обработка формы

Наконец, давайте реализуем логику регистрации на нашем уровне контроллера: __

Пример 6.1.1. - МетодRegisterAccount в контроллере

@RequestMapping(value = "/user/registration", method = RequestMethod.POST)
public ModelAndView registerUserAccount(
  @ModelAttribute("user") @Valid UserDto accountDto,
  BindingResult result,
  WebRequest request,
  Errors errors) {

    User registered = new User();
    if (!result.hasErrors()) {
        registered = createUserAccount(accountDto, result);
    }
    if (registered == null) {
        result.rejectValue("email", "message.regError");
    }
    if (result.hasErrors()) {
        return new ModelAndView("registration", "user", accountDto);
    }
    else {
        return new ModelAndView("successRegister", "user", accountDto);
    }
}
private User createUserAccount(UserDto accountDto, BindingResult result) {
    User registered = null;
    try {
        registered = service.registerNewUserAccount(accountDto);
    } catch (EmailExistsException e) {
        return null;
    }
    return registered;
}

Что нужно отметить в приведенном выше коде:

  1. Контроллер возвращает объектModelAndView, который является удобным классом для отправки данных модели (user), привязанных к представлению.

  2. Контроллер будет перенаправлять на регистрационную форму, если во время проверки будут установлены какие-либо ошибки.

  3. МетодcreateUserAccount вызываетUserService для сохранения данных. Мы обсудим реализациюUserService в следующем разделе.

7. UserService - Операция регистра

Закончим реализацию операции регистрации вUserService:

Пример 7.1. ИнтерфейсIUserService

public interface IUserService {
    User registerNewUserAccount(UserDto accountDto)
      throws EmailExistsException;
}

Пример 7.2. - КлассUserService

@Service
public class UserService implements IUserService {
    @Autowired
    private UserRepository repository;

    @Transactional
    @Override
    public User registerNewUserAccount(UserDto accountDto)
      throws EmailExistsException {

        if (emailExists(accountDto.getEmail())) {
            throw new EmailExistsException(
              "There is an account with that email address:  + accountDto.getEmail());
        }
        User user = new User();
        user.setFirstName(accountDto.getFirstName());
        user.setLastName(accountDto.getLastName());
        user.setPassword(accountDto.getPassword());
        user.setEmail(accountDto.getEmail());
        user.setRoles(Arrays.asList("ROLE_USER"));
        return repository.save(user);
    }
    private boolean emailExists(String email) {
        User user = repository.findByEmail(email);
        if (user != null) {
            return true;
        }
        return false;
    }
}

8. Загрузка сведений о пользователе для входа в систему безопасности

В нашемprevious article для входа использовались жестко закодированные учетные данные. Давайте изменим это, а такжеuse the newly registered user information и учетные данные. Мы реализуем пользовательский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().toLowerCase(), enabled, accountNonExpired,
          credentialsNonExpired, accountNonLocked,
          getAuthorities(user.getRoles()));
    }

    private static List getAuthorities (List roles) {
        List authorities = new ArrayList<>();
        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }
}

8.2. Включить нового поставщика проверки подлинности

Чтобы включить новую пользовательскую службу в конфигурации Spring Security, нам просто нужно добавить ссылку наUserDetailsService внутри элементаauthentication-manager и добавить bean-компонентUserDetailsService:

Пример 8.2.- Диспетчер аутентификации иUserDetailsService


    


Или через конфигурацию Java:

@Autowired
private MyUserDetailsService userDetailsService;

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

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

И мы закончили - полностью и почтиproduction ready registration processреализованы с помощью Spring Security и Spring MVC. Далее мы собираемся обсудить процесс активации недавно зарегистрированной учетной записи путем проверки электронной почты нового пользователя.

Реализацию этого учебного пособия по Spring Security REST можно найти вthe GitHub project - это проект на основе Eclipse, поэтому его должно быть легко импортировать и запускать как есть.