[JSP/Servlet] 로그인 인증 구조 정리 : Session, Remember-Me
목차
1. Session 로그인 방식 선택 이유
2. Remember-Me 방식 추가 리팩토링
3. Session vs Remember-Me 비교
4. Session 방식의 한계와 확장 방향
5. 부록 : [JavaScript] 아이디 저장 기능 - 로컬 스토리지 (localStorage)
1. Session 로그인 방식 사용의 이유
세미프로젝트 때 구현한 로그인 기능 기반으로, HTTP의 Stateless 특성을 극복하는 세 가지 인증 방식을 정리하고자 함.
HTTP는 요청과 응답이 끝나면 연결 상태를 저장하지 않는 Stateless 프로토콜이기 때문에, 별도 저장소 없이는 페이지를 이동할 때마다 다시 로그인해야 하는 문제가 생김. 따라서 세미 프로젝트의 로그인 기능 구현 시 JSP/Servlet의 기본 인증 방식인 Session 방식을 사용하였음. 로그인 성공시 서버 메모리에 사용자 정보를 저장하고, JSESSIONID 쿠키로 해당 세션을 식별하는 방식. (로그인 요청 -> Controller 인증 처리 -> session.setAttribute() -> JSESSIONID 쿠키 발급)

이는 개발자도구 f12 -> Application -> Cookies -> JSESSIONID 에서 확인 가능함.

전체적인 흐름은 하기와 같다. (브라우저에서 로그인 요청 -> 서버 메모리에 세션 생성 -> JSESSIONID로 쿠키 발급 -> 브라우저 쿠키 저장소에 저장 -> 이후 모든 요청에 쿠키 포함.)

다만, 브라우저 종료 혹은 세션 만료(서버 설정--> web.xml 에서 세션은 30분동안 저장되도록 설정함.)시 브라우저 종료 후 재접속하면 세션이 만료되어 사용자 편의성이 떨어지는 문제가 있어, 세션이 만료되더라도 자동으로 로그인 상태를 복구할 수 있도록 Remember-Me(자동 로그인) 기능을 추가함.

