본문 바로가기

Language/Spring Security

기억하기 인증 - rememberMe()

RememberMe 인증

  • 사용자가 웹 사이트나 애플리케이션에 로그인할 때 자동으로 인증 정보를 기억하는 기능
  • UsernamePasswordAuthenticationFilter와 함께 사용
  • AbstractAuthenticationProcessingFilter 슈퍼 클래스에서 훅을 통해 구현
    • 인증 성공 시 RememberMeServices.loginSuccess()를 통해 RememberMe 토큰을 생성하고 쿠키로 전달
    • 인증 실패 시 RememberMeServices.loginFail()을 통해 쿠키를 지움
    • LogoutFilter와 연계해서 로그아웃 시 쿠키를 지움

 

토큰 생성

  • 기본적으로 암호화된 토큰으로 생성되며 브라우저에 쿠키를 보내고, 향후 세션에서 이 쿠키를 감지하여 자동 로그인이 이루어지는 방식으로 달성
    → 단방향으로 암호화가 되기 때문에 복호화가 불가하여 중요한 데이터가 노출되지 않음
    (단, 탈취한 토큰으로 인증은 가능)
base(username + ":" + expirationTime + ":" + algorithmName + ":" 
+ algorithmHex(username + ":" + expirationTime + ":" + password + ":" + key))
  • username : UserDetailsService로 식별 가능한 사용자 이름
  • password : 검색된 UserDetails에 일치하는 비밀번호
  • expirationTime : remember-me 토큰이 만료되는 날짜와 시간(밀리초 표현)
  • key : remember-me 토큰의 수정을 방지하기 위한 개인 키
  • algorithmName : rememeber-me 토큰 서명을 생성하고 검증하는 데 사용(기본적으로 SHA-256 알고리즘 사용)

 

RememberMeServices 구현체

  • TokenBasedRememberMeServices쿠키 기반 토큰의 보안을 위해 해싱 사용(메모리에 저장하는 메모리 방식)
  • PersistentTokenBasedRememberMeServices → 생성된 토큰을 저장하기 위해 데이터베이스나 다른 영구 저장 매체 사용
  • 두 구현 모두 사용자의 정보를 검색하기 위한 UserDetailsService 필요

 

rememberMe() API

  • RememberMeConfigurer 설정 클래스를 통해 여러 API 설정 가능
  • 내부적으로 RememberMeAuthenticationFilter 가 생성되어 자동 인증 처리 담당
http.rememberMe(httpSecurityRememberMeConfigurer -> httpSecurityRememberMeConfigurer
.alwaysRemember(true) // "기억하기(remember-me)" 매개변수가 설정되지 않았을 때에도 항상 기억하기 사용
 .tokenValiditySeconds(3600) // 토큰이 유효한 시간(초 단위)을 지정 가능
 .userDetailsService(userDetailService) // UserDetails 를 조회하기 위해 사용되는 UserDetailsService를 지정
.rememberMeParameter("remember") // 로그인 시 사용자를 기억하기 위해 사용되는 HTTP 매개변수, 기본값은 remember-me
 .rememberMeCookieName("remember") // 기억하기(remember-me) 인증을 위한 토큰을 저장하는 쿠키 이름, 기본값은 remember-me
.key("security") // 기억하기(remember-me) 인증을 위해 생성된 토큰을 식별하는 키를 설정
)

IndexController 코드

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {
    @GetMapping("/")
    public String index(){
        return "index";
    }

    @GetMapping("/loginPage")
    public String login(){
        return "loginPage";
    }
}

 

SecurityConfig 코드

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .authorizeHttpRequests( auth -> auth.anyRequest().authenticated())
                .formLogin(Customizer.withDefaults())

                .rememberMe(rememberMe -> rememberMe
//                .alwaysRemember(true)  // "기억하기" 매개변수가 설정되지 않았을 때에도 쿠키가 항상 생성되어야 함
                .tokenValiditySeconds(3600)  // 토큰이 유효한 시간(초 단위)
                .userDetailsService(userDetailsService())  // UserDetails를 조회하기 위해 사용되는 UserDetailsService 지정
                .rememberMeParameter("remember")  // 로그인 시 사용자를 기억하기 위해 사용되는 HTTP 매개변수
                .rememberMeCookieName("remember")  // 기억하기 인증을 위한 토큰을 저장하는 쿠키 이름
                .key("security")); // 기억하기 기능을 사용하기 위한 키

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService(){
        UserDetails user = User.withUsername("user").password("{noop}1111").roles("USER").build();
        return  new InMemoryUserDetailsManager(user);
    }

}

테스트

  • localhost:8080 접속하여 Remember me 체크 후 로그인
    • F12 개발자 도구에서 remember 쿠키가 생성된 것 확인
    • JSESSION ID 우 클릭 → Delete

  • localhost:8080 재 접속
  • → 쿠키가 없었다면 loginPage로 이동되었어야 함


  • localhost:8080 Remember me 체크하지 않고 로그인
    • F12 개발자 도구에 remember 쿠키 없는 것 확인
    • JSESSIONID 삭제 후 재 접속

  • loginPage로 리다이렉트


  • RememberMe는 기억하기 위해 쿠키를 만듦
  • JSESSIONID가 삭제 되더라도 쿠키만 남아있으면 인증을 거치지 않음(자동 로그인)
  • 로그아웃 시 LogoutFilter와 연계하여 함께 삭제