Success handler что это

REST API с использованием Spring Security и JWT

Рано или поздно каждый Java-разработчик столкнется с необходимостью реализовать защищенное REST API приложение. В этой статье хочу поделиться своей реализацией этой задачи.

1. Что такое REST?

REST (от англ. Representational State Transfer — «передача состояния представления») – это общие принципы организации взаимодействия приложения/сайта с сервером посредством протокола HTTP.

Диаграмма ниже показывает общую модель.

Success handler что это. image loader. Success handler что это фото. Success handler что это-image loader. картинка Success handler что это. картинка image loader

Всё взаимодействие с сервером сводится к 4 операциям (4 — это необходимый и достаточный минимум, в конкретной реализации типов операций может быть больше):

Получение данных с сервера (обычно в формате JSON, или XML);

Добавление новых данных на сервер;

Модификация существующих данных на сервере;

Удаление данных на сервере

Более подробно можно прочесть в остальных источниках, статей о REST много.

2. Задача

Необходимо подготовить защищенное REST приложение, доступ к которому может быть осуществлен только для авторизованного пользователя. Авторизация с передачей логина и пароля выполняется отдельным запросом, при успешной авторизации система должна сгенерировать и вернуть токен. Валидация остальных запросов должна быть осуществлена по токену.
Схема нашего приложения будет выглядеть следующим образом:

Success handler что это. image loader. Success handler что это фото. Success handler что это-image loader. картинка Success handler что это. картинка image loader

3. Технологии

Для решения используем фреймворк Spring Boot и Spring Web, для него требуется:

Авторизация и валидация будет выполнена силами Spring Security и JsonWebToken (JWT).
Для уменьшения кода использую Lombok.

4. Создание приложения

Переходим к практике. Создаем Spring Boot приложение и реализуем простое REST API для получения данных пользователя и списка пользователей.

4.1 Создание Web-проекта

Создаем Maven-проект SpringBootSecurityRest. При инициализации, если вы это делаете через Intellij IDEA, добавьте Spring Boot DevTools, Lombok и Spring Web, иначе добавьте зависимости отдельно в pom-файле.

Success handler что это. image loader. Success handler что это фото. Success handler что это-image loader. картинка Success handler что это. картинка image loader

4.2 Конфигурация pom-xml

После развертывания проекта pom-файл должен выглядеть следующим образом:

Должен быть указан parent-сегмент с подключенным spring-boot-starter-parent;

И установлены зависимости spring-boot-starter-web, spring-boot-devtools и Lombok.

4.3 Создание ресурса REST

Разделим все классы на слои, создадим в папке com.springbootsecurityrest четыре новые папки:

model – для хранения POJO-классов;

repository – в полноценных проектах используется для взаимодействия с БД, но т.к. у нас ее нет, то он будет содержать список пользователей;

service – слой сервиса, прослойка между контролером и слоем ресурсов, используется для получения данных из ресурса, их проверки и преобразования (если это необходимо);

rest – будет содержать в себе классы контроллеры.

В папке model создаем POJO класс User.

В папке repository создаём класс UserRepository c двумя методами:

getByLogin – который будет возвращать пользователя по логину;

getAll – который будет возвращать список всех доступных пользователей. Чтобы Spring создал бин на основании этого класса, устанавливаем ему аннотацию @Repository.

В папке service создаем класс UserService. Устанавливаем классу аннотацию @Service и добавляем инъекцию бина UserRepository. В класс добавляем метод getAll, который будет возвращать всех пользователей и getByLogin для получения одного пользователя по логину.

Создаем контроллер UserController в папке rest, добавляем ему инъекцию UserService и создаем один метод getAll. С помощью аннотации @GetMapping указываем адрес контроллера, по которому он будет доступен клиенту и тип возвращаемых данных.

Запускаем приложение и проверяем, что оно работает, для этого достаточно в браузере указать адрес http://localhost:8080/users, если вы все сделали верно, то увидите следующее:

Success handler что это. image loader. Success handler что это фото. Success handler что это-image loader. картинка Success handler что это. картинка image loader

5. Spring Security

Простенькое REST API написано и пока оно открыто для всех. Двигаемся дальше, теперь его необходимо защитить, а доступ открыть только авторизованным пользователям. Для этого воспользуемся Spring Security и JWT.

