[Spring] 33. 자동 로그인과 Remember-Me 구조

[Spring] 33. 자동 로그인과 Remember-Me 구조

이전 글에서는 Cookie와 Session의 차이를 살펴봤습니다. Cookie는 브라우저에 저장되고, Session은 서버에 저장됩니다. 세션 기반 로그인에서는 브라우저가 JSESSIONID 쿠키를 서버로 보내고, 서버는 이 값을 이용해 세션 저장소에서 로그인 정보를 찾습니다.

이번 글에서는 자동 로그인과 Remember-Me 구조를 정리하겠습니다. 자동 로그인은 사용자가 브라우저를 닫았다가 다시 들어와도 로그인 상태를 복원할 수 있게 만드는 기능입니다.

핵심은 간단합니다. 자동 로그인은 쿠키에 아이디와 비밀번호를 저장하는 것이 아니라, 랜덤 토큰을 쿠키에 저장하고 서버 DB에서 그 토큰을 검증하는 방식으로 구성해야 합니다.


1. 자동 로그인이란?

자동 로그인은 사용자가 다시 사이트에 방문했을 때 로그인 과정을 생략하고 로그인 상태를 복원하는 기능입니다. 보통 로그인 화면에서 “자동 로그인”, “로그인 상태 유지”, “Remember Me” 같은 체크박스로 제공됩니다.

일반 로그인
 → 브라우저를 닫거나 세션이 만료되면 다시 로그인 필요

자동 로그인
 → 브라우저를 다시 열어도 일정 기간 로그인 상태 복원 가능

자동 로그인은 편리하지만 보안상 주의가 필요합니다. 브라우저에 저장되는 쿠키를 이용하기 때문에 민감한 정보를 그대로 저장하면 안 됩니다.


2. 자동 로그인에서 절대 하면 안 되는 것

자동 로그인을 만들 때 가장 위험한 실수는 쿠키에 아이디와 비밀번호를 그대로 저장하는 것입니다.

위험한 쿠키 예시

rememberId=springUser
rememberPw=1234
role=ADMIN

쿠키는 브라우저에 저장됩니다. 사용자가 직접 볼 수도 있고, 상황에 따라 탈취될 위험도 있습니다. 따라서 비밀번호, 권한 정보, 민감한 개인정보를 쿠키에 그대로 저장하면 안 됩니다.

좋은 구조는 쿠키에 랜덤 토큰만 저장하고, 서버 DB에서 그 토큰이 유효한지 확인하는 방식입니다.

쿠키에 저장할 값
 → 자동 로그인 토큰

서버 DB에 저장할 값
 → 토큰과 회원 정보 연결

3. 자동 로그인 기본 흐름

자동 로그인의 기본 흐름은 다음과 같습니다.

1. 사용자가 로그인 화면에서 자동 로그인을 체크한다.
2. 아이디와 비밀번호를 입력하고 로그인한다.
3. 서버가 회원 정보를 확인한다.
4. 로그인 성공 시 세션에 loginMember를 저장한다.
5. 자동 로그인 체크가 되어 있으면 랜덤 토큰을 생성한다.
6. 서버 DB에 토큰과 회원 정보를 저장한다.
7. 브라우저 쿠키에도 토큰을 저장한다.
8. 이후 사용자가 다시 방문하면 쿠키의 토큰을 서버로 보낸다.
9. 서버는 DB에서 토큰을 확인한다.
10. 유효한 토큰이면 세션에 loginMember를 다시 저장한다.
11. 로그인 상태가 복원된다.

즉, 자동 로그인은 쿠키만으로 로그인하는 것이 아닙니다. 쿠키의 토큰과 서버 DB의 토큰 정보가 함께 맞아야 로그인 상태를 복원합니다.


4. 일반 세션 로그인과 자동 로그인 차이

구분 세션 로그인 자동 로그인
상태 유지 기준 서버 세션 쿠키 토큰 + 서버 DB 검증
브라우저 종료 후 세션 쿠키가 사라지면 로그인 해제될 수 있음 자동 로그인 쿠키가 남아 있으면 복원 가능
브라우저 저장 값 JSESSIONID 자동 로그인 토큰
서버 저장 값 loginMember 회원 번호, 토큰, 만료 시간
주의점 세션 만료 관리 토큰 탈취, 만료, 삭제 처리

