애플리케이션에서 최종 사용자가 로그인할 수 있다면, 로그아웃도 가능해야 합니다.
기본적으로 Spring Security는 /logout 엔드포인트를 제공하므로 추가 코드가 필요하지 않습니다.
이 섹션의 나머지 부분은 다음과 같은 다양한 사용 사례를 다룹니다:
아래는 위에서 설명한 Spring Security 로그아웃 동작 방식을 보다 자세히 풀어서 정리한 내용입니다.
1. 기본 동작
Spring Security를 사용하는 애플리케이션에서 spring-boot-starter-security 의존성을 추가하거나, @EnableWebSecurity 애너테이션을 적용하면, 별도의 추가 설정 없이도 /logout 엔드포인트를 사용할 수 있습니다. 이때 기본적으로 Spring Security는 다음과 같은 요청에 응답하게 됩니다.
GET /logout
사용자가 브라우저에서 GET 요청으로 /logout 엔드포인트에 접근하면, Spring Security는 로그아웃을 진행하기 전에 확인 페이지를 렌더링합니다. 이 페이지는 다음과 같은 이유로 제공됩니다.
만약 애플리케이션에서 CSRF 보호를 비활성화했다면, 이 확인 페이지는 표시되지 않고 바로 로그아웃 동작이 수행됩니다.
POST /logout
로그아웃을 실제로 수행하는 엔드포인트입니다. GET 요청을 통한 확인 페이지를 거치지 않고도, 필요하다면 곧장 CSRF 토큰을 포함한 POST 요청을 통해 로그아웃을 실행할 수 있습니다. 이는 API 기반 애플리케이션이나 프론트엔드 자바스크립트 코드에서 직접 POST 요청을 통해 로그아웃을 진행하는 경우 유용합니다.
2. 로그아웃 처리 과정 (POST /logout)
사용자가 /logout 엔드포인트에 POST 요청을 보내면, Spring Security는 여러 개의 LogoutHandler 인스턴스를 순차적으로 호출하여 로그아웃 처리를 진행합니다. 기본적으로 수행되는 작업들은 다음과 같습니다.
HTTP 세션 무효화 (SecurityContextLogoutHandler)
사용자의 세션을 무효화시켜 세션에 보관된 사용자 정보 및 인증 정보를 제거합니다. 이를 통해 해당 사용자가 이후에 기존 세션으로 재인증 없이 시스템 접근하는 것을 방지합니다.
SecurityContext 초기화 (SecurityContextLogoutHandler)
SecurityContextHolder에 담겨 있던 인증 정보(SecurityContext)를 비웁니다. 이로써 인증 객체와 관련된 모든 정보가 메모리 상에서 제거됩니다.
SecurityContextRepository 초기화 (SecurityContextLogoutHandler)
SecurityContextRepository는 SecurityContext를 저장하고 조회하는 저장소 역할을 합니다. 로그아웃 시 이 저장소를 초기화함으로써 사용자 인증 상태 정보를 영속적으로 유지하지 않도록 보장합니다.
RememberMe 인증 정보 정리 (TokenRememberMeServices / PersistentTokenRememberMeServices)
"Remember Me" 기능은 사용자가 브라우저를 닫았다가 다시 열어도 인증 상태를 유지할 수 있게 해주는 기능입니다. 로그아웃 시 이 정보(쿠키나 토큰)를 제거하거나 무효화하여 더 이상 사용자가 이 Remember Me 토큰을 통해 재인증되지 못하게 합니다.
CSRF 토큰 제거 (CsrfLogoutHandler)
CSRF 보호를 활성화한 경우, 로그인 세션 동안 유지되던 CSRF 토큰을 제거하여 이후 해당 토큰을 통한 요청이 더 이상 유효하지 않도록 합니다.
LogoutSuccessEvent 발행 (LogoutSuccessEventPublishingLogoutHandler)
로그아웃 완료 시점에 LogoutSuccessEvent를 발행하여, 애플리케이션 내에서 로그아웃에 따른 후처리 로직을 동적으로 추가하거나 감사 로깅(auditing) 등의 작업을 수행할 수 있도록 합니다.
3. 로그아웃 성공 후 처리 (LogoutSuccessHandler)
위의 모든 로그아웃 처리 로직을 거친 후, 기본적으로 LogoutSuccessHandler가 동작하여 사용자를 /login?logout 경로로 리다이렉트합니다. 이 경로는 로그인 화면에 "로그아웃이 성공적으로 이루어졌다"는 메시지를 표시하기 위해 사용될 수 있습니다. 이는 기본 동작이며, 개발자는 필요에 따라 이 동작을 커스터마이징하여 다른 페이지로 리다이렉트하거나 JSON 응답을 반환하도록 할 수도 있습니다.
정리하자면, Spring Security는 기본적으로 /logout 엔드포인트를 제공하며 GET 요청 시 확인 페이지 표시(단, CSRF 보호 활성화 시), POST 요청 시 일련의 핸들러를 통한 세션/컨텍스트 초기화, Remember Me 정보 제거, CSRF 토큰 제거, 로그아웃 이벤트 발행을 수행한 뒤, /login?logout로 리다이렉트합니다. 개발자는 이를 바탕으로 로그아웃 동작을 상황에 맞게 확장하거나 커스터마이징할 수 있습니다.
1. 기본 동작
LogoutFilter는 필터 체인에서 AuthorizationFilter보다 먼저 실행됩니다. 따라서 기본적으로 /logout 엔드포인트는 명시적으로 허용하지 않아도 접근이 가능합니다.
그러나 직접 만든 커스텀 로그아웃 엔드포인트는 일반적으로 접근 가능하도록 permitAll 구성을 추가해야 합니다.
2. 로그아웃 URI 변경하기
Spring Security가 기본적으로 매칭하는 URI를 변경하려면 로그아웃 DSL을 통해 다음과 같이 설정할 수 있습니다:
http
.logout((logout) -> logout.logoutUrl("/my/logout/uri"));
이 설정은 단순히 LogoutFilter의 URI를 변경하므로 별도의 권한 설정은 필요하지 않습니다.
3. 커스텀 로그아웃 성공 엔드포인트 설정
Spring MVC를 사용하여 커스텀 로그아웃 성공 엔드포인트를 생성하거나 (혹은 드물게 커스텀 로그아웃 엔드포인트를 생성) 하려면, Spring Security에서 이를 명시적으로 허용해야 합니다.
그 이유는 Spring MVC가 요청을 처리하는 시점이 Spring Security 이후이기 때문입니다.
다음과 같은 방식으로 authorizeHttpRequests 또는 <intercept-url>을 사용하여 설정할 수 있습니다:
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/my/success/endpoint").permitAll()
// ...
)
.logout((logout) -> logout.logoutSuccessUrl("/my/success/endpoint"));
위 예시에서는 LogoutFilter에 로그아웃 완료 후 /my/success/endpoint로 리다이렉트하도록 지시하며, AuthorizationFilter에서 /my/success/endpoint 엔드포인트를 명시적으로 허용합니다.
4. permitAll 설정으로 간소화
동일한 엔드포인트를 두 번 지정하는 것은 번거로울 수 있습니다. Java 구성을 사용하는 경우, 로그아웃 DSL에서 permitAll 속성을 설정하면 더 간단히 처리할 수 있습니다:
http
.authorizeHttpRequests((authorize) -> authorize
// ...
)
.logout((logout) -> logout
.logoutSuccessUrl("/my/success/endpoint")
.permitAll()
);
이 설정은 모든 로그아웃 URI를 자동으로 허용 목록에 추가합니다.
1. Java 구성에서 정리 작업 추가
Java 구성을 사용하는 경우, 로그아웃 DSL의 addLogoutHandler 메서드를 호출하여 사용자 정의 정리 작업을 추가할 수 있습니다. 예를 들어:
CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie");
http
.logout((logout) -> logout.addLogoutHandler(cookies));
LogoutHandler 인스턴스는 정리 작업을 수행하기 위한 용도로 사용되며, 예외를 던지지 않아야 합니다.LogoutHandler는 함수형 인터페이스이므로 람다식을 사용하여 사용자 정의 핸들러를 제공할 수 있습니다.2. 로그아웃 DSL에서 직접 설정 가능한 정리 작업
일부 정리 작업은 매우 일반적이어서 로그아웃 DSL 및 <logout> 요소에서 직접 설정할 수 있도록 노출되어 있습니다. 대표적인 예는 세션 무효화 및 추가적으로 삭제할 쿠키를 설정하는 것입니다.
예를 들어, 위에서 사용한 CookieClearingLogoutHandler를 다음과 같이 설정할 수도 있습니다:
http
.logout((logout) -> logout.deleteCookies("our-custom-cookie"));
JSESSIONID 쿠키는 명시적으로 지정할 필요가 없습니다. 왜냐하면 SecurityContextLogoutHandler가 세션 무효화 과정에서 이미 이를 제거하기 때문입니다.3. Clear-Site-Data를 사용한 로그아웃
사용자가 로그아웃할 때, 브라우저에 저장된 데이터를 정리하기 위해 Clear-Site-Data 헤더를 활용할 수 있습니다. (이 내용은 추가적인 구성 방식으로 활용 가능하다는 점에서 언급되었습니다.)
1. Clear-Site-Data HTTP 헤더란?
Clear-Site-Data HTTP 헤더는 브라우저가 특정 웹사이트에 속한 쿠키, 저장소, 캐시 등을 정리하도록 지시하는 헤더입니다. 이를 사용하면 로그아웃 시 세션 쿠키를 포함한 모든 데이터를 안전하게 정리할 수 있습니다.
2. Spring Security에서 Clear-Site-Data 헤더 설정하기
Spring Security를 구성하여 로그아웃 시 Clear-Site-Data 헤더를 추가하려면 다음과 같이 설정합니다:
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter());
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData));
ClearSiteDataHeaderWriter의 생성자에 원하는 정리 작업 목록을 전달하여 헤더를 작성합니다.3. 특정 데이터만 정리하기 (예: 쿠키 삭제)
Clear-Site-Data를 사용하여 특정 데이터만 삭제하려면 다음과 같이 설정할 수 있습니다:
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.COOKIES));
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData));
Directive.COOKIES: 쿠키만 정리합니다.1. 기본 동작
대부분의 경우, logoutSuccessUrl을 사용하여 로그아웃 완료 후 리다이렉션할 URL을 설정하면 충분합니다. 하지만 로그아웃 완료 후 리다이렉션 대신 다른 작업을 수행해야 할 경우가 있습니다. 이를 위해 Spring Security는 LogoutSuccessHandler라는 컴포넌트를 제공합니다.
2. HTTP 상태 코드 반환하기
리다이렉션 대신 HTTP 상태 코드만 반환하려는 경우, 성공 핸들러 인스턴스를 제공하여 설정할 수 있습니다. 예를 들어:
http
.logout((logout) -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()));
이 설정은 로그아웃 성공 시 지정된 상태 코드(예: 200, 204)를 반환합니다.
3. 사용자 정의 핸들러 제공하기
LogoutSuccessHandler는 함수형 인터페이스이므로, 람다식을 사용하여 사용자 정의 핸들러를 제공할 수도 있습니다. 이를 통해 로그아웃 성공 시 복잡한 로직을 실행하거나 특정 요구 사항에 맞춘 처리를 수행할 수 있습니다.
1. 기본 권장사항
Spring Security에서 제공하는 로그아웃 DSL을 사용하여 로그아웃을 구성하는 것이 강력히 권장됩니다.
이유는 Spring Security가 로그아웃을 올바르고 완전하게 처리하기 위해 필요한 구성 요소 호출을 간과하기 쉽기 때문입니다.
커스텀 로그아웃 엔드포인트를 직접 만들기보다는, LogoutHandler를 등록하여 사용하는 것이 더 간단한 경우가 많습니다.
2. 커스텀 로그아웃 엔드포인트가 필요한 경우
특정 상황에서 커스텀 로그아웃 엔드포인트가 필요하다면, 아래와 같이 Spring MVC 컨트롤러를 작성할 수 있습니다:
@PostMapping("/my/logout")
public String performLogout() {
// .. 로그아웃 처리
return "redirect:/home";
}
하지만, Spring Security의 SecurityContextLogoutHandler를 호출하지 않으면 로그아웃이 안전하고 완전하게 이루어지지 않을 수 있습니다. 최소한 아래와 같은 방식으로 SecurityContextLogoutHandler를 호출해야 합니다:
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
@PostMapping("/my/logout")
public String performLogout(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
// .. 로그아웃 처리
this.logoutHandler.logout(request, response, authentication);
return "redirect:/home";
}
3. SecurityContextLogoutHandler의 역할
위와 같은 구현은 Spring Security가 요구하는 작업을 수행하여 보안을 유지합니다.
SecurityContextLogoutHandler는 다음을 처리합니다:
SecurityContextHolderStrategy 초기화: 인증 정보를 메모리에서 제거합니다.SecurityContextRepository 초기화: 인증 컨텍스트를 저장하는 리포지토리를 비웁니다.4. 명시적으로 엔드포인트 허용하기
커스텀 로그아웃 엔드포인트를 만들 경우, 해당 엔드포인트에 대한 접근을 명시적으로 허용해야 합니다.
5. 주의사항
SecurityContextLogoutHandler를 호출하지 않을 경우, 인증 정보가 이후 요청에서 여전히 사용 가능할 수 있으며, 이는 사용자가 실제로 로그아웃되지 않았음을 의미합니다. 따라서 반드시 SecurityContextLogoutHandler를 호출하여 보안 취약점을 방지해야 합니다.
로그아웃 구성이 완료되면 Spring Security의 MockMvc 지원을 사용하여 테스트할 수 있습니다.
HttpServletRequest.logout()<logout> 요소에 대한 문서