Spring Security это Java/JavaEE framework, предоставляющий механизмы построения систем аутентификации и авторизации, а также другие возможности обеспечения безопасности для корпоративных приложений, созданных с помощью Spring Framework.

JSON Web Token (JWT) — это открытый стандарт (RFC 7519) для создания токенов доступа, основанный на формате JSON. Как правило, используется для передачи данных для аутентификации в клиент-серверных приложениях. Токены создаются сервером, подписываются секретным ключом и передаются клиенту, который в дальнейшем использует данный токен для подтверждения своей личности.

5.1 Подключаем зависимости

Добавляем новые зависимости в pom-файл.

5.2 Генерация и хранения токена

Начнем с генерации и хранения токена, для этого создадим папку security и в ней создаем класс JwtTokenRepository с имплементацией интерфейса CsrfTokenRepository (из пакета org.springframework.security.web.csrf).

Интерфейс указывает на необходимость реализовать три метода:

Генерация токена в методе generateToken;

Сохранения токена – saveToken;

Получение токена – loadToken.

Генерируем токен силами Jwt, пример реализации метода.

Параметр secret является ключом, необходимым для расшифровки токена, оно может быть постоянным для всех токенов, но лучше сделать его уникальным только для пользователя, например для этого можно использовать ip-пользователя или его логин. Дата exp является датой окончания токена, рассчитывается как текущая дата плюс 30 минут. Такой параметр как продолжительность жизни токена рекомендую вынести в application.properties.

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

Сохранение токена выполняем в response (ответ от сервера) в раздел headers и открываем параметр для чтения фронта указав имя параметра в Access-Control-Expose-Headers.

Добавляем к классу еще один метод по очистке токена из response, будем использовать его при ошибке авторизации.

5.3 Создание нового фильтра для SpringSecurity

Создаем новый класс JwtCsrfFilter, который является реализацией абстрактного класса OncePerRequestFilter (пакет org.springframework.web.filter). Класс будет выполнять валидацию токена и инициировать создание нового. Если обрабатываемый запрос относится к авторизации (путь /auth/login), то логика не выполняется и запрос отправляется далее для выполнения базовой авторизации.

5.4 Реализация сервиса поиска пользователя

Теперь необходимо подготовить сервис для поиска пользователя по логину, которого будем авторизовывать. Для этого нам необходимо добавить к сервису UserService интерфейс UserDetailsService из пакета org.springframework.security.core.userdetails. Интерфейс требует реализовать один метод, выносить его в отдельный класс нет необходимости.

Полученного пользователя необходимо преобразовать в класс с реализацией интерфейса UserDetails или воспользоваться уже готовой реализацией из пакета org.springframework.security.core.userdetails. Последним параметром конструктора необходимо добавить список элементов GrantedAuthority, это роли пользователя, у нас их нет, оставим его пустым. Если пользователя по логину не нашли, то бросаем исключение UsernameNotFoundException.

5.5 Обработка авторизации

По результату успешно выполненной авторизации возвращаю данные авторизованного пользователя. Для этого создадим еще один контроллер AuthController с методом getAuthUser. Контроллер будет обрабатывать запрос /auth/login, а именно обращаться к контексту Security для получения логина авторизованного пользователя, по нему получать данные пользователя из сервиса UserService и возвращать их на фронт.

5.6 Обработка ошибок

Что бы видеть ошибки авторизации или валидации токена, необходимо подготовить обработчик ошибок. Для этого создаем новый класс GlobalExceptionHandler в корне com.springbootsecurityrest, который является расширением класса ResponseEntityExceptionHandler с реализацией метода handleAuthenticationException.

Метод будет устанавливать статус ответа 401 (UNAUTHORIZED) и возвращать сообщение в формате ErrorInfo.

5.7 Настройка конфигурационного файла Spring Security.

Все данные подготовили и теперь необходимо настроить конфигурационный файл. В папке com.springbootsecurityrest создаем файл SpringSecurityConfig, который является реализацией абстрактного класса WebSecurityConfigurerAdapter пакета org.springframework.security.config.annotation.web.configuration. Помечаем класс двумя аннотациями: Configuration и EnableWebSecurity.