자동 로그인은 세션 로그인을 대체한다기보다, 세션이 없을 때 토큰을 이용해 다시 세션을 만들어주는 보조 기능에 가깝습니다.


5. 자동 로그인 DB 테이블 설계

자동 로그인 토큰은 서버 DB에 저장해야 합니다. 간단한 테이블 구조는 다음처럼 설계할 수 있습니다.

컬럼 설명
remember_no 자동 로그인 번호
member_no 회원 번호
token 자동 로그인 토큰
expire_date 토큰 만료 시간
reg_date 토큰 생성 시간

실무에서는 토큰 원문을 DB에 그대로 저장하기보다 해시해서 저장하는 방식도 고려합니다. 처음 학습 단계에서는 “쿠키 토큰과 DB 토큰을 대조한다”는 흐름을 먼저 이해하면 됩니다.


6. 로그인 form에 자동 로그인 체크박스 추가하기

로그인 화면에는 자동 로그인 체크박스를 추가할 수 있습니다.

<form action="/member/login" method="post">
    <input type="text" name="memberId" placeholder="아이디">
    <input type="password" name="memberPw" placeholder="비밀번호">

    <label>
        <input type="checkbox" name="rememberMe" value="Y">
        자동 로그인
    </label>

    <button type="submit">로그인</button>
</form>

사용자가 체크하면 rememberMe=Y 값이 서버로 전달됩니다. 체크하지 않으면 해당 값이 전달되지 않을 수 있습니다.

자동 로그인 체크
 → rememberMe=Y

자동 로그인 미체크
 → rememberMe 값 없음

7. 로그인 DTO에 rememberMe 추가하기

로그인 요청 DTO에도 자동 로그인 여부를 담을 필드를 추가할 수 있습니다.

public class MemberLoginDto {

    private String memberId;
    private String memberPw;
    private String rememberMe;

    public String getMemberId() {
        return memberId;
    }

    public void setMemberId(String memberId) {
        this.memberId = memberId;
    }

    public String getMemberPw() {
        return memberPw;
    }

    public void setMemberPw(String memberPw) {
        this.memberPw = memberPw;
    }

    public String getRememberMe() {
        return rememberMe;
    }

    public void setRememberMe(String rememberMe) {
        this.rememberMe = rememberMe;
    }

    public boolean isRememberMeChecked() {
        return "Y".equals(rememberMe);
    }
}

isRememberMeChecked() 같은 메서드를 만들어두면 Controller나 Service에서 자동 로그인 체크 여부를 읽기 쉽습니다.


8. 로그인 성공 시 자동 로그인 토큰 생성하기

로그인에 성공하고 자동 로그인이 체크되어 있다면 토큰을 생성합니다. 간단한 학습 예제에서는 UUID를 사용할 수 있습니다.

String token = UUID.randomUUID().toString();

토큰은 예측하기 어려운 랜덤 문자열이어야 합니다. 실제 서비스에서는 더 강한 랜덤 토큰 생성 방식과 토큰 해시 저장을 고려하는 것이 좋습니다.

자동 로그인 토큰
 → 사용자를 식별하기 위한 임시 열쇠
 → 아이디나 비밀번호를 대신 저장하면 안 됨
 → 예측하기 어려운 랜덤 값이어야 함

9. 자동 로그인 Cookie 만들기

자동 로그인 토큰을 브라우저에 저장하려면 Cookie를 만들어 응답에 추가합니다.

private void addRememberMeCookie(
        HttpServletResponse response,
        String token
) {
    Cookie cookie = new Cookie("rememberToken", token);
    cookie.setMaxAge(60 * 60 * 24 * 14);
    cookie.setPath("/");
    cookie.setHttpOnly(true);

    response.addCookie(cookie);
}

위 코드는 rememberToken이라는 쿠키를 만들고 14일 동안 유지하도록 설정하는 예시입니다.

rememberToken 자동 로그인 토큰 쿠키 이름
setMaxAge() 쿠키 유지 시간 설정
setPath("/") 사이트 전체 경로에서 쿠키 사용
setHttpOnly(true) JavaScript에서 쿠키 접근을 제한

HTTPS 환경에서는 cookie.setSecure(true)도 고려해야 합니다. 운영 환경에서는 보안 옵션을 신중하게 설정해야 합니다.


10. 로그인 Controller에서 자동 로그인 처리하기

