Перейти к основному содержимому

Ручной выход из системы с помощью Spring Security

· 3 мин. чтения

1. Введение

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

В этом руководстве мы сосредоточимся на ручном выходе из системы с помощью Spring Security.

Мы предполагаем, что читатели уже понимают стандартный процесс выхода из Spring Security .

2. Базовый выход из системы

Когда пользователь пытается выйти из системы, это имеет несколько последствий для его текущего состояния сеанса . Нам нужно уничтожить сессию двумя шагами:

  1. Недействительная информация о сеансе HTTP.
  2. Очистите 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 по умолчанию — мы просто показываем его здесь для ясности.

Часто выход из системы также требует, чтобы мы удалили некоторые или все файлы 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 .