Spring in Action Covers Spring 5-1--11 — страница 23 из 63


 registry.addViewController("/login");


}

Наконец, необходимо определить само представление страницы входа. Поскольку вы используете Thymeleaf в качестве механизма шаблонов, следующий шаблон Thymeleaf должен нам подойти:




Taco Cloud




Login



Unable to login. Check your username and password.


New here? Click


here to register.














Ключевые вещи, которые следует отметить об этой странице входа в систему, - это путь, который она публикует, и имена полей имени пользователя и пароля.  По умолчанию Spring Security прослушивает запросы на вход в /login и ожидает, что поля username и password будут называться username и password. Но это можено настроить. Например, следующая конфигурация настраивает путь и имена полей :

.and().formLogin()


 .loginPage("/login")


 .loginProcessingUrl("/authenticate")


 .usernameParameter("user")


 .passwordParameter("pwd")

Здесь вы указываете, что Spring Security должна прослушивать запросы /authenticate для обработки запросов на вход. Кроме того, поля username и password теперь должны быть названы user и pwd.

По умолчанию успешный вход приведет пользователя непосредственно к странице, на которую он переходил, когда Spring Security определила, что ему необходимо залогиниться. Если бы пользователь должен был перейти непосредственно на страницу входа в систему, успешный вход в систему привел бы его к корневому пути (например, к домашней странице). Но вы можете изменить это, указав страницу на которую переходить при успешном входе в систему по умолчанию:

.and().formLogin()


 .loginPage("/login")


 .defaultSuccessUrl("/design")

Как настроено здесь, если пользователь должен был успешно войти в систему после прямого перехода на страницу входа, он будет перенаправлен на страницу / design.

При желании вы можете принудительно заставить пользователя перейти на страницу design после входа в систему, даже если он находился на другой странце до входа в систему, передав значение true в качестве второго параметра в defaultSuccessUrl:

.and().formLogin()


 .loginPage("/login")


 .defaultSuccessUrl("/design", true)

Теперь, когда вы имеете дело с пользовательской страницей входа, давайте перейдем на другую сторону монеты аутентификации и посмотрим, как вы можете позволить пользователю выйти из системы.

4.3.3 Выход из системы

Выход из системы так же важен, как и вход в приложение. Чтобы выйти, -вам просто нужно вызвать logout для объекта HttpSecurity:

.and().logout()


 .logoutSuccessUrl("/")

Это настраивает фильтр безопасности, который перехватывает запросы POST для /logout. Поэтому, чтобы обеспечить возможность выхода из системы, вам просто нужно добавить форму и кнопку выхода из системы в представлениях вашего приложения:



Когда пользователь нажимает кнопку, его сеанс очищается, и он выходит из приложения. По умолчанию он будет перенаправлен на страницу входа, где сможет снова войти в систему. Но если вы хотите, чтобы он был отправлен на другую страницу, вы можете вызвать logoutSucessFilter (), чтобы указать другую целевую страницу после выхода:

.and().logout()


 .logoutSuccessUrl("/")

В этом случае, пользователи будут отправлены на главную страницу после выхода.

4.3.4 Предотвращение подделки межсайтовых запросов

Подделка межсайтовых запросов (CSRF) является обычной атакой безопасности. Он включает в себя предоставление пользователю кода на злонамеренно разработанной веб-странице, которая автоматически (и обычно тайно) отправляет форму другому приложению от имени пользователя, который часто является жертвой атаки. Например, пользователю может быть представлена форма на веб-сайте злоумышленника, которая автоматически публикует URL-адрес на банковском веб-сайте пользователя (который предположительно плохо спроектирован и уязвим для такой атаки) для перевода денег. Пользователь может даже не знать, что атака произошла, пока не заметит пропажу денег со своего счета.

Для защиты от таких атак приложения могут создать токен CSRF при отображении формы, поместить этот токен в скрытое поле, а затем хранить его для последующего использования на сервере. После отправки формы токен отправляется обратно на сервер вместе с остальными данными формы. Затем запрос перехватывается сервером и сравнивается с первоначально созданным токеном. Если токены совпадают, запрос разрешается обработать. В противном случае форма должна быть отрисована вредоносным веб-сайтом без знания токена, сгенерированного сервером.

К счастью, Spring Security имеет встроенную защиту CSRF. Еще более удачным является то, что она включена по умолчанию, и вам не нужно явно настраивать её. Необходимо только убедиться, что все формы, отправляемые приложением, содержат поле с именем _csrf, содержащее токен CSRF.

Spring Security даже упрощает это, помещая токен CSRF в атрибут запроса с именем _csrf. Таким образом можно отобразить токен CSRF в скрытом поле в шаблоне Thymeleaf:

Если вы используете библиотеку тегов Spring MVC’s JSP или Thymeleaf с Spring Security dialect, вам даже не нужно явно создавать скрытое поле. Скрытое поле будет создано автоматически.

В Thymeleaf, вам просто нужно убедиться, что один из атрибутов элемента

с префиксом в качестве атрибута Thymeleaf. Это обычно не проблема, так как довольно часто Thymeleaf отображает путь как относительный контекст.  Например, атрибут th:action-это все, что вам нужно для отображения скрытого поля Thymeleaf:

Можно отключить поддержку CSRF, но я не решаюсь показать вам, как это сделать. CSRF-защита важна и легко обрабатывается в формах, поэтому нет причин ее отключать. Но если вы настаиваете на его отключении, вы можете сделать это, вызвав disable() следующим образом:

.and().csrf().disable()

Опять же, я предупреждаю вас не отключать защиту CSRF, особенно для производственных приложений.

Вся ваша безопасность веб-уровня теперь настроена для Taco Cloud.  Помимо прочего, теперь у вас есть настраиваемая страница входа и возможность аутентификации пользователей в репозитории пользователей с поддержкой JPA. Теперь давайте посмотрим, как можно получить информацию о зарегистрированных пользователей.

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 объект в качестве параметра вместо Principal:

@PostMapping


public String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus,