- 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() 오버라이드 |
| 활용 | 로그 기록, 모니터링, 보안 알림 등 |
'Spring Boot > Security 쿠키,세션 기반 인증,인가' 카테고리의 다른 글
| 세션 기반 사용자 인가 구현 : 인증과 인가의 관계 (0) | 2026.05.14 |
|---|---|
| Remember-Me : 보안 (0) | 2026.05.14 |
| Remember-Me : 구현 방식 (1) | 2026.05.12 |
| Remember-me : 인증 개념과 필요성 (0) | 2026.05.11 |
| 동시 세션 제어와 세선 고정 보호 : 세션 고정 보호 (0) | 2026.05.10 |