Как вы узнали в главе 3, Spring Data JPA будет автоматически генерировать реализацию этого интерфейса во время выполнения. Таким образом, теперь можно написать пользовательский сервис ,сведений о пользователе использующую этот репозиторий.
СОЗДАНИЕ СЕРВИСА СВЕДЕНИЙ О ПОЛЬЗОВАТЕЛЕ
UserDetailsService Spring Security - это довольно простой интерфейс:
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
Как вы можете видеть, реализации этого интерфейса даны имя пользователя и должны либо вернуть объект UserDetails или бросить UsernameNotFoundException, если данный логин не появляется никаких результатов.
Поскольку класс User implements UserDetails, а UserRepository предоставляет метод findByUsername(), они идеально подходят для использования в пользовательской реализации UserDetailsService. В следующем листинге показан сервис сведений о пользователе, который вы будете использовать в приложении Taco Cloud.
Листинг 4.6 Определение собственного сервиса сведений о пользователе
package tacos.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import tacos.User;
import tacos.data.UserRepository;
@Service
public class UserRepositoryUserDetailsService implements UserDetailsService {
private UserRepository userRepo;
@Autowired
public UserRepositoryUserDetailsService(UserRepository userRepo) {
this.userRepo = userRepo;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepo.findByUsername(username);
if (user != null) {
return user;
}
throw new UsernameNotFoundException("User '" + username + "' not found");
}
}
UserRepositoryUserDetailsService внедряется с экземпляром UserRepository через его конструктор. Затем в методе loadByUsername(), вызывается findByUsername () объекта UserRepository для поиска User.
Метод loadByUsername () имеет одно простое правило: он никогда не должен возвращать null. Поэтому, если вызов findByUsername() возвращает null, loadByUsername() будет бросать UsernameNotFoundException. В противном случае будет возвращен найденный пользователь.
Вы видите, что UserRepositoryUserDetailsService аннотируется @Service. Это еще одна из аннотаций стереотипа Spring, которые помечают его для включения в сканирование компонентов Spring, поэтому нет необходимости явно объявлять этот класс как bean. Spring автоматически обнаружит его и создаст его как bean.
Однако по-прежнему необходимо настроить сервис сведений о пользователе с Spring Security. Таким образом, вы вернетесь к методу configure() еще раз:
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
На этот раз просто вызовите метод userDetailsService (), передав экземпляр UserDetailsService, который был autowired к SecurityConfig.
Как и при аутентификации на основе JDBC, вы можете (и должны) также настроить кодировщик паролей, чтобы пароль мог быть закодирован в базе данных. Это можно сделать, сначала объявив bean типа PasswordEncoder, а затем внедрив (injecting) его в конфигурацию сервиса сведений о пользователе, вызвав passwordEncoder():
@Bean
public PasswordEncoder encoder() {
return new StandardPasswordEncoder("53cr3t");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(encoder());
}
Важно обсудить последнюю строку в методе configure(). Казалось бы, вы вызываете метод encoder() и передаете его возвращаемое значение в passwordEncoder(). В действительности, однако, поскольку метод encoder() аннотируется @Bean, он будет использоваться для объявления компонента PasswordEncoder в контексте приложения Spring. Любые вызовы encoder() будут перехвачены, чтобы возвратить экземпляр bean из контекста приложения.
РЕГИСТРАЦИЯ ПОЛЬЗОВАТЕЛЕЙ
Несмотря на то, что Spring Security обрабатывает многие аспекты безопасности, она не принимает непосредственного участия в процессе регистрации пользователей, поэтому вы будете полагаться на Spring MVC для обработки этой задачи. Класс RegistrationController в следующем листинге представляет и обрабатывает регистрационные формы.
Листинг 4.7 Контроллер регистрации пользователей
package tacos.security;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import tacos.data.UserRepository;
@Controller
@RequestMapping("/register")
public class RegistrationController {
private UserRepository userRepo;
private PasswordEncoder passwordEncoder;
public RegistrationController(UserRepository userRepo, PasswordEncoder passwordEncoder) {
this.userRepo = userRepo;
this.passwordEncoder = passwordEncoder;
}
@GetMapping
public String registerForm() {
return "registration";
}
@PostMapping
public String processRegistration(RegistrationForm form) {
userRepo.save(form.toUser(passwordEncoder));
return "redirect:/login";
}
}
Как любой типичный Spring MVC controller, RegistrationController аннотирован @Controller для того чтобы обозначить его как controller и отметить его для сканирования компонентов (component scanning). Он также аннотируется @RequestMapping, так что он будет обрабатывать запросы, путь которых /register.
В частности, запрос GET для /register будет обрабатываться методом registerForm (), который просто возвращает логическое имя страницы регистрации. В следующем списке показан шаблон Thymeleaf, определяющий страницу регистрации.
Листинг 4.8 Thymeleaf структура формы регистрации
xmlns:th="http://www.thymeleaf.org">
Register
После отправки данных формы, запрос HTTP POST будет обработан методом processRegistration(). Объект RegistrationForm, передаваемый в processRegistration(), привязан к данным запроса и описывается следующим классом:
package tacos.security;
import org.springframework.security.crypto.password.PasswordEncoder;
import lombok.Data;
import tacos.User;
@Data
public class RegistrationForm {
private String username;
private String password;
private String fullname;
private String street;
private String city;
private String state;
private String zip;
private String phone;
public User toUser(PasswordEncoder passwordEncoder) {
return new User(
username, passwordEncoder.encode(password),
fullname, street, city, state, zip, phone);
}
}
По большей части RegistrationForm - это простой класс с поддержкой Lombok и несколькими свойствами. Но метод toUser() использует эти свойства для создания нового пользовательского объекта, который будет сохранен с помощью внедренного UserRepository.
Вы, без сомнения, заметили, что RegistrationController внедряется с PasswordEncoder. Это точно тот же bean PasswordEncoder который вы объявили ранее. При обработке данных формы, RegistrationController передает ее методу toUser(), который использует ее для кодирования пароля перед сохранением в базу данных. Таким образом, отправленный пароль записывается в закодированном виде, и сервис сведений о пользователе сможет пройти проверку подлинности с использованием этого закодированного пароля.