로그인 Controller에서 세션 로그인과 자동 로그인 토큰 발급을 함께 처리할 수 있습니다.

@PostMapping("/login")
public String login(
        MemberLoginDto memberLoginDto,
        HttpSession session,
        HttpServletResponse response,
        Model model
) {
    LoginMember loginMember = memberService.login(memberLoginDto);

    if (loginMember == null) {
        model.addAttribute("loginError", "아이디 또는 비밀번호가 올바르지 않습니다.");
        return "member/login";
    }

    session.setAttribute("loginMember", loginMember);

    if (memberLoginDto.isRememberMeChecked()) {
        String token = UUID.randomUUID().toString();

        memberService.saveRememberMeToken(loginMember.getMemberNo(), token);
        addRememberMeCookie(response, token);
    }

    return "redirect:/";
}

이 코드의 흐름은 다음과 같습니다.

로그인 성공
 → session에 loginMember 저장
 → 자동 로그인 체크 확인
 → 토큰 생성
 → DB에 토큰 저장
 → 브라우저 쿠키에 토큰 저장
 → 메인으로 redirect

11. RememberMe DTO 만들기

DB에 저장할 자동 로그인 정보를 DTO로 만들 수 있습니다.

public class RememberMeDto {

    private Long rememberNo;
    private Long memberNo;
    private String token;
    private LocalDateTime expireDate;

    public RememberMeDto(Long memberNo, String token, LocalDateTime expireDate) {
        this.memberNo = memberNo;
        this.token = token;
        this.expireDate = expireDate;
    }

    public Long getRememberNo() {
        return rememberNo;
    }

    public Long getMemberNo() {
        return memberNo;
    }

    public String getToken() {
        return token;
    }

    public LocalDateTime getExpireDate() {
        return expireDate;
    }
}

Service에서는 토큰 만료 시간을 계산해서 저장할 수 있습니다.

public void saveRememberMeToken(Long memberNo, String token) {
    LocalDateTime expireDate = LocalDateTime.now().plusDays(14);

    RememberMeDto rememberMeDto = new RememberMeDto(
            memberNo,
            token,
            expireDate
    );

    memberMapper.insertRememberMeToken(rememberMeDto);
}

12. 자동 로그인 복원 흐름

자동 로그인 복원은 사용자가 로그인하지 않은 상태로 보호된 URL에 접근했을 때 동작할 수 있습니다. Interceptor에서 세션을 먼저 확인하고, 세션이 없으면 자동 로그인 쿠키를 확인하는 방식입니다.

1. 사용자가 보호된 URL로 접근한다.
2. Interceptor가 세션의 loginMember를 확인한다.
3. loginMember가 있으면 Controller 실행을 허용한다.
4. loginMember가 없으면 rememberToken 쿠키를 확인한다.
5. 쿠키가 있으면 DB에서 토큰을 조회한다.
6. 토큰이 유효하고 만료되지 않았다면 회원 정보를 조회한다.
7. 세션에 loginMember를 다시 저장한다.
8. Controller 실행을 허용한다.
9. 토큰이 없거나 유효하지 않으면 로그인 페이지로 이동한다.

즉, 자동 로그인은 세션이 없을 때 쿠키 토큰으로 세션을 다시 만들어주는 흐름입니다.


13. 자동 로그인 Interceptor 예시

자동 로그인까지 고려한 Interceptor는 다음처럼 작성할 수 있습니다.

public class LoginCheckInterceptor implements HandlerInterceptor {

    private final MemberService memberService;

    public LoginCheckInterceptor(MemberService memberService) {
        this.memberService = memberService;
    }

    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler
    ) throws Exception {
        HttpSession session = request.getSession(false);

        if (session != null && session.getAttribute("loginMember") != null) {
            return true;
        }

        String rememberToken = findCookieValue(request, "rememberToken");

        if (rememberToken != null) {
            LoginMember loginMember = memberService.findLoginMemberByRememberToken(rememberToken);

            if (loginMember != null) {
                HttpSession newSession = request.getSession();
                newSession.setAttribute("loginMember", loginMember);
                return true;
            }
        }

        response.sendRedirect("/member/login");
        return false;
    }

    private String findCookieValue(HttpServletRequest request, String name) {
        Cookie[] cookies = request.getCookies();

        if (cookies == null) {
            return null;
        }

        for (Cookie cookie : cookies) {
            if (name.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }

        return null;
    }
}

