본문 바로가기

Spring Boot/Security 쿠키,세션 기반 인증,인가

Remember-Me : 설정

 - Spring Serurity Remember-Me 설정법

 - 기본 구성 요소(rememberMe(), 쿠키 이름, 만료 시간, 키) 설정하기

 - 사용자 데이터베이스와 연동하여 Remember-Me 인증 적용하기

 - 영구 토큰 저장소를 활용하는 방법

 - 토큰 검증 로직 커스터마이징


Remember-Me 기본 설정하기

Spring Security 에서 HttpSecurity.rememberme() 메서드로 손쉽게 설정할 수 있음

이 기능을 통해 사용자가 로그인할 때 remember-me 옵션을 선택하면, 브라우저를 닫아도 일정 기간 자동 로그인이 유지됨

 

1. 기본 구성 요소

옵션 설명
rememberMeParameter 로그인 폼에서 전달되는 파라미터 이름
(기본값 : remember-me)
rememberMeCookieName 생성되는 쿠키 이름(기본값 : remember-me)
tokenValiditySeconds 쿠키의 유효 시간(초 단위)
key 쿠키 서명에 사용되는 고정 키

 

    @Bean
    @Order(1)
    public SecurityFilterChain sessionFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/login","/session-expired", "/css/**").permitAll()
                        .anyRequest().authenticated()
                )
                .rememberMe(remember -> remember
                        /**
                        * TokenBasedRememberMeServices, DataSource 주입이 필요하지 않음
                        */
                        .key("my-remember-key") // 쿠키 생성 시 사용되는 고정 키
                        .rememberMeCookieName("my-remember-me") // 쿠키의 이름
                        .rememberMeParameter("remember-me") // 로그인 폼에서 사용하는 파라미터명
                        .tokenValiditySeconds(7 * 24 * 60 * 60) // 쿠키 만료 시간
                        .userDetailsService(new InMemoryUsers().userDetailsService()) // 사용자 검증 서비스 추가
                );
        return http.build();
    }

 

rememberMe 설정을 통해 사용자가 remember-me 옵션을 체크하면, 쿠키가 생성되어 자동 로그인이 동작한다

 

  • key 값은 반드시 예측 불가능한 값을 사용해야한다
  • tokenValiditySeconds 는 너무 길게 잡으면 보안상 위험할 수 있다
  • 쿠키 이름을 프로젝트에 맞게 커스터마이징하여 충돌을 방지하는게 좋다

 

항목 기본값 실무 권장
rememberMeParameter() remember-me 상황에 맞게 변경하기
rememberMeCookieName() remember-me 프로젝트 전용 이름 사용하기
tokenValiditySeconds 14일 서비스 보안 수준에 맞춰서 조정하기
key 랜덤한 문자열 환경 변수 / 설정 파일로 관리하여 감추기

사용자 데이터베이스 연동

Remember-Me 기능을 실제 사용자 데이터베이스와 연동하면 실무에 가깝게 환경을 구성할 수 있다

UserDetailsService 를 활용하여 DB에서 사용자를 조회하도록 설정한다

 

User.java

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;

@Entity
@Setter
@Getter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    private String username;

    @Column
    private String password;
}

 

 

UserRepository.java

import com.b1uffer.multisessiontest.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

 

CustomUserDetailsService.java

import com.b1uffer.multisessiontest.repository.UserRepository;
import lombok.AllArgsConstructor;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
@AllArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username)
                .map(user -> User.withUsername(user.getUsername())
                        .roles("USER")
                        .build())
                .orElseThrow(() -> new UsernameNotFoundException("User not found : " + username));
    }
}

 

