Процесът на регистрация с пролетна сигурност

Тази статия е част от поредица: • Урок за пролетна регистрация на сигурността

• Процесът на регистрация с пролетна сигурност (текуща статия) • Регистрация - Активирайте нов акаунт по имейл

• Пролетна регистрация на сигурността - Изпратете отново имейл за потвърждение

• Регистрация с Spring Security - Кодиране на парола

• Регистрационният API става RESTful

• Spring Security - Нулирайте паролата си

• Регистрация - Сила и правила на паролата

• Актуализиране на вашата парола

1. Общ преглед

В тази статия ще внедрим основен процес на регистрация с Spring Security. Това надгражда концепциите, разгледани в предишната статия, където разгледахме вход.

Целта тук е да добавите пълен процес на регистрация, който позволява на потребителя да се регистрира, валидира и продължава потребителските данни.

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

Първо - нека приложим проста страница за регистрация, показваща следните полета :

  • име (собствено и фамилно име)
  • електронна поща
  • парола (и поле за потвърждение на паролата)

Следващият пример показва проста страница за регистрация.html :

Пример 2.1.

form

first

Validation error

last

Validation error

email

Validation error

password

Validation error

confirm submit login

3. Обектът DTO на потребителя

Имаме нужда от обект за прехвърляне на данни, за да изпратим цялата информация за регистрация до нашия пролетен бекенд. Обектът DTO трябва да разполага с цялата информация, която ще ни е необходима по-късно, когато създаваме и попълваме нашия потребителски обект:

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. Контролерът за регистрация

А след регистрацията връзка на вход страница потребителят ще бъде отведен до регистрация страницата. Този заден край за тази страница живее в контролера за регистрация и се преобразува в „/ потребител / регистрация“ :

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

@GetMapping("/user/registration") public String showRegistrationForm(WebRequest request, Model model) { UserDto userDto = new UserDto(); model.addAttribute("user", userDto); return "registration"; }

Когато контролерът получава искането "/ потребител / регистрация" , тя създава новата UserDto обекта, който ще подкрепи регистрация форма, той се връща и се свързва - доста ясен.

5. Проверка на данните за регистрация

След това - нека разгледаме проверките, които контролерът ще извърши при регистрация на нов акаунт:

  1. Всички задължителни полета са попълнени (Няма празни или нулеви полета)
  2. Имейл адресът е валиден (добре оформен)
  3. Полето за потвърждение на паролата съвпада с полето за парола
  4. Акаунтът вече не съществува

5.1. Вградената проверка

За простите проверки ще използваме анотации за проверка на бин на обекта на DTO обекта - анотации като @NotNull , @NotEmpty и т.н.

За да задействаме процеса на валидиране, ние просто ще анотираме обекта в слоя на контролера с анотацията @Valid :

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

5.2. Персонализирана проверка за проверка на валидността на имейли

След това - нека проверим имейл адреса и се уверете, че е добре оформен. Ще създадем персонализиран валидатор за това, както и анотация за валидиране по избор - нека го наречем @ValidEmail .

Бързо обозначаване тук - пускаме собствена собствена анотация вместо @Email на Hibernate, защото Hibernate счита стария формат на интранет адреси: [имейл защитен] като валиден (вижте статията за 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 - тъй като тя се прилага концептуално.

Пример 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. Използване на персонализирана проверка за потвърждение на паролата

Също така се нуждаем от персонализирана анотация и валидатор, за да сме сигурни, че полетата за парола и matchPassword съвпадат:

Пример 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 {}; }

Notice that the @Target annotation indicates that this is a TYPE level annotation. This is because we need the entire UserDto object to perform the validation.

The custom validator that will be called by this annotation is shown below:

Example 5.3.2. The PasswordMatchesValidator Custom Validator

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

Now, the @PasswordMatches annotation should be applied to our UserDto object:

@PasswordMatches public class UserDto { ... }

All custom validations are of course evaluated along with all standard annotations when the entire validation process runs.

5.4. Check That the Account Doesn't Already Exist

The fourth check we'll implement is verifying that the email account doesn't already exist in the database.

This is performed after the form has been validated and it's done with the help of the UserService implementation.

Example 5.4.1. – The Controller's createUserAccount Method Calls the UserService Object

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

Example 5.4.2. – UserService Checks for Duplicate Emails

@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; } }

The UserService relies on the UserRepository class to check if a user with a given email address already exists in the database.

Now – the actual implementation of the UserRepository in the persistence layer isn't relevant for the current article. One quick way is, of course, to use Spring Data to generate the repository layer.

6. Persisting Data and Finishing-Up Form Processing

Finally – let's implement the registration logic in our controller layer:

Example 6.1.1. – The RegisterAccount Method in the Controller

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

Things to notice in the code above:

  1. The controller is returning a ModelAndView object which is the convenient class for sending model data (user) tied to the view.
  2. The controller will redirect to the registration form if there are any errors set at validation time.

7.The UserService – Register Operation

Let's finish the implementation of the registration operation in the UserService:

Example 7.1. The IUserService Interface

public interface IUserService { User registerNewUserAccount(UserDto userDto) throws UserAlreadyExistException; }

Example 7.2. – The UserService Class

@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. Loading User Details for Security Login

In our previous article, login was using hard coded credentials. Let's change that and use the newly registered user information and credentials. We'll implement a custom UserDetailsService to check the credentials for login from the persistence layer.

8.1. The Custom UserDetailsService

Let's start with the custom user details service implementation:

@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. Enable the New Authentication Provider

To enable the new user service in the Spring Security configuration – we simply need to add a reference to the UserDetailsService inside the authentication-manager element and add the UserDetailsService bean:

Example 8.2.- The Authentication Manager and the UserDetailsService

Or, via Java configuration:

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

9. Conclusion

And we're done – a complete and almost production ready registration process implemented with Spring Security and Spring MVC. Next, we're going to discuss the process of activating the newly registered account by verifying the email of the new user.

The implementation of this Spring Security REST Tutorial can be found in the GitHub project – this is an Eclipse based project, so it should be easy to import and run as it is.

Next » Registration – Activate a New Account by Email « Previous Spring Security Registration Tutorial