Реализуем метод configure(AuthenticationManagerBuilder auth), в класс AuthenticationManagerBuilder устанавливаем сервис UserService, для того что бы Spring Security при выполнении базовой авторизации мог получить из репозитория данные пользователя по логину.

Реализуем метод configure(HttpSecurity http):

Разберем метод детальнее:

.authorizeRequests().antMatchers(«/auth/login»).authenticated() для запроса /auth/login выполняем авторизацию силами security. Что бы не было двойной валидации (по токену и базовой), запрос был добавлен в исключение к классу JwtCsrfFilter;

6. Проверка функционала

Для проверки использую Postman. Запускаем бэкенд и выполняем запрос http://localhost:8080/users с типом GET.

Токена нет, валидация не пройдена, получаем сообщение с 401 статусом.

Success handler что это. image loader. Success handler что это фото. Success handler что это-image loader. картинка Success handler что это. картинка image loader

Пытаемся авторизоваться с неверными данными, выполняем запрос http://localhost:8080/auth/login с типом POST, валидация не выполнена, токен не получен, вернулась ошибка с 401 статусом.

Success handler что это. image loader. Success handler что это фото. Success handler что это-image loader. картинка Success handler что это. картинка image loader

Авторизуемся с корректными данными, авторизация выполнена, получен авторизованный пользователь и токен.

Success handler что это. image loader. Success handler что это фото. Success handler что это-image loader. картинка Success handler что это. картинка image loader Success handler что это. image loader. Success handler что это фото. Success handler что это-image loader. картинка Success handler что это. картинка image loader

Повторяем запрос http://localhost:8080/users с типом GET, но с полученным токеном на предыдущем шаге. Получаем список пользователей и обновленный токен.

Success handler что это. image loader. Success handler что это фото. Success handler что это-image loader. картинка Success handler что это. картинка image loader

Заключение

В этой статье рассмотрели один из примеров реализации REST приложения с Spring Security и JWT. Надеюсь данный вариант реализации кому то окажется полезным.

Источник

Spring Security — за кулисами

Задачи безопасности, такие как аутентификация пользователя и авторизация пользователя для просмотра ресурсов приложения, обычно выполняются сервером приложений. Эти задачи могут быть делегированы потоку безопасности Spring, освобождающему сервер приложений от выполнения этих задач. Spring security в основном решает эти задачи путем реализации стандартного javax.servlet.Filter. Для инициализации безопасности Spring в вашем приложении вам нужно объявить следующий фильтр в вашем файле web.xml:

Теперь этот фильтр (springSecurityFilterChain) просто делегирует запрос в среду безопасности Spring, где определенные задачи безопасности будут обрабатываться фильтрами безопасности, определенными в контексте приложения. Так как это происходит?

Внутри метода doFilter DelegatingFilterProxy (реализация javax.servlet.Filter) контекст приложения Spring будет проверен на предмет bean-компонента с именем springSecurityFilterChain. Этот bean-компонент «springSecurityFilterChain» на самом деле является псевдонимом, определенным для цепочки весенних фильтров.

Поэтому, когда проверка выполняется в контексте приложения, она возвращает bean-компонент filterChainProxy. Эта цепочка фильтров отличается от цепочки javax.servlet.FilterChain, которая используется фильтрами Java, определенными в web.xml, для вызова следующего возможного фильтра, если он существует, или передачи запроса в сервлет / jsp. Bean filterChainProxy состоит из упорядоченного списка фильтров безопасности, которые определены в контексте приложения Spring. Итак, вот следующий набор вопросов:

1. Кто инициализирует / определяет этот фильтр ChainProxy?
2. Какие фильтры безопасности определены в контексте приложения Spring?
3. Чем эти фильтры безопасности отличаются от обычных фильтров, определенных в web.xml?

Теперь перейдем к первому вопросу: filterChainProxy инициализируется, когда в контексте приложения определен элемент ‹http из пространства имен безопасности. Вот основная структура элемента ‹http:

Теперь HttpSecurityBeanDefinitionParser из среды Spring считывает этот ‹http-элемент для регистрации filterChainProxy в контексте приложения. Элемент http с автоматической настройкой, установленной в true, на самом деле является сокращенной записью для следующего:

Мы обсудим подэлементы ‹http› позже. Итак, теперь перейдем ко второму вопросу, что все фильтры регистрируются в цепочке фильтров по умолчанию? Вот ответ из весенней документации:

