본문 바로가기

Development/Spring

[Spring] Spring Security에서 지원하는 Logout과 JWT 기반 Logout 구현의 차이

스프링 공부를 위해 구입한 도서에서는 JWT 기반 회원가입, 로그인 기능은 있어도 로그아웃은 없길래

로그아웃 기능은 어떻게 구현할까 찾아보았다.

기능 추가를 하려면 새 DB를 써야해??

구글링으로 스프링 JWT 기반 로그아웃 기능을 어떻게 구현했는지 찾아보았는데 Redis를 사용해서 로그인 시 Token 데이터를 저장하고, 로그아웃 시 Redis에서 토큰 데이터를 지우는 방식으로 구현하였다.

로그아웃 하려고 DB를 새로 추가하라니.. 그럼 로그인 기능도 바꿔주어야 하는 것 아닌가..란 생각이 들었다.

그래서 다른 방법을 찾기 위해 Spring Security에서 지원하는 Logout에 대해 알아보았다.

Spring Security에서 기본으로 제공하는 Logout

https://docs.spring.io/spring-security/reference/servlet/authentication/logout.html#logout-java-configuration

 

Handling Logouts :: Spring Security

If you are using Java configuration, you can add clean up actions of your own by calling the addLogoutHandler method in the logout DSL, like so: Custom Logout Handler CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie"

docs.spring.io

위 문서를 요약하자면,

Spring Security는 기본적으로 /logout 에 대한 엔드포인트를 제공하기 때문에 별도의 코드를 작성하지 않아도 로그아웃 기능을 사용할 수 있다.
spring-boot-starter-security 의존 관계를 추가하거나 @EnableWebSecurity 어노테이션을 사용하면 스프링 시큐리티는 자동으로 로그아웃 지원 기능을 추가한다.
기본적으로 GET /logoutPOST /logout 둘 다 지원한다.

GET /logout:

  • GET /logout 요청 시 스프링 시큐리티는 로그아웃 확인 페이지를 표시한다.
  • 이 페이지는 사용자가 정말 로그아웃 할 의도인지 확인하는 역할을 하며, 또한 POST /logout 에 필요한 CSRF 토큰을 제공하는 간편한 방법을 제공한다.
  • 설정에서 CSRF 보호 기능을 비활성화한 경우 사용자에게 로그아웃 확인 페이지가 표시되지 않고 바로 로그아웃이 수행된다.

POST /logout:

  • 애플리케이션에서 로그아웃을 유도하기 위해서는 POST /logout 요청만 하면 된다. (GET /logout 필수 아님)
  • 요청 시 필요한 CSRF 토큰이 포함되어 있어야 한다.

POST /logout 수행 작업:

POST /logout 요청 시 스프링 시큐리티는 일련의 LogoutHandler 를 사용하여 기본적으로 다음과 같은 작업을 수행한다.

  1. HTTP 세션 무효화 (SecurityContextLogoutHandler):
  • 현재 사용자의 HTTP 세션을 무효화한다.
  • 이는 세션에 저장된 모든 데이터(사용자 인증 정보, 권한 등)를 삭제하는 것을 의미
  • 세션 무효화 후에는 더 이상 해당 세션을 사용하여 요청을 처리할 수 없다.
  1. SecurityContextHolderStrategy 초기화 (SecurityContextLogoutHandler):
  • 현재 SecurityContext를 지운다.
  • SecurityContext는 현재 사용자의 인증 정보, 권한, 기타 보안 관련 정보를 포함하는 객체이다.
  • SecurityContext를 지우면 현재 사용자에 대한 모든 보안 정보가 초기화된다.
  1. SecurityContextRepository 초기화 (SecurityContextLogoutHandler):
  • SecurityContextRepository 에 저장된 SecurityContext를 지운다.
  • SecurityContextRepository 는 SecurityContext를 영구적으로 저장하는 데 사용되는 저장소이다.
  • 일반적으로 SecurityContextRepository 는 데이터베이스 또는 캐시를 사용하여 SecurityContext를 저장한다.

