Spring Security - UserDetailsService with Remember-me feature (TokenBasedRememberMeServices)

여러가지 서비스를 이용하다보면, 자동 로그인하기라는 체크 박스를 기본적으로 확인할 수 있다.
스프링 시큐리티에서 지원하는 Remember-me 라는 기능이 바로 그 기능이다.

웹사이트를 자주 방문하는 사용자에게 편리한 기능이다. 사용자가 웹 브라우저를 종료한 후에도 사용자의 웹 브라우저에 Remember-Me 쿠키를 저장함으로써 재방문 시 사용자를 기억하는 기능이다. 이는 사용자가 사용자명이나 패스워드를 다시 입력할 필요가 없다.

스프링 시큐리티 Remember-Me 기능에는 두 가지가 있다.

  1. 토큰 기반의 Remember-Me 기능으로, 암호화 시그니처에 의존한다. (TokenBasedRememberMeServices)
  2. 영구 토큰 (Persistent Token) 기반 Remember-Me 기능이며, 데이터베이스 (datastore) 가 필요하다. (PersistentTokenBasedRememberMeServices)

먼저, 토큰 기반 Remember-Me 기능을 살펴보자.
아래의 로그인 화면과 같이, login.html 에서 Remember me 체크 박스를 만들어준다.
그리고 name attribute 설정을 해준다.



spring security remember-me 기능의 default parameter 는 "remember-me" 이다.
물론 다르게 설정이 가능하다.

아래는 시큐리티 설정이다.
HttpSecurity 에 rememberMe() 와 key() tokenValiditySeconds() 메서드를 통해 설정한다.
key() -> Remember-Me 쿠키의 시그니처 생성 시 사용되는 고유한 키를 정의한다. 애플리케이션의 고유 이름을 포함한 최소 36자 길이의 문자열을 사용할 것을 추천한다.
tokenValiditySeconds -> Remember-Me 쿠키의 만료 시간을 설정한다. -1 로 설정한다면, 2주를 의미한다.


토큰 기반 Remember-Me 기능의 동작 원리

Remember-Me 기능은 Base64 인코딩 문자열을 포함하는 쿠키를 사용자 웹 브라우저에 설정한다.
  1. 사용자명
  2. 만료 날짜 및 시간
  3. 만료 날짜 및 시간, username, password 및 rememberMe 메서드의 key 애트리뷰트에 대한 MD5 해시 값 (시그니쳐)


UserDetailsManager 는 UserDetailsService interface 를 상속받는다.


아래는 UserDetailsManager 를 구현한 커스텀 UserDetailsService 이다.
createUser(UserDetails user) 메서드는 스프링 시큐리티가 해당 UserDetails 를 관리할 수 있도록, SecurityContextHolder 에 setAuthentication() 메서드를 통해서 추가해준다.
나머지 아래에 updateUser(), deleteUser(), changePassword(), userExists() 는 편의상 아직 구현하지 않았다.


아래의 TokenBasedRememberMeServices 의 ProcessAutoLoginCookie() 메서드의 중간쯤에 getUserDetailsService().loadUserByUsername() 메서드를 호출하고 있는것을 확인할 수 있다.

스프링 시큐리티는 사용자명과 만료일을 추출한 후, 사용자명을 사용해 UserDetailsService (UserDetailsService 를 사용하지 않는다면, Remember-Me 기능을 사용할 수 없다는 단점이 있다.) 에서 사용자 패스워드를 검색한다. 여기서 key 는 rememberMe 메서드를 사용해 제공했기 때문에 위에서 설정한 값을 알고 있다.

토큰 기반 Remember-Me 기능은 TokenBasedRememberMeServices 의 processAutoLoginCookies() 메서드를 사용한다. 해당 메서드안에서 UserDetailsService 의 loadUserByUsername() 메서드 호출을 통해 UserDetails 를 가져와 시그니쳐를 계산한다.

물론, AbstractRememberMeServices 를 상속받아 커스텀 RememberMeServices 를 재정의하고, 스프링 시큐리티에 커스텀 RememberMeServices 를 사용하도록 할 수 있을 것 같아 보인다. ( 시간 날 때 해보고 공유할 수 있도록 해보겠다. 해보지 않아서 확신하기 어렵다.)

이제 스프링 시큐리티는 사용자명, 만료일, 패스워드 및 key 를 사용해 예상되는 시그니처를 계산한다. 그 후, 예상되는 시그니쳐와 쿠키의 시그니쳐를 비교한다.

예상되는 시그니쳐와 쿠키의 시그니쳐가 일치한다면, 사용자명과 만료일이 유효하다는 것을 신뢰할 수 있다. 따라서 개념적으로 Remember-Me Key(애플리케이션만 알고 있는) 와 사용자 패스워드(사용자만이 알고 있는) 를 모른 채 시그니쳐를 위조하는 것은 불가능하다. 즉, 시그니처가 일치하고 토큰이 만료되지 않은 경우 합법적인 사용자만이 로그인할 수 있다.


아래는 Remember me 체크박스를 체크하지 않고 로그인을 한다.

 그 결과 JSESSIONID 라는 세션 정보를 담은 쿠키만 존재한다.



이번에는 Remember me 체크 박스를 체크하고 로그인한다.
아래와 같이 remember-me 라는 쿠키가 추가적으로 생겼다.

remember-me 쿠키 이름을 다르게 설정하고 싶다면, rememberMeCookieName() 메서드를 통해서 설정이 가능하다.


Remember-Me 의 안전성

사용자의 편의를 위해 제공하는 모든 기능은 안전한 사이트를 보안 위험에 노출시킬 위험성을 항상 안고 있다. Remember-Me 기능도 기본 형태로 사용하면 악의적인 사용자가 중간에서 사용자의 쿠키를 가로채서 재사용할 위험이 있다. 


SSL 및 기타 네트워크 보안 기술을 함께 사용한다면 해당 유형의 공격을 방어할 수 있다.
하지만, 저장된 사용자의 세션을 도용하거나 손상시키는 XSS 와 같은 공격 기법도 있다. 따라서 Remember-Me 기능은 사용자에게는 편리한 기능이지만, 보안 위험이 도사리고 있으므로 주의해야 한다.




다음은, 영구 토큰 기반 Remember-Me 에 대해 알아보자.

//next

댓글

이 블로그의 인기 게시물

About JVM Warm up

About idempotent

About Kafka Basic

About ZGC

sneak peek jitpack

Spring Boot Actuator readiness, liveness probes on k8s

About Websocket minimize data size and data transfer cost on cloud

About G1 GC

대학생 코딩 과제 대행 java, python, oracle 네 번째