1. Введение
Spring Security — это стандарт для защиты приложений на базе Spring. Он имеет несколько функций для управления аутентификацией пользователя, включая вход и выход из системы.
В этом руководстве мы сосредоточимся на ручном выходе из системы с помощью Spring Security.
Мы предполагаем, что читатели уже понимают стандартный процесс выхода из Spring Security .
2. Базовый выход из системы
Когда пользователь пытается выйти из системы, это имеет несколько последствий для его текущего состояния сеанса . Нам нужно уничтожить сессию двумя шагами:
- Недействительная информация о сеансе HTTP.
- Очистите
SecurityContext
, так как он содержит информацию для проверки подлинности.
Эти два действия выполняются обработчиком SecurityContextLogoutHandler.
Давайте посмотрим, что в действии:
@Configuration
public class DefaultLogoutConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout(logout -> logout
.logoutUrl("/basic/basiclogout")
.addLogoutHandler(new SecurityContextLogoutHandler())
);
}
}
Обратите внимание, что SecurityContextLogoutHandler
добавляется Spring Security по умолчанию — мы просто показываем его здесь для ясности.
3. Выход из системы для очистки файлов cookie
Часто выход из системы также требует, чтобы мы удалили некоторые или все файлы cookie пользователя.
Для этого мы можем создать собственный LogoutHandler
, который перебирает все файлы cookie и истечет их срок действия при выходе из системы:
@Configuration
public class AllCookieClearingLogoutConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout(logout -> logout
.logoutUrl("/cookies/cookielogout")
.addLogoutHandler((request, response, auth) -> {
for (Cookie cookie : request.getCookies()) {
String cookieName = cookie.getName();
Cookie cookieToDelete = new Cookie(cookieName, null);
cookieToDelete.setMaxAge(0);
response.addCookie(cookieToDelete);
}
})
);
}
}
Фактически, Spring Security предоставляет CookieClearingLogoutHandler
, который является готовым к использованию обработчиком выхода из системы для удаления файлов cookie.
4. Выход из заголовка Clear-Site-Data
Точно так же мы можем использовать специальный заголовок ответа HTTP для достижения той же цели; здесь в игру вступает заголовок Clear-Site-Data .
По сути, заголовок Clear-Data-Site
очищает данные просмотра (файлы cookie, хранилище, кеш), связанные с запрашивающим веб-сайтом:
@Configuration
public class ClearSiteDataHeaderLogoutConfiguration extends WebSecurityConfigurerAdapter {
private static final ClearSiteDataHeaderWriter.Directive[] SOURCE =
{CACHE, COOKIES, STORAGE, EXECUTION_CONTEXTS};
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout(logout -> logout
.logoutUrl("/csd/csdlogout")
.addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(SOURCE)))
);
}
}
Однако очистка хранилища может повредить состояние приложения, если мы очищаем только один тип хранилища. Поэтому из-за Incomplete Clearing заголовок применяется только в том случае, если запрос является безопасным.
5. Выход по запросу
Точно так же мы можем использовать метод HttpServletRequest.logout()
для выхода пользователя из системы.
Во-первых, давайте добавим необходимую конфигурацию для ручного вызова logout()
по запросу:
@Configuration
public static class LogoutOnRequestConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/request/**")
.authorizeRequests(authz -> authz.anyRequest()
.permitAll())
.logout(logout -> logout.logoutUrl("/request/logout")
.addLogoutHandler((request, response, auth) -> {
try {
request.logout();
} catch (ServletException e) {
logger.error(e.getMessage());
}
}));
}
}
Наконец, давайте создадим тестовый пример, чтобы убедиться, что все работает так, как ожидалось:
@Test
public void givenLoggedUserWhenUserLogoutOnRequestThenSessionCleared() throws Exception {
this.mockMvc.perform(post("/request/logout").secure(true)
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(unauthenticated())
.andReturn();
}
6. Заключение
Таким образом, Spring Security имеет множество встроенных функций для обработки сценариев аутентификации. Всегда полезно научиться программно использовать эти функции.
Как всегда, код этих примеров доступен на GitHub .