세션 기반 로그인 vs Stateless: CSRF/Remember-me/세션관리
웹(서버 렌더링)과 API(SPA/모바일)는 인증 상태를 유지하는 방식이 다릅니다. 전통적인 세션 기반 로그인은 서버 세션과 쿠키에 의존하고, Stateless API는 토큰(JWT 등)으로 상태를 유지합니다. 이 글은 두 방식을 서로 다른 SecurityFilterChain으로 분리해 구성하는 실전 패턴을 설명합니다.
1) 핵심 비교
- 세션 기반: 서버 메모리/스토리지에 세션 상태 저장, CSRF 보호 필요, Remember-me 옵션 사용 가능
- Stateless: 세션 상태 없음, CSRF 비활성, Bearer 토큰으로 인증(만료·회전 정책 포함)
2) 두 개의 SecurityFilterChain
@Configuration
@EnableMethodSecurity
public class DualChainSecurity {
@Bean @Order(1)
SecurityFilterChain api(HttpSecurity http) throws Exception {
http.securityMatcher("/api/**")
.csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.POST, "/api/auth/**").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(oauth -> oauth.jwt()); // 또는 httpBasic/bearerResolver 커스터마이즈
return http.build();
}
@Bean @Order(2)
SecurityFilterChain web(HttpSecurity http, PersistentTokenRepository ptr) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/css/**", "/js/**").permitAll()
.anyRequest().authenticated())
.formLogin(fl -> fl
.loginPage("/login")
.defaultSuccessUrl("/")
.failureUrl("/login?error"))
.rememberMe(rm -> rm
.tokenRepository(ptr)
.tokenValiditySeconds(60 * 60 * 24 * 14) // 14일
.userDetailsService(new AppUserDetailsService(/* inject */)))
.sessionManagement(sm -> sm
.sessionFixation(sf -> sf.migrateSession())
.maximumSessions(1) // 동시 세션 1개 제한
.expiredUrl("/login?expired"))
.csrf(csrf -> csrf.ignoringRequestMatchers("/webhook/**")); // 필요한 경로만 제외
return http.build();
}
}
3) Remember-me 토큰 저장소(JDBC)
@Configuration
public class RememberMeConfig {
@Bean
public PersistentTokenRepository persistentTokenRepository(DataSource ds) {
JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
repo.setDataSource(ds);
// 최초 1회: repo.setCreateTableOnStartup(true);
return repo;
}
}
Remember-me는 자동 로그인용 장기 쿠키입니다. 보안을 위해 HTTPS + Secure + HttpOnly를 기본으로 사용하세요.
4) 세션 보안 포인트
- 세션 고정 방지: 로그인 성공 시
migrateSession()로 세션 ID 교체 - 동시 세션 제한: 계정 공유/탈취 감지에 유용
- 세션 스토리지: 대규모 서비스는 Spring Session + Redis로 외부화
5) 쿠키 SameSite & 보안 속성
Boot 3.x는 서버 설정만으로 SameSite 제어가 가능합니다.
server:
servlet:
session:
cookie:
secure: true
http-only: true
same-site: lax # SPA/OAuth 리디렉트 시에는 "none"(+ Secure) 고려
6) 로그아웃/토큰 무효화
- 세션 기반:
http.logout()으로 세션 무효화 + 쿠키 삭제 - Stateless: 서버 저장소(예: Redis)에 리프레시 토큰을 저장하고, 로그아웃 시 해당 토큰 폐기
7) SPA & API 조합 현실 팁
- 원 도메인이라면 SameSite=Lax 쿠키로도 무난히 동작
- 서브도메인 간 교차라면 SameSite=None + Secure + CORS 구성이 필요
- API는 401/403을 명확히 구분, 프런트는 401에서 로그인 라우팅
8) CORS(선택)
@Configuration
public class CorsCfg {
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration c = new CorsConfiguration();
c.setAllowedOrigins(List.of("https://app.example.com"));
c.setAllowedMethods(List.of("GET","POST","PUT","DELETE"));
c.setAllowedHeaders(List.of("*"));
c.setAllowCredentials(true);
UrlBasedCorsConfigurationSource s = new UrlBasedCorsConfigurationSource();
s.registerCorsConfiguration("/**", c);
return s;
}
}
9) 선택 가이드
- 서버 템플릿(Thymeleaf 등) 위주의 웹: 세션 기반 + CSRF 유지 + Remember-me
- SPA/모바일 API: Stateless + Bearer(JWT) + CSRF 비활성 + 토큰 회전
- 두 세계가 공존하면 다중 체인으로 분리
⏪ 이전 글 보기: AuthenticationProvider & UserDetailsService 커스터마이징
⏩ 다음 글 보기: JWT 심화: 만료/클레임/키 회전 + 4편과의 연결 전략
⏩ 다음 글 보기: JWT 심화: 만료/클레임/키 회전 + 4편과의 연결 전략
👉 1편: SecurityFilterChain 완전정복: 요청 매칭/인가 룰 제대로 이해하기
👉 2편: PasswordEncoder와 회원가입: BCrypt·Pepper·비밀번호 정책
👉 3편: AuthenticationProvider & UserDetailsService 커스터마이징
👉 4편: 세션 기반 로그인 vs Stateless: CSRF/Remember-me/세션관리
👉 5편: JWT 심화: 만료/클레임/키 회전 + 4편과의 연결 전략
👉 6편: OAuth2 로그인(Google/GitHub) + JWT 브릿지
👉 7편: 권한 모델링: ROLE vs 권한 문자열, @PreAuthorize, 도메인 권한
👉 8편: CORS·XSS·헤더 보안: SPA/REST 현실 설정
'Java & Spring' 카테고리의 다른 글
| OAuth2 로그인(Google/GitHub) + JWT 브릿지 (0) | 2025.10.13 |
|---|---|
| JWT 심화: 만료/클레임/키 회전 + 4편과의 연결 전략 (0) | 2025.10.13 |
| AuthenticationProvider & UserDetailsService 커스터마이징 (0) | 2025.10.12 |
| PasswordEncoder와 회원가입: BCrypt·Pepper·비밀번호 정책 (1) | 2025.10.11 |
| SecurityFilterChain 완전정복: 요청 매칭/인가 룰 제대로 이해하기 (0) | 2025.10.11 |