이 Interceptor는 먼저 세션을 확인합니다. 세션 로그인 정보가 없으면 자동 로그인 쿠키를 확인하고, 토큰이 유효하면 세션을 새로 만들어 로그인 상태를 복원합니다.


14. 자동 로그인 Service 예시

Service에서는 토큰을 DB에서 조회하고, 만료 여부를 확인한 뒤 로그인 사용자 정보를 반환할 수 있습니다.

public LoginMember findLoginMemberByRememberToken(String token) {
    RememberMeDto rememberMe = memberMapper.findRememberMeByToken(token);

    if (rememberMe == null) {
        return null;
    }

    if (rememberMe.getExpireDate().isBefore(LocalDateTime.now())) {
        memberMapper.deleteRememberMeToken(token);
        return null;
    }

    MemberDto member = memberMapper.findByMemberNo(rememberMe.getMemberNo());

    if (member == null) {
        return null;
    }

    return new LoginMember(
            member.getMemberNo(),
            member.getMemberId(),
            member.getMemberName(),
            member.getRole()
    );
}

이 흐름에서 중요한 점은 만료된 토큰을 그대로 사용하지 않는 것입니다. 토큰이 만료되었다면 DB에서 삭제하고 로그인 복원을 실패 처리하는 것이 좋습니다.


15. 로그아웃 시 자동 로그인 토큰 삭제하기

자동 로그인 상태에서 로그아웃할 때는 세션만 제거하면 부족합니다. 브라우저의 자동 로그인 쿠키와 DB의 자동 로그인 토큰도 함께 제거해야 합니다.

@PostMapping("/logout")
public String logout(
        HttpServletRequest request,
        HttpServletResponse response,
        HttpSession session
) {
    String rememberToken = findCookieValue(request, "rememberToken");

    if (rememberToken != null) {
        memberService.deleteRememberMeToken(rememberToken);
        deleteRememberMeCookie(response);
    }

    session.invalidate();

    return "redirect:/";
}

자동 로그인 쿠키를 삭제하는 메서드는 다음처럼 만들 수 있습니다.

private void deleteRememberMeCookie(HttpServletResponse response) {
    Cookie cookie = new Cookie("rememberToken", null);
    cookie.setMaxAge(0);
    cookie.setPath("/");
    cookie.setHttpOnly(true);

    response.addCookie(cookie);
}

쿠키를 삭제할 때는 생성할 때와 같은 이름과 path를 사용하는 것이 중요합니다.

로그아웃
 → DB 자동 로그인 토큰 삭제
 → rememberToken 쿠키 삭제
 → session.invalidate()
 → 메인으로 redirect

16. 자동 로그인 보안 주의사항

자동 로그인은 편리하지만 보안상 신중하게 구현해야 합니다. 최소한 다음 사항은 고려하는 것이 좋습니다.

주의사항 설명
쿠키에 비밀번호 저장 금지 비밀번호, 권한, 민감 정보를 쿠키에 직접 저장하면 안 됩니다.
랜덤 토큰 사용 예측하기 어려운 토큰을 사용해야 합니다.
토큰 만료 시간 설정 무기한 유지하지 말고 만료 시간을 둬야 합니다.
로그아웃 시 토큰 삭제 DB 토큰과 쿠키를 함께 삭제해야 합니다.
HttpOnly 설정 JavaScript에서 쿠키 접근을 제한할 수 있습니다.
Secure 설정 HTTPS 환경에서는 Secure 옵션을 고려해야 합니다.
토큰 해시 저장 실무에서는 DB에 토큰 원문 대신 해시값 저장을 고려할 수 있습니다.

17. 자주 하는 실수

1) 쿠키에 아이디와 비밀번호를 저장하는 경우

자동 로그인은 쿠키에 비밀번호를 저장하는 기능이 아닙니다. 쿠키에는 랜덤 토큰만 저장하고, 서버 DB에서 검증해야 합니다.

나쁜 예시
 → rememberPw=1234

좋은 구조
 → rememberToken=랜덤토큰

2) 로그아웃할 때 rememberToken을 삭제하지 않는 경우

세션만 제거하고 자동 로그인 쿠키를 남겨두면 다음 방문 때 다시 로그인 상태가 복원될 수 있습니다. 로그아웃 시에는 DB 토큰과 쿠키를 함께 삭제해야 합니다.

3) 만료된 토큰을 계속 허용하는 경우