Поэтому по умолчанию, когда мы добавляем ‹http› элемент, будут добавлены три вышеуказанных фильтра. И так как мы установили для auto-config значение true, BasicAuthenticationFilter, LogoutFilter и UsernamePasswordAuthenticationFilter также добавляются в цепочку фильтров. Теперь, если вы посмотрите на исходный код любого из этих фильтров, это также стандартные реализации javax.servlet.Filter. Но, определяя эти фильтры в контексте приложения, а не в файле web.xml, сервер приложений передает управление в Spring для решения задач, связанных с безопасностью. И SpringC filterChainProxy позаботится о цепочке фильтров безопасности, которые должны применяться по запросу. Это отвечает на третий вопрос.

Чтобы получить более точный контроль над фильтрами безопасности, которые должны применяться к запросу, мы можем определить нашу собственную реализацию FilterChainProxy.

Из приведенного выше xml мы видим, что мы не хотим, чтобы какие-либо фильтры применялись к изображениям, тогда как для остальных запросов указан список фильтров, которые должны быть применены. Итак, в общем, мы указываем цепочки фильтров в порядке наименьшего ограничения на наиболее ограниченные. Но такого рода регистрация наших собственных цепочек фильтров, как правило, не требуется. Spring, через элемент ‹http, предоставляет несколько хуков, с помощью которых мы можем получить более точный контроль над тем, как применяется безопасность. Итак, рассмотрим подробно, что можно настроить через элемент ‹http.

1. Аутентификация: HttpBasicAuthentication и аутентификация на основе формы входа
2. Поддержка авторизации через ACL (список контроля доступа)
3. Выйти из службы поддержки
4. Поддержка анонимного входа
5. Запомнить меня
6. Параллельное управление сессиями

(1) Аутентификация: Аутентификация может быть обработана двумя способами — HttpBasicAuthentication и аутентификацией на основе формы входа. Мы кратко обсудим эти два вопроса. Прежде чем понять их, было бы хорошо иметь базовое понимание AuthenticationManager, которое лежит в основе реализации аутентификации с помощью Spring Security. Внутри элемента диспетчера аутентификации мы определяем всех поставщиков аутентификации, доступных для приложения. А поставщик аутентификации содержит реализацию UserDetailsService. Spring загружает информацию о пользователе в UserDetailsService и сравнивает комбинацию имени пользователя и пароля с учетными данными, указанными при входе в систему. Вот интерфейс UserDetailsService:

Spring предоставляет две встроенные реализации этого сервиса:

(a) Сохраните данные логина / пароля пользователя в контексте приложения:

Это хорошо подходит, когда пользователей приложения мало. Это можно инициализировать следующим образом:

Тег ‹authentication-provider› соответствует DaoAuthenticationProvider, который фактически вызывает реализацию предоставляемой UserDetailsService. В этом случае мы предоставляем имена пользователей и пароли непосредственно в XML. Когда пользовательская база приложения огромна, мы бы предпочли хранить информацию в базе данных.

Соответствующий bean-компонент, который инициализируется для ‹user-service›, является org.springframework.security.core.userdetails.memory.InMemoryDaoImpl

(б) Хранение пользовательских данных в базе данных: вот как это должно быть инициализировано.

Соответствующий класс в Spring — org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl. Если вы посмотрите на этот класс, то обнаружите, что имя пользователя и пароль хранятся в таблице пользователей, а роли, которые могут быть назначены пользователям, хранятся в таблице полномочий. Мы поговорим о ролях позже. Вот запросы, которые этот класс выполняет для получения учетных данных и полномочий пользователей из базы данных:

Теперь предположим, что у вас есть унаследованная база данных, в которой ваши пользовательские данные хранятся в некоторых других таблицах, а затем мы можем настроить запросы выборки, которые выполняет Spring для получения учетных данных и полномочий пользователя. Скажем, у меня есть таблица участника, в которой есть поля id, username, password и таблица Role, в которой есть поля username, role. Вот как мы должны настроить:

Теперь перейдем к способам выполнения аутентификации:

HttpBasicAuthentication: это можно настроить следующим образом:

