4.4 Определение пользователя
Часто недостаточно просто знать, что пользователь вошел в систему. Обычно важно также знать, кто он, чтобы вы могли представить вид и функционал приложения в зависимости от вошедшего пользователя.
Например, в OrderController при первоначальном создании объекта Order, связанного с формой заказа, было бы неплохо предварительно заполнить заказ именем и адресом пользователя, чтобы ему не пришлось повторно вводить его для каждого заказа. Возможно, еще более важно, что при сохранении заказа необходимо связать сущность Order с пользователем, создавшим заказ.
Для достижения желаемой связи между сущностью Order и сущностью User необходимо добавить новое свойство в класс Order:
@Data
@Entity
@Table(name="Taco_Order")
public class Order implements Serializable {
...
@ManyToOne
private User user;
...
}
Аннотация @ManyToOne этого свойства указывает, что заказ принадлежит одному пользователю, и, наоборот, что у пользователя может быть много заказов. (Поскольку вы используете Lombok, вам не нужно явно определять методы доступа для свойства.)
В OrderController метод processOrder() отвечает за сохранение заказа. Его необходимо изменить, чтобы определить, кто является аутентифицированным пользователем, и вызвать setUser () у объекта Order, чтобы связать заказ с пользователем.
Существует несколько способов определить пользователя. Вот несколько из наиболее распространенных способов:
-Inject основные(Principal) объекты в метод контроллера
-Inject объект аутентификации(Authentication) в метод контроллера
-Использовать SecurityContextHolder, чтобы получить в контексте безопасности
-Используйте аннотированный метод @AuthenticationPrincipal
Например, можно изменить processOrder(), чтобы он принимал java.security.Principal в качестве параметра. Затем вы можете использовать основное(principal) имя для поиска пользователя используя UserRepository:
@PostMapping
public String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus,
Principal principal) {
...
User user = userRepository.findByUsername(
principal.getName());
order.setUser(user);
...
}
Это отлично работает, но засоряет код, который иначе не связан с безопасностью с кодом безопасности. Можно урезать часть кода безопасности, изменив processOrder(), чтобы он принимал Authentication объект в качестве параметра вместо Principaclass="underline"
@PostMapping
public String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus,
Authentication authentication) {
...
User user = (User) authentication.getPrincipal();
order.setUser(user);
...
}
С Authentication в руках, вы можете вызвать getPrincipal(), чтобы получить основной (principal) объект, который в этом случае является User. Обратите внимание, что getPrincipal() возвращает java.util.Object, поэтому вам нужно привести его к User.
Однако, возможно, самым чистым решением является просто принимать на вход объект User в методе processOrder(), но аннотировать его @AuthenticationPrincipal, чтобы он был субъектом проверки подлинности:
@PostMapping
public String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus,
@AuthenticationPrincipal User user) {
if (errors.hasErrors()) {
return "orderForm";
}
order.setUser(user);
orderRepo.save(order);
sessionStatus.setComplete();
return "redirect:/";
}
Что хорошо в @AuthenticationPrincipal, так это то, что он не требует приведения (как с Authentication), и он ограничивает код безопасности самой аннотацией. К моменту получения объекта User в processOrder() он готов к использованию для Оrder.
Есть еще один способ определить, кто является аутентифицированным пользователем, хотя это немного грязно в том смысле, что он очень тяжел для кода, специфичного для безопасности. Вы можете получить объект аутентификации из контекста безопасности и затем запросить его участника (principal) следующим образом:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
Несмотря на то, что этот фрагмент имеет большой объем кода для обеспечения безопасности, он имеет одно преимущество перед другими описанными подходами: его можно использовать в любом месте приложения, а не только в методах обработчика контроллера. Это делает его пригодным для использования на более низких уровнях кода.
Итог:
--Spring Security autoconfiguration отличный способ начать работу с безопасностью, но большинству приложений необходимо явно настроить безопасность для удовлетворения своих уникальных требований безопасности.
-User details могут управляться в хранилищах пользователей, поддерживаемых реляционными базами данных,
LDAP или полностью настраиваемыми реализациями.
-Spring Security автоматически защищает от CSRF-атак.