Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- JPA
- MVC요청플로우
- 공통규약
- JDBC
- SEQUENCE
- Transaction
- 사면초가
- 이벤트핸들러this
- 커뮤니티서버
- 공통기술
- 냄새라도
- 단위테스트
- AOP
- index
- 이벤트핸들러등록
- MVC
- springsecurity
- 전전긍긍
- OOP
- model
- string
- DDD
- SpringLegacy
- 클린아키텍처
- 설정세팅
- Ajax
- 로그백
- 문제선택근거
- TDD
- 디스크i/o
Archives
- Today
- Total
개발이군고구마
[Spring Security] redis/DB로 자동로그인 구현 본문
728x90
Problem
- 자동로그인 시도시 로그인 ""지연"" 현상
- 자동로그인을 해놓을 경우 다른 기기에서 로그인을 하더라도 해당 정보 계속해서 유지됨
- 예를 들어 타 기기에서 의도치 않게 자동로그인이 되면 계속해서 그 정보가 남아 타인이 계정에 계속 접속하는 것이 가능하여 ""보안"" 우려
Cause
- 자동로그인 토큰 검색을 위해 매번 DB 조회
- 특히, DB 버퍼에 쿼리 캐시가 없다면 디스크 I/O 필요하므로 시간, CPU 비용이 상승하게 되는 것
- 자동로그인 여부 확인 시 따로 ""기기 식별 번호"" 를 검증하는 로직이 없음
- REDIS 조회 {완전로그인(KEY) : UTID}
- DB 조회 : WHERE -> 완전로그인
Solution
1️⃣ REDIS 연동
- 완전 로그인 토큰을 key 값으로 데이터 insert
- 만료시간은 15일
- 참고) remember-me 쿠키 값 만료날짜는 30일
1) 최초로그인

- 첫 로그인 시에는 로그인 url 로 들어오기 때문에 password filter를 타게 됨
- password filter 이후 remeber me 서비스 실행
if (authenticationIsRequired(username)) {
Authentication authResult = this.authenticationManager
.authenticate(authRequest); ** 인증 시작 (PROVIDER, SERVICE- loadByUsername, UserDetail)
if (debug) {
this.logger.debug("Authentication success: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult); ** 세션 생성
this.rememberMeServices.loginSuccess(request, response, authResult); ** remember me 쿠키값
onSuccessfulAuthentication(request, response, authResult);
}
2) 최초 로그인 이후 세션 유지 상태
- security context 있는 경우 remember me filter를 생략함
if (SecurityContextHolder.getContext().getAuthentication() == null) { ** ✨세션이 없는 경우만
Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
response);
...
chain.doFilter(request, response);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
chain.doFilter(request, response);
}
3) 세션 만료 이후 자동로그인