По умолчанию, когда это включено, браузер обычно отображает диалог входа в систему для входа пользователей. Вместо диалога входа в систему мы можем настроить его для отображения конкретной страницы входа. Этот вид аутентификации формально определен в стандарте протокола передачи гипертекста. Учетные данные для входа в систему (в кодировке Base 64) отправляются на сервер под заголовком HTTP Authentication. Но у этого есть свои недостатки. Самая большая проблема связана с выходом с сервера. Большинство браузеров, как правило, кэшируют сеансы, другой пользователь не может войти в систему, обновив браузер. Определение ‹http-basic› фактически определяет фильтр BasicAuthenticationFilter за кулисами. При успешной аутентификации объект аутентификации будет помещен в Spring securityContext. К контексту безопасности можно получить доступ через класс SecurityContextHolder. Вот как выглядит объявление компонента BasicAuthenticationFilter:

Для получения дополнительной информации о позициях фильтра обратитесь к enum org.springframework.security.config.http.SecurityFilters

Форма авторизации на основе входа: вот как мы ее включаем:

Но есть несколько хуков, предоставляемых Spring. Атрибут default-target-url указывает, куда должна перейти страница входа в систему после проверки подлинности пользователя, а authentication-fail-url определяет страницу, на которую должен перейти пользователь, если проверка подлинности не удалась.

Spring имеет 2 встроенных реализации для обработчиков успеха. SimpleUrlAuthenticationSuccessHandler и SavedRequestAwareAuthenticationSuccessHandler. Последнее расширяет первое.

Цель SavedRequestAwareAuthenticationSuccessHandler — перевести пользователя на страницу, с которой он был перенаправлен на страницу входа для аутентификации. Это обработчик успеха по умолчанию, когда определен элемент ‹form-login›. Мы также можем переопределить это с помощью нашей пользовательской реализации. Предположим, что мы всегда хотим показывать определенную страницу, как только пользователь входит в систему, а не переводить его на страницу, на которой он находился ранее, мы можем установить Always-use-default-target в true.

Также есть 2 встроенные реализации для обработчиков ошибок: SimpleUrlAuthenticationFailureHandler и ExceptionMappingAuthenticationFailureHandler. Последнее расширяет первое.

Вот как выглядит определение фильтра:

В случае входа в систему не будет проблем с выходом из системы, как обсуждалось в базовой аутентификации. Но недостатком является то, что имя пользователя и пароль отправляются в виде открытого текста в заголовках. Это может быть сделано путем кодирования паролей с использованием методов шифрования. Spring предоставляет встроенную поддержку для этого, используя ‹password-encoder› element в провайдере аутентификации. Вот как мы должны это настроить:

2. Поддержка авторизации через ACL: Spring поддерживает авторизацию через ‹intercept-url› in ‹http›

Каждый intercept-url указывает шаблон URL и роли, которые пользователь должен иметь для доступа к тем URL, которые соответствуют указанному шаблону. Обратите внимание, что шаблоны url всегда заканчиваются символом *. Если ‘*’ не указано, то проблема в том, что хакер может обойти механизм безопасности, просто передавая некоторые параметры в URL.

Так что происходит за кулисами, когда Spring передает все эти URL-адреса для перехвата в качестве метаданных в FilterSecurityInterceptor. Вот как это можно настроить без использования ‹intercept-url›:

Таким образом, из приведенного выше кода вы можете видеть, что анонимные пользователи могут получить доступ только к странице messageList, а для просмотра любых других страниц он должен быть зарегистрирован как пользователь в приложении. Также, если вы внимательно наблюдаете за объявлением bean-компонента, есть свойство accessDecisionManager. Какова цель этого?

Это боб, который на самом деле принимает решения об управлении доступом. Он должен реализовывать интерфейс AccessDecisionManager. Spring предоставляет три встроенных менеджера принятия решений о доступе. Прежде чем понять, как работает диспетчер принятия решений о доступе, нам необходимо узнать, что именно представляет собой AccessDecisionVoter. AccessDecisionManager фактически состоит из одного или нескольких избирателей, принимающих решения о доступе. Этот избиратель инкапсулирует логику, чтобы позволить / запретить / воздержаться от просмотра ресурса пользователем. Голосование за решение «воздержаться» более или менее похоже на отказ от голосования вообще. Поэтому результаты голосования представлены полями констант ACCESS_GRANTED, ACCESS_DENIED и ACCESS_ABSTAIN, определенных в интерфейсе AccessDecisionVoter. Мы можем определить избирателей, принимающих решения по индивидуальному доступу, и добавить их в определение нашего менеджера по принятию решений. Итак, теперь возвращаясь к встроенным менеджерам решений, вот они:

