Выбрать главу

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-атак.