2️⃣ COOKIE 에 [완전로그인+자동로그인]을 조합한 값을 인코딩하여 DB 조회시 인덱싱 효과
- DB 조회시에 조건이 2개이 걸려 한 행만 조회하여 검색 시간이 단축됨
3️⃣ 다른 기기로 로그인할 경우 완전로그인 KEY 값에 대한 데이터 삭제
- REDIS 가져온 기기 식별자와 다른 모든 식별번호(행)를 삭제 (완전로그인 - 현재 로그인한 기기)
- DB 가져온 자동로그인 토큰외에 모든 자동로그인 토큰 삭제 (완전로그인 - 당시 로그인한 자동로그인 토큰)
- REDIS / DB 완전로그인 인자에 대해 기기 식별 번호에 대한 정보만 가지고 있기 때문에 다른 디바이스로 로그인 하는 경우 값이 없는 걸로 인지하여 로그인 불가
[why & what if]
? 왜 REDIS를 사용했는가, 다른 옵션을 없었을까
? 왜 자동로그인 토큰 Persistent Remember-Me 토큰 은 매번 유저가 접속할 때마다 갱신되는 것일까?
- 자동로그인은 쿠키 기반이라 탈취 위험이 높기 때문에,
“한 번 사용된 토큰은 즉시 폐기”하는 보안 정책을 적용하기 위해 매번 토큰을 바꾼다고 함 ( Replay 공격 방지)
? 자동로그인 토큰 쿠키 값이 만료되더라도 DB 값들은 삭제되지 않는 것일까
? JWT스럽게 개선하려면 어떤 아키텍처가 될 수 있을까
Impact
? HTTP SESSION의 값은 어디에 저장되는 것일까? SERVLET의 가장 앞단에 위치하는 것일까
- (시점) 세션 객체가 처음 만들어지는 순간 = request.getSession(true) 가 호출되는 순간 (filter들의 success handler)
- HTTP SESSION 객체 (MAP형태)를 생성함
- 이후 HTTP SESSION 객체에 SET 을 할 때마다 쿠키에 저장되는 ""공통 메서드"" 정의함
public void onAuthenticationSuccess(NcpAuthenticaitonRequest ncpAuthenticaitonRequest)
throws IOException, ServletException {
HttpServletRequest request = ncpAuthenticaitonRequest.getRequest();
HttpServletResponse response = ncpAuthenticaitonRequest.getResponse();
Authentication authentication = ncpAuthenticaitonRequest.getAuthentication();
.....
Map<String, Object> rsltData = null;
try {
HttpSession session = request.getSession();
session.setAttribute("SPRING_SECURITY_CONTEXT", context); ** 쿠키 JSESSION ID로 저장
- (영역) Servlet Container 내부의 Session Manager 메모리 영역에 HTTP SESSION 객체 저장
- 그런데 어차피 쿠키에 http session 객체를 인코딩 해서 집어넣고, 가져와서 디코딩으로 푼 후에 인증을 하기 때문에 메모리에 큰 의미는 없지 않을까 싶음.
? AUTHENTICATE와 같은 유저 정보 또는 SESSION ID 는 매번 유저가 로그인 할 때마다 갱신되는 것인지
- 아니다. 모두 재인증일 때만 갱신된다
- Authentication(유저 정보)
- ✅ “로그인(재인증)에 성공할 때마다” 새로 세팅된다.
- UsernamePasswordAuthenticationFilter 나 RememberMeAuthenticationFilter 가 인증에 성공
- SecurityContextHolder.getContext().setAuthentication(newAuth);
- ❌ 단순 요청/페이지 이동에서는 갱신 안 하고 기존 Authentication 재사용.
- 이미 세션이 있고, 거기에 SPRING_SECURITY_CONTEXT 가 들어있으면 SecurityContextPersistenceFilter 가 그 값을 그냥 꺼내서 ThreadLocal에 올려 준다.
- ✅ “로그인(재인증)에 성공할 때마다” 새로 세팅된다.
- SESSION ID(JSESSIONID)
- ✅ 인증 성공 시마다 세션 ID를 새로 발급해서 바꾸는 게 기본이다.
- ❌ 이미 로그인된 상태에서 그냥 요청만 하는 동안에는 그대로 유지된다.
- Authentication(유저 정보)
? 세션 정보도 있고 REMEMBER ME 정보도 쿠키에 있는 경우 RememberMeServices.loginSuccess()를 통해 REDIS 및 DB 에 자동로그인 토큰 값이 갱신이 되나?
- 아니다. ID/PW로 명시적으로 로그인(로그인 URL)하면서 remember-me를 요청(true)했거나, 세션 정보가 없는 경우만 갱신
- UsernamePasswordAuthenticationFilter 같은 “로그인 처리 필터”가 인증에 성공했을 때 (로그인 URL이 있을 때만 타는 필터임) successfulAuthentication() 안에서 성공 핸들러 → RememberMeServices.loginSuccess() 를 호출하는 구조
- 세션이 있다면 rememberfilter를 건너뜀
| 세션 O + Remember-Me 쿠키 O | ❌ 실행 안함 | ❌ 갱신 안됨 |
| 세션 X + Remember-Me 쿠키 O | ✔ 실행 | ✔ 갱신 |
| Remember-Me 쿠키 없음 | ❌ 실행 안함 | ❌ 없음 |
? 완전로그인토큰/자동로그인토큰 생성 방식과 JWT 토큰 생성방식과의 유사점과 사용 맥락
- 완전로그인토큰/자동로그인토큰 = 서버 저장소 기반의 랜덤 토큰(key-value 기반)
JWT = 자체 정보·서명 포함한 self-contained 토큰
- 유사점
| 완전로그인/자동로그인 토큰 | JWT | |
| 클라이언트가 토큰을 보관 | 쿠키(Remember-me) 사용 | 쿠키/Authorization 헤더 |
| 토큰을 서버에 다시 보내 인증을 복구 | 쿠키에 있는 Series/Token 조합으로 DB 조회 | JWT 자체에 포함된 Signature 검증 |
| 세션 없이도 인증 가능 | 세션 끊겨도 자동 로그인 | 원래 세션不要 방식 |
| 유출되면 위험 | 쿠키 탈취 → 계정 탈취 | 토큰 탈취 → 계정 탈취 |
| 토큰 롤링(재발급) | 자동로그인 시마다 토큰 갱신 | Refresh Token 존재 시 롤링 |
- 차이점
- 토큰 검증 방식
- 보안 전략
- 매번 자동로그인 성공 시 토큰 롤링(Token Rotation) 필요
- Access Token은 짧은 TTL
- 토큰에 담는 내용
- 의미 없는 랜덤 문자열
- 클레임(Claims)에 사용자 정보, 권한, 만료 시간 등 내장
- 사용맥락
- 세션이 없으면 → remember-me 쿠키로 세션을 복구해주는 보조 인증, 세션 기반(Spring Security 세션)과 함께 사용
- JWT Access Token을 헤더에 넣어 API 요청 서버는 DB 조회 없이 바로 인증, 모바일 앱/SPA(React, Vue) 환경
'SERVER > Spring' 카테고리의 다른 글
| [HandlerMethodArgumentResolver] 레거시코드, 고쳐도 될까? (0) | 2025.12.31 |
|---|---|
| [Spring Batch] 수기 업무의 늪에서 탈출하기: 조건부 흐름 제어와 대용량 처리 (1) | 2025.12.02 |
| DispatcherServlet의 예외 처리 과정 (4) | 2025.07.11 |
| [Spring/DB]Transaction 과 Lock (@Transactional 사용에 대한 궁금증을 시작으로) (0) | 2025.03.08 |
| [Spring : Design Pattern] 3. AOP와 Proxy (Self Invocation부터 시작) (0) | 2024.11.09 |