1. Обзор
В этой статье мы реализуем базовый процесс регистрации с помощью Spring Security. Это строится на основе концепций , рассмотренных в предыдущей статье, где мы рассматривали логин.
Цель здесь состоит в том, чтобы добавить полный процесс регистрации , который позволяет пользователю регистрироваться, проверять и сохранять пользовательские данные.
Дальнейшее чтение:
Сервлет 3 Асинхронная поддержка с Spring MVC и Spring Security
Пружинная защита с Тимелеафом
Spring Security – Заголовки управления кэшем
2. Страница Регистрации
Во – первых-давайте реализуем простую страницу регистрации, отображающую следующие поля :
- имя (имя и фамилия)
- электронная почта
- пароль (и поле подтверждения пароля)
В следующем примере показан простой registration.html страница:
Пример 2.1.
loginform
3. Пользователь Возражает
Нам нужен объект передачи данных для отправки всей регистрационной информации в наш бэкэнд 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. Позже мы также реализуем наши собственные пользовательские аннотации проверки для проверки формата адреса электронной почты, а также для подтверждения пароля. (см. Раздел 5)
4. Регистрационный Контролер
Ссылка Регистрация на странице вход приведет пользователя на страницу регистрация . Этот бэкэнд для этой страницы живет в контроллере регистрации и сопоставляется с “/user/registration” :
Пример 4.1. показать Регистрацию Метод
@GetMapping("/user/registration") public String showRegistrationForm(WebRequest request, Model model) { UserDto userDto = new UserDto(); model.addAttribute("user", userDto); return "registration"; }
Когда контроллер получает запрос “/user/registration” , он создает новый объект UserDTO , который будет поддерживать форму registration , связывает ее и возвращает – довольно просто.
5. Проверка Регистрационных Данных
Далее – давайте посмотрим на валидации, которые контроллер будет выполнять при регистрации новой учетной записи:
- Все обязательные поля заполнены (нет пустых или нулевых полей)
- Адрес электронной почты действителен (хорошо сформирован)
- Поле подтверждения пароля совпадает с полем пароля
- Учетная запись еще не существует
5.1. Встроенная Валидация
Для простых проверок мы будем использовать готовые аннотации проверки бобов на объекте DTO – аннотации типа @NotNull , @NotEmpty и т. Д.
Чтобы запустить процесс проверки, мы просто аннотируем объект в слое контроллера аннотацией @Valid :
public ModelAndView registerUserAccount( @ModelAttribute("user") @Valid UserDto userDto, HttpServletRequest request, Errors errors) { ... }
5.2. Пользовательская валидация для проверки валидности электронной почты
Далее – давайте проверим адрес электронной почты и убедимся, что он правильно сформирован. Для этого мы создадим пользовательский валидатор , а также пользовательскую аннотацию валидации – назовем это @ValidEmail .
Краткое примечание здесь – мы сворачиваем нашу собственную пользовательскую аннотацию вместо Hibernate | @Email , потому что Hibernate считает старый формат адресов интрасети: [email protected] допустимым (см. Stackoverflow article), что не очень хорошо.
Вот аннотация проверки электронной почты и пользовательский валидатор:
Пример 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 extends Payload>[] payload() default {}; }
Обратите внимание, что мы определили аннотацию на уровне FIELD – поскольку именно там она применяется концептуально.
Пример 5.2.2. – Пользовательский EmailValidato r:
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(); } }
Давайте теперь использовать новую аннотацию в нашей UserDTO реализации:
@ValidEmail @NotNull @NotEmpty private String email;
5.3. Использование пользовательской проверки для подтверждения пароля
Нам также нужна пользовательская аннотация и валидатор, чтобы убедиться, что поля password и matching Password совпадают:
Пример 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 extends Payload>[] payload() default {}; }
Обратите внимание, что аннотация @Target указывает на то, что это аннотация уровня TYPE . Это происходит потому, что для выполнения проверки нам нужен весь объект UserDTO .
Пользовательский валидатор, который будет вызван этой аннотацией, показан ниже:
Пример 5.3.2. Валидатор совпадений паролей Пользовательский валидатор
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()); } }
Теперь аннотацию @PasswordMatches следует применить к нашему объекту UserDTO :
@PasswordMatches public class UserDto { ... }
Все пользовательские проверки, конечно, оцениваются вместе со всеми стандартными аннотациями, когда выполняется весь процесс проверки.
5.4. Убедитесь, Что Учетная Запись Еще Не Существует
Четвертая проверка, которую мы осуществим, – это проверка того, что учетная запись email еще не существует в базе данных.
Это выполняется после того, как форма была проверена, и это делается с помощью реализации User Service .
Пример 5.4.1. – Контроллер создать Учетную запись Пользователя Метод t Вызывает UserService O объект
@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 }
Пример 5.4.2. – Пользователь Услуга Проверяет наличие дубликатов писем
@Service public class UserService implements IUserService { @Autowired private UserRepository repository; @Transactional @Override public User registerNewUserAccount(UserDto userDto) throws UserAlreadyExistException { if (emailExist(userDto.getEmail())) { throw new UserAlreadyExistException( "There is an account with that email address: " + userDto.getEmail()); } ... // the rest of the registration operation } private boolean emailExist(String email) { return userRepository.findByEmail(email) != null; } }
Служба пользователей использует класс UserRepository для проверки того, существует ли пользователь с заданным адресом электронной почты в базе данных.
Теперь – фактическая реализация UserRepository в слое персистентности не имеет отношения к текущей статье. Один из быстрых способов-это, конечно, использовать данные Spring для создания слоя репозитория .
6. Сохранение данных и Окончательная обработка форм
Наконец – давайте реализуем логику регистрации в нашем слое контроллера:
Пример 6.1.1. Регистрация Учетной записи Метод в контроллере
@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; } return new ModelAndView("successRegister", "user", userDto); }
Что нужно заметить в приведенном выше коде:
- Контроллер возвращает объект ModelAndView , который является удобным классом для отправки данных модели ( user ), привязанных к представлению.
- Контроллер перенаправит вас на регистрационную форму, если во время проверки будут установлены какие-либо ошибки.
7. Операция UserService – Register
Завершим реализацию операции регистрации в Пользовательском сервисе :
Пример 7.1. IUserService Интерфейс
public interface IUserService { User registerNewUserAccount(UserDto userDto) throws UserAlreadyExistException; }
Пример 7.2. UserService Класс
@Service public class UserService implements IUserService { @Autowired private UserRepository repository; @Transactional @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
Давайте начнем с пользовательской реализации 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 ListgetAuthorities (List roles) { List authorities = new ArrayList<>(); for (String role : roles) { authorities.add(new SimpleGrantedAuthority(role)); } return authorities; } }
8.2. Включите Новый поставщик аутентификации
Чтобы включить новую пользовательскую службу в конфигурации Spring Security, нам просто нужно добавить ссылку на UserDetailsService внутри элемента authentication-manager и добавить UserDetailsService bean:
Пример 8.2.- Менеджер аутентификации и UserDetailsService
Или через конфигурацию Java:
@Autowired private MyUserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); }
9. Заключение
И мы закончили – полный и почти готовый к производству процесс регистрации реализован с помощью Spring Security и Spring MVC. Далее мы обсудим процесс активации только что зарегистрированной учетной записи путем проверки электронной почты нового пользователя.
Реализацию этого учебника Spring Security REST можно найти в проекте GitHub – это проект на основе Eclipse, поэтому его должно быть легко импортировать и запускать как есть.