2. Remember-Me 방식 추가로 리팩토링 진행
※ Remember-Me : Session이 없을 때 토큰 기반으로 Session을 재생성하는 구조.
리팩토링 부분
1) DB 테이블 추가 : Remember_me 테이블 --> 사용자 로그인 상태를 토큰으로 기억.
2) LoginController 추가 --> 토큰 생성, DB 저장, 쿠키 저장
3) DAO 구현 : 토큰 저장, 토큰 조회(token으로 회원 찾기), 토큰 삭제 ---> 의미 : "아이디/비밀번호" 대신 "토큰" 사용
4) Filter 추가
컨트롤러 추가 부분
// 추가 (remember me)
String rememberMe = request.getParameter("rememberMe");
if ("Y".equals(rememberMe)) {
String token = java.util.UUID.randomUUID().toString();
// DB에 userid, token, expire_at 저장
mdao.saveRememberMeToken(loginuser.getUserid(), token);
jakarta.servlet.http.Cookie rememberCookie =
new jakarta.servlet.http.Cookie("rememberMe", token);
rememberCookie.setMaxAge(60 * 60 * 24 * 7); // 7일
rememberCookie.setHttpOnly(true);
rememberCookie.setPath(request.getContextPath());
response.addCookie(rememberCookie);
}
Filter 생성 : 세션 확인 (로그인시 자동로그인 검사 뛰어넘고) -> rememberMe 토큰 있으면 DB에서 token 조회 -> 토큰 유효하면 session 다시 생성함.
package hk.login.filter;
import java.io.IOException;
import hk.member.domain.MemberDTO;
import hk.member.model.MemberDAO;
import hk.member.model.MemberDAO_imple;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
public class RememberMeFilter implements Filter {
private MemberDAO mdao = new MemberDAO_imple();
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
// 이미 로그인 상태면 자동로그인 검사 필요 없음
if (session == null || session.getAttribute("loginuser") == null) {
String rememberToken = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("rememberMe".equals(cookie.getName())) {
rememberToken = cookie.getValue();
break;
}
}
}
if (rememberToken != null) {
try {
MemberDTO loginuser = mdao.findUserByRememberMeToken(rememberToken);
if (loginuser != null) {
HttpSession newSession = request.getSession();
newSession.setAttribute("loginuser", loginuser);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
chain.doFilter(request, response);
}
}

JSESSIONID는 서버 메모리에 있는 session을 찾는 키이고, 30분동안 유지되며 현재 로그인 상태를 유지하고 있다는 뜻임. rememberMe토큰은 세션이 없을 때 DB 기반으로 로그인을 복구시키는 키이며, 7일동안 유지되도록 셋팅함. rememberMe 토큰을 추가한 후 로그인 동작 흐름은 하기와 같음.(즉, 7일동안 자동로그인이 유지되는 것. 세션 만료되고 다시 접속시 재생성 -> 또 만료되면 재접속후 재생성 반복 ...)
※ 로그인 시 session 생성 및 JSESSIONID 발급 (기존과 동일) -> 자동로그인 체크시 -> rememberMe 토큰 생성 -> DB저장 + 쿠키 저장
※ 세션 만료 시 서버 재시작 -> JSESSIONID 삭제 -> 자동 로그인 작동 -> rememberMe 쿠키 확인 -> DB에서 토큰 조회 -> session 재생성 -> 다시 JSESSIONID 발급 -> 로그인 유지

3. Session VS Remember-Me 비교

정리하면, 세션은 현재 로그인 상태를 유지하는 방식이고, Remeber-Me는 세션이 사라진 이후 로그인 상태를 복구하는 구조임. 처음에는 remember me 방식은 그러면 단순히 로그인 유지 시간을 늘리는 건가..? 라고 생각했는데, 세션이 만료되면, db에서 토큰 기반으로 인증 후 Session을 재생성하는 자동 로그인 구조라는 것임. (다만 결과적으로 로그인 유지 시간이 기존 30분에서 7일 단위로 늘어난 것은 맞음) 처음에 Remember-Me 기능을 구현하면서, 세션이 30분마다 만료되기 때문에 자동으로 계속 재생성되는 구조라서 서버에 부담이 되는거 아닌가?.. 하는 의문이 생겼지만 주기적으로 재생성되는 것이 아니라... 세션이 만료되면 토큰을 통해 세션을 재성성하는 것임. 즉, 처음 로그인 후 30분 이상 시간이 지나면서 재접속시에만 세션이 생성되는 구조.

4. Session 방식의 한계와 확장 방향
Session은 서버 메모리에 저장되기 때문에, 사용자가 많아질수록 메모리 사용량이 증가하고, 서버가 여러 대일 때 세션 공유 문제(Load Balancing)가 발생할 수 있음.
해결 방법은 3가지가 있음.
1. Redis (세션을 외부 Redis 서버에 저장)
2. Spring Session (기존 HttpSession을 Redis / DB / Hazelcast 등에 저장하도록 변경)
3. JWT 방식 전환 (서버에 상태를 저장하지 않는 Stateless 인증 토큰 방식)
파이널 프로젝트 때 팀원 중 한 분이 로그인 구조를 session 방식 -> jwt 방식 전환을 해서, 간접경험은 해봤기 때문에... (기회가 된다면 전환 내용도 추가적으로 포스팅 하겠음.) 플러스로 JWT는 Session을 없애는 방향인 반에 Redis는 기존 Session 구조를 유지하는 방향이기 때문에, 다음 단계에서는 Redis를 통해 세션 저장소를 외부로 빼보고자 함...
+ 더해서 이번에는 JSP/Servlet 환경에서 Remember-Me를 직접 구현하였지만, 이 구조는 Spring Security의 Remember-Me 기능으로 확장할 수 있음. 다음과 같은 방향으로 개선할 수 있다고 함...

5. 부록 : [JavaScript] 아이디 저장 기능 - 로컬 스토리지 (LocalStorage)
localStorage는 Session/Cookie 인증 구조와 크게 관련은 없지만, 사용자 편의를 위한 프론트 기능이므로 별도 부록으로 정리하였음. 아이디 저장 기능은 서버(Session/DB)가 아닌 브라우저의 localStorage를 활용하여 프론트 단에서 처리함.
※ Local Storage : HTML5에서 도입된 Weg Storage API임. 브라우저에 데이터를 키-값(Key-Value) 쌍으로 저장하는 클라이언트 측 저장소. 서버나 DB를 거치지 않고 브라우저 자체에 데이터를 보관함. 객체나 배열을 저장하려면 JSON.stringify()로 직렬화하는 작업 필요함.


주요 메서드
// 저장
localStorage.setItem("saveid", "hong123");
// 조회
localStorage.getItem("saveid"); // → "hong123"
// 삭제
localStorage.removeItem("saveid");
// 전체 초기화
localStorage.clear();
동작흐름은 다음과 같다.
1. JSP 로드시 JS 자동 실행 -> localStorage.gettem("saveid") -- 페이지 로딩시 아이디 자동 세팅 (브라우저에 저장된 값을 가져와 input에 자동 입력함.)
const savedId = localStorage.getItem("saveid");
if(savedId) {
$("#userid").val(savedId);
$("#saveid").prop("checked", true);
}
2. 체크 여부에 따라 저장/삭제 처리
if($("#saveid").is(":checked")) {
localStorage.setItem("saveid", $("#userid").val());
} else {
localStorage.removeItem("saveid");
}
로그인 시 아이디 저장 체크 하면, 브라우저 localStorage에 아이디 저장됨. 개발자도구(Application -> Local Storage)에서 저장된 값(키 : "saveid" / 값 : "test") 확인 가능함. 세션스토리지와 달리 직접 지우기 전 까지 영구적임.

보안 주의 : localStorage는 JavaScript로 자유롭게 접근 가능하므로 비밀번호, 토큰, 개인정보는 저장하면 안 됨. 저장된 값은 F12 -> Application -> Local Storage 에서 확인 가능함.
British Airways 데이터 유출 사건은 실제 악성 JavaScript를 삽입하여 사용자 입력정보를 실시간을 탈취한 Web Skimming 공격사례이며 하기 링크를 통해 참고 가능함.
참고 : https://en.wikipedia.org/wiki/British_Airways_data_breach?utm_source=chatgpt.com