자동 로그인 토큰에는 만료 시간이 필요합니다. 만료된 토큰이면 DB에서 삭제하고 로그인 복원을 실패 처리하는 것이 좋습니다.

4) 자동 로그인 체크 여부를 세션에만 저장하는 경우

자동 로그인은 브라우저를 닫은 뒤에도 복원되어야 하므로 세션만으로는 부족합니다. 일정 기간 유지되는 쿠키와 서버 DB의 토큰 정보가 필요합니다.

5) Interceptor에서 자동 로그인 복원 후 세션 저장을 빼먹는 경우

토큰이 유효하다는 것을 확인했다면 세션에 loginMember를 다시 저장해야 합니다. 그래야 이후 요청에서 일반 세션 로그인처럼 동작할 수 있습니다.

HttpSession newSession = request.getSession();
newSession.setAttribute("loginMember", loginMember);

18. 전체 흐름 정리

자동 로그인 전체 흐름을 정리하면 다음과 같습니다.

로그인 시

1. 사용자가 자동 로그인을 체크한다.
2. 로그인에 성공한다.
3. 서버가 세션에 loginMember를 저장한다.
4. 서버가 자동 로그인 토큰을 생성한다.
5. DB에 memberNo, token, expireDate를 저장한다.
6. 브라우저 쿠키에 rememberToken을 저장한다.

이후 방문 시

1. 사용자가 보호된 URL에 접근한다.
2. Interceptor가 세션을 확인한다.
3. 세션에 loginMember가 없으면 rememberToken 쿠키를 확인한다.
4. DB에서 토큰을 조회한다.
5. 토큰이 존재하고 만료되지 않았으면 회원 정보를 조회한다.
6. 세션에 loginMember를 다시 저장한다.
7. Controller 실행을 허용한다.

로그아웃 시

1. DB의 rememberToken을 삭제한다.
2. 브라우저의 rememberToken 쿠키를 삭제한다.
3. session.invalidate()로 세션을 제거한다.
4. 로그아웃 상태가 된다.

정리

자동 로그인은 브라우저 쿠키와 서버 DB의 토큰 정보를 이용해 로그인 상태를 복원하는 기능입니다. 쿠키에는 아이디나 비밀번호를 저장하지 않고, 예측하기 어려운 랜덤 토큰만 저장해야 합니다. 서버는 그 토큰을 DB에서 확인하고, 유효한 경우 세션에 loginMember를 다시 저장합니다.

자동 로그인 쿠키 브라우저에 저장되는 랜덤 토큰입니다.
자동 로그인 DB 회원 번호, 토큰, 만료 시간을 저장합니다.
Interceptor 세션이 없을 때 자동 로그인 쿠키를 확인하고 로그인 상태를 복원할 수 있습니다.
로그아웃 세션, 쿠키, DB 토큰을 함께 정리해야 합니다.

처음에는 다음 기준을 꼭 기억하면 됩니다.

자동 로그인 체크
 → 토큰 생성

토큰 저장
 → 쿠키 + DB

다시 방문
 → 쿠키 토큰 확인
 → DB 토큰 검증
 → 세션 복원

로그아웃
 → 세션 삭제
 → 쿠키 삭제
 → DB 토큰 삭제

쿠키에 저장하면 안 되는 것
 → 비밀번호, 권한, 민감 정보

연습해보기

아래 요구사항을 기준으로 자동 로그인 구조를 설계해보세요.

요구사항

1. 로그인 화면에 자동 로그인 체크박스를 둔다.
2. 자동 로그인 체크 후 로그인 성공 시 rememberToken을 생성한다.
3. rememberToken을 브라우저 쿠키에 저장한다.
4. rememberToken, memberNo, expireDate를 DB에 저장한다.
5. 사용자가 다시 방문했을 때 세션이 없으면 rememberToken 쿠키를 확인한다.
6. DB에서 토큰이 유효한지 확인한다.
7. 유효하면 세션에 loginMember를 다시 저장한다.
8. 로그아웃 시 rememberToken 쿠키와 DB 토큰을 삭제한다.

다음 글에서는 Spring에서 비밀번호 암호화를 처리하는 방법을 정리하겠습니다. 회원가입과 로그인에서 평문 비밀번호를 그대로 저장하거나 비교하지 않고, 안전하게 해시 처리하는 흐름을 살펴보겠습니다.