4. RememberMe 인증 정보 초기화 (TokenRememberMeServices / PersistentTokenRememberMeServices):

  • Remember-Me 기능을 사용하여 로그인한 경우 사용자의 RememberMe 인증 정보를 초기화한다.
  • RememberMe 인증 정보는 사용자가 로그아웃 후에도 자동으로 로그인하도록 설정하는 데 사용된다.
  • RememberMe 인증 정보를 정리하면 사용자는 다음 로그인 시 다시 로그인 절차를 거쳐야 한다.

5. 저장된 CSRF 토큰 삭제 (CsrfLogoutHandler):

  • 현재 사용자의 CSRF 토큰을 삭제한다.
  • CSRF 토큰은 사이트 간 요청 위조 공격을 방지하는 데 사용된다.
  • CSRF 토큰을 삭제하면 사용자는 로그아웃 후 새 CSRF 토큰을 요청해야 한다.

6. LogoutSuccessEvent 발생 (LogoutSuccessEventPublishingLogoutHandler):

  • LogoutSuccessEvent 이벤트를 발생시킨다.
  • 이 이벤트는 로그아웃 프로세스가 성공적으로 완료되었음을 나타낸다.
  • 개발자는 이 이벤트를 사용하여 로그아웃 후 수행해야 할 작업을 구현할 수 있다.

위 작업이 완료되면 기본 LogoutSuccessHandler 를 실행하여 일반적으로 /login?logout 페이지로 리다이렉트한다.

JWT 기반 로그아웃 기능

  1. 클라이언트 측 로그아웃

이 방식은 클라이언트 측에서 JWT 토큰을 사용하지 못하게 만드는 것을 목표로 한다.

사용자는 로그아웃 엔드포인트 (예: /logout)에 요청을 보내 로그아웃을 시작한다.
클라이언트 측 (브라우저 애플리케이션)에서 JWT 토큰을 무효화한다. 이는 일반적으로 로컬 스토리지 또는 쿠키에서 토큰을 제거하는 것을 의미한다.

  1. 블랙리스트를 사용한 로그아웃

이 방식은 서버 측에 만료된 토큰을 블랙리스트에 추가하는 방법이다. 블랙리스트에 있는 토큰을 사용하는 모든 요청은 거부된다.

사용자는 로그아웃 엔드포인트에 요청을 보내 로그아웃을 시작한다.
서버는 JWT 토큰을 블랙리스트 (데이터베이스 테이블, 캐시 등)에 추가한다.
이 때, 들어오는 요청에는 인증 헤더에 JWT 토큰이 포함되어 있다.
서버는 요청을 처리하기 전에 블랙리스트를 확인한다. 토큰이 블랙리스트에 있는 경우 요청은 거부된다.

SpringSecurity의 Logout과 JWT 기반 Logout의 차이

JWT 기반 로그인을 구현한 경우, Spring Security의 기본 Logout 기능과 호환되지 않는 문제가 발생할 수 있다.

이는 세션 관리 방식 차이 때문이다.

 

SpringSecurity의 Logout 은 HTTP 세션을 기반으로 하며, 세션 무효화를 통해 로그아웃을 수행한다.
반면, JWT 기반 로그인은 세션을 사용하지 않고 JWT 토큰을 사용하여 사용자를 인증한다.

따라서 SpringSecurity의 Logout 을 사용하면 JWT 토큰이 무효화되지 않고 세션만 무효화되어 로그아웃이 완료되지 않는다.

느낀 점

  • 토큰과 세션 인증 방식에 대해 공부하는 경험이었다.
  • 결국 토큰 기반 인증 방식에서 로그아웃 기능을 위해 인메모리 DB를 사용하는 것은 불가피해 보인다.

참조

 

Handling Logouts :: Spring Security

If you are using Java configuration, you can add clean up actions of your own by calling the addLogoutHandler method in the logout DSL, like so: Custom Logout Handler CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie"

docs.spring.io

  • Google Gemini