구현한 CustomUserDetailsService 를 SecurityConfig 에 주입하면, Remember-Me 인증이 DB 사용자와 연동된다

 

                .rememberMe(remember -> remember
                        /**
                        * TokenBasedRememberMeServices, DataSource 주입이 필요하지 않음
                        */
                        .key("my-remember-key")
                        .rememberMeCookieName("my-remember-me")
                        .rememberMeParameter("remember-me")
                        .tokenValiditySeconds(7 * 24 * 60 * 60)
                        .userDetailsService(customUserDetailsService) // 여기 
                );

 

 

  • 반드시 Password는 인코딩된 값을 사용해야한다
  • DB 조회 과정에서 예외가 발생하면 자동 로그인이 실패하므로, 적절한 예외 처리와 로그 기록이 필요하다

 

 

정리

항목 설명
UserDetailsService 사용자 정보를 DB에서 조회
PasswordEncoder 비밀번호를 안전하게 관리
예외 처리 사용자 없음 - > UsernameNotFoundException 발생

 


영구 토큰 저장소 구현하기

쿠키 기반 Remember-Me 는 보안성이 낮기 때문에
실무에서는 PersistentTokenBasedRememberMeServices 를 활용하여 영구 토큰 저장소를 DB에 구성하는게 일반적이다

 

schema.sql

CREATE TABLE persistent_logins (
                                   username VARCHAR(64) NOT NULL,
                                   series VARCHAR(64) PRIMARY KEY,
                                   token VARCHAR(64) NOT NULL,
                                   last_used TIMESTAMP NOT NULL
);

 

 

Security 설정

                .rememberMe(remember -> remember
                        /**
                        * TokenBasedRememberMeServices, DataSource 주입이 필요하지 않음
                        */
                        .key("my-remember-key") // 쿠키 생성 시 사용되는 고정 키
//                        .rememberMeCookieName("my-remember-me") // 쿠키의 이름
//                        .rememberMeParameter("remember-me") // 로그인 폼에서 사용하는 파라미터명
                        .tokenValiditySeconds(7 * 24 * 60 * 60) // 쿠키 만료 시간
                        .userDetailsService(customUserDetailsService) // 사용자 검증 서비스 추가
                        /**
                         * PersistentTokenBasedRememberMeServices, FilterChain 에 DataSource 주입이 필요함
                         */
                        .tokenRepository(persistentTokenRepository(dataSource)) // 여기
                );

 

이 방식은 사용자의 Remember-Me 정보를 DB에서 관리하므로(persistent_logins), 쿠키만 탈취해서는 재사용이 불가하다

 

 

  • DB에 저장된 토큰은 주기적으로 정리(clean-up) 하는 작업이 필요하다
  • JdbcTokenRepositoryImpl 은 기본 구현체로, 필요하다면 커스터마이징이 가능하다

 

항목 쿠키 기반 영구 토큰 기반
저장 위치 브라우저 쿠키 서버 DB
보안성 낮음 높음
성능 빠름 DB 부하 존재

토큰 검증 로직 커스터마이징하기

기본 구현체만으로도 충분하긴 하지만, 특정 서비스 환경에서는 토큰 검증 로직을 커스터마이징 할 수 있다

import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;

import java.util.Date;

public class CustomTokenRepository extends JdbcTokenRepositoryImpl {
    @Override
    public void updateToken(String series, String tokenValue, Date lastUsed) {
        super.updateToken(series, tokenValue, lastUsed);
        // 커스터마이징 로직, 로그 기록 추가 등
        System.out.println("Token updated for series : " + series + " and tokenValue : " + tokenValue);
    }
}

 

커스터마이징 하면 토큰 갱신 시 특정 동작(로그 기록, 모니터링 등)을 추가할 수 있다

 

 

  • 대규모 서비스에서는 토큰 검증 및 갱신 과정을 로깅하여 장애 대응을 용이하게 해야 한다
  • 토큰 검증 실패시, 적절한 알림 시스템을 연동하면 보안성 강화에 도움이 된다고 함

 

항목 설명
기본 구현체 JdbcTokenRepositoryImpl
커스터마이징 updateToken()
getTokenForSeries() 오버라이드
활용 로그 기록, 모니터링, 보안 알림 등