Теперь приложение Taco Cloud имеет полную поддержку регистрации и аутентификации пользователей. Но если вы запустите его сейчас, вы заметите, что вы даже не можете попасть на страницу регистрации без запроса на вход. Потому что, по умолчанию, все запросы требуют проверки подлинности. Давайте посмотрим, как веб-запросы перехватываются и защищаются, чтобы вы могли исправить эту странную ситуацию с курицей и яйцом.
4.3 Защита веб-запросов
Требования безопасности для Taco Cloud должны требовать, чтобы пользователь аутентифицировался перед созданием тако или размещением заказов. Но домашняя страница, страница входа и страница регистрации должны быть доступны неавторизованным пользователям.
Чтобы настроить эти правила безопасности, позвольте мне представить вам другой метод configure() класса WebSecurityConfigurerAdapter:
@Override
protected void configure(HttpSecurity http) throws Exception {
...
}
Этот метод configure() принимает объект HttpSecurity, который может использоваться для настройки обработки безопасности на веб-уровне. Среди многих вещей, которые вы можете настроить с помощью HttpSecurity следующие:
-Требование соблюдения определенных условий безопасности перед обработкой запроса
-Настройка пользовательской страницы входа
-Возможность выхода пользователей из приложения
-Настройка защиты от подделки межсайтовых запросов
Перехват запросов, чтобы гарантировать, что у пользователя есть надлежащие полномочия, является одной из наиболее распространенных вещей, для которых вы настроите HttpSecurity. Давайте сделаем, чтобы ваши клиенты Taco Cloud отвечали этим требованиям.
4.3.1 Защита запросов
Необходимо убедиться, что запросы /design и /orders доступны только авторизованным пользователям; все остальные запросы должны быть разрешены для всех пользователей. Следующая реализация configure() делает именно это:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders")
.hasRole("ROLE_USER")
.antMatchers(“/”, "/**").permitAll();
}
Вызов метода AuthorizationRequests() возвращает объект (ExpressionInterceptUrlRegistry), в котором можно указать URL-пути, шаблоны и требования безопасности для этих путей. В этом случае необходимо указать два правила безопасности:
-Запросы /design и /orders должны быть доступны для пользователей с предоставленными полномочиями ROLE_USER.
-Все запросы должны быть разрешены всем пользователям.
Порядок этих правил важен. Правила безопасности, объявленные первыми, имеют приоритет над правилами, объявленными ниже. Если бы вы поменяли порядок этих двух правил безопасности, все запросы имели бы permitAll(), примененный к ним; правило для /design и /orders запросов не имело бы никакого эффекта.
Методы hasRole() и allowAll() - это всего лишь несколько методов объявления требований безопасности для путей запросов. Таблица 4.1 описывает все доступные методы.
Табл. 4.1 Методы настройки для определения способа защиты пути
Метод
Описание
access(String)
Разрешает доступ, если данное выражение SpEL имеет значение true
anonymous()
Разрешает доступ анонимным пользователям
authenticated()
Разрешает доступ аутентифицированным пользователям
denyAll()
Безоговорочно запрещает доступ
fullyAuthenticated()
Разрешает доступ, если пользователь полностью аутентифицирован (не запоминается)
hasAnyAuthority(String...)
Разрешает доступ, если у пользователя есть какие-либо из указанных полномочий
hasAnyRole(String...)
Разрешает доступ, если пользователь имеет любую из указанных ролей
hasAuthority(String)
Разрешает доступ, если пользователь имеет полномочия
hasIpAddress(String)
Разрешает доступ, если запрос поступает с указанного IP-адреса
hasRole(String)
Разрешает доступ, если пользователь имеет данную роль
not()
Отрицает эффект любого другого метода доступа
permitAll()
Разрешает безоговорочный доступ
rememberMe()
Разрешает доступ пользователям, прошедшим проверку подлинности с помощью remember-me (запомни меня)
Большинство методов, описанных в табл. 4.1, предоставляют основные правила безопасности для обработки запросов, но они являются самоограничивающимися и разрешают только правила безопасности, определенные этими методами. Кроме того, можно использовать метод access() для предоставления выражения SpEL для объявления расширенных правил безопасности. Spring Security расширяет SpEL, чтобы включить несколько определенных для безопасности значений и функций, как указано в таблице 4.2.
Таблица 4.2 Spring Security расширения Spring Expression Language
Выражение безопасности
Возвращаемое значение
authentication
Объект проверки подлинности пользователя
denyAll
Всегда имеет значение false
hasAnyRole(list of roles)
true, если пользователь имеет любую из заданных ролей
hasRole(role)
true, если пользователь имеет заданную роль
hasIpAddress(IP address)
true, если запрос пришел с заданого IP-адреса
isAnonymous()
true, если пользователь является анонимным
isAuthenticated()
true, если пользователь прошел проверку подлинности
isFullyAuthenticated()
true, если пользователь полностью аутентифицирован (не включая аутентификацию с remember-me (запомни меня))
isRememberMe()
true, если пользователь прошел аутентификацию через remember-me (запомни меня)
permitAll
Всегда принимает значение true
principal
Основной объект пользователя
Как вы можете видеть, большинство расширений выражений безопасности в таблице 4.2 соответствуют аналогичным методам в таблице 4.1. На самом деле, используя метод access() вместе с выражениями hasRole() и permitAll, вы можете переписать configure() следующим образом.
Листинг 4.9 Использование выражений Spring для определения правил авторизации
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders")
.access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll");
}
Сначала это может показаться не таким уж большим делом. В конце концов, эти выражения отражают только то, что вы уже сделали с вызовами методов. Но выражения могут быть гораздо более гибкими. Например, предположите, что (по какой-то сумасшедшей причине) вы только хотели позволить пользователям с полномочиями ROLE_USER создавать новые тако в вторникам (например, Taco Tuesday); вы могли переписать выражение как показано в этой модифицированной версии configure():
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders")
.access("hasRole('ROLE_USER') && " +
"T(java.util.Calendar).getInstance().get("+
"T(java.util.Calendar).DAY_OF_WEEK) == " +
"T(java.util.Calendar).TUESDAY")
.antMatchers(“/”, "/**").access("permitAll");
}
С SpEL-based ограничениями безопасности возможности практически безграничны. Бьюсь об заклад, что вы уже придумываете интересные ограничения безопасности, основанные на SpEL.
Потребности в авторизации для приложения Taco Cloud удовлетворяются простым использованием access() и выражений SpEL в листинге 4.9. Теперь давайте посмотрим настройки страницы входа в систему приложения Taco Cloud.
4.3.2 Создание пользовательской страницы входа
Default-ная страница входа намного лучше, чем неуклюжее диалоговое окно HTTP basic, с которого вы начали, но она все еще довольно просто и не совсем вписывается в стиль остальной части приложения Taco Cloud.
Чтобы заменить встроенную страницу входа, сначала необходимо сообщить Spring Security, по какому пути будет находиться ваша пользовательская страница входа. Это можно сделать, вызвав formLogin() для объекта HttpSecurity, переданного в configure():
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders")
.access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll")
.and()
.formLogin()
.loginPage("/login");
}
Обратите внимание, что перед вызовом formLogin() этот раздел конфигурации и предыдущий раздел соединяются вызовом and(). Метод and() означает, что вы завершили настройку авторизации и готовы применить некоторые дополнительные настройки HTTP. Вы будете использовать and() несколько раз, когда начнете новые разделы конфигурации.
После перемычки and() вызовите formLogin(), чтобы начать настройку пользовательской формы входа. Вызов loginPage() после этого определяет путь, где будет предоставлена ваша пользовательская страница входа. Когда Spring Security определит, что пользователь не прошел проверку подлинности и должен войти в систему, он перенаправит их по этому пути.
Теперь необходимо предоставить контроллер, обрабатывающий запросы по этому пути. Поскольку ваша страница входа в систему будет довольно простой (ничего, кроме представления) достаточно легко объявить ее контроллером представления в WebConfig. Следующие метод addViewControllers() устанавливает login page view контроллер наряду с контроллером представления, для "/" home контроллера:
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");