По умолчанию диспетчер принятия решений на основе AffirrativeBase будет инициализирован с двумя избирателями: RoleVoter и AuthenticatedVoter. RoleVoter предоставляет доступ, если пользователь играет определенную роль в качестве требуемого ресурса. Но обратите внимание, что роль должна начинаться с префикса «ROLE_», если избиратель должен предоставить доступ. Но это можно настроить и для другого префикса. Скоро увидим, как это сделать. AuthenticatedVoter предоставляет доступ, только если пользователь аутентифицирован. Допустимые уровни аутентификации: IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED и IS_AUTHENTICATED_ANONYMOUSLY. Предположим, мы хотим определить собственного избирателя и добавить его в диспетчер принятия решений о доступе, вот что мы делаем:

Источник

Spring Security Success Handler

In this article, we will look at the Spring security success handler and how to write custom success handler. In this post, let’s see how to redirect user after login using Spring security. Redirecting user to a different page is a very common requirement for any web application. We will use Spring Boot for this article, but most of the concepts and code base holds true for simple Spring application.

1. Application Setup

Let’s start by creating the web application. We can use the IDE or Spring Initializr to bootstrap our application. We are using Spring Initializr for this post as it offer a fast way to pull the dependencies to build our application.

Success handler что это. spring security success handler. Success handler что это фото. Success handler что это-spring security success handler. картинка Success handler что это. картинка spring security success handler

If you like to use the Spring Boot CLI to generate the project structure, run the following command from the terminal.

Here is our pom.xml file:

We are adding MySQL as the dependencies to store user login details in the database and will customize the user service to get user login details from the DB.

2. Spring Security Configuration.

The next step for our application is to complete the Spring Security configuration. To use the Spring security success handler, we will complete following configuration setup.

2.1. Customer JPA Entity.

2.2. Database Configurations.

Spring Boot JPA staters provides multiple features to support the integration with the underlying database.Let’s define the database property to help Spring Boot JPA connect with the database.

When we start the application, Spring Boot will generate the DDL and create a database schema for us:

Success handler что это. password encoding in spring security table. Success handler что это фото. Success handler что это-password encoding in spring security table. картинка Success handler что это. картинка password encoding in spring security table

If you like to run the DDL yourself, here is the sample DDL SQL script for your reference:

2.3. Spring JPA Respository.

To enable the database operations on the Customer entity, let’s define a CustomerRepositoty class. Please read Spring JPA for more details on sophisticated support to build repositories based on Spring and JPA.

3. Custom UserService

Spring Security use UserDetailsService interface is used in order to lookup the username, password and GrantedAuthorities for any user. We will provide a custom implementation to load the user for our application.

4. Spring Security Configuration

Let’s connect all these services to ensure they work together during user login process. This is how our Spring security config file looks like:

There are few important things happening in the above code.

The most important point is the configure method, which includes a default success handler. In our example, it will always redirect the authenticated user to the welcome page. This is great, but it has few limitations.

5. Custom Success Handler

Let’s create and configure our custom success handler. I am keeping the logic simple in Spring security custom handler but you can add the custom logic as per your requirement. To give you an idea, here is what I did in one of our ecommerce platform.

You have few options while creating custom success handler in your Spring security configuration.

The last step is to configure the success handler in the Spring security. To add this, let’s change our previous Spring security configuration class.

With this, our configuration and setup is complete for the Spring security custom success handler. I am not including the HTML part in this post as you can download it from the GitHub. Let’s run and test our application.

6. Running and Testing Application.

Success handler что это. success handler login. Success handler что это фото. Success handler что это-success handler login. картинка Success handler что это. картинка success handler login

Once you fill the correct details, it will redirect you to the welcome page (as per our custom success handler).

Success handler что это. success handler welcome. Success handler что это фото. Success handler что это-success handler welcome. картинка Success handler что это. картинка success handler welcome

In case admin login to the system, we will redirect the admin to a different welcome screen (as per our code)

Let’s login as admin to validate if our custom success handler works as expected or not:

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *