springboot

Spring Boot에서 JWT + Redis로 게스트 로그인 시스템 구현하기

2025-04-18
3분 분량
JAVA

이번에 웹게임 프로젝트를 진행하면서 비회원도 게스트 계정으로 게임을 플레이할 수 있게 하는 기능이 필요했다.

따라서 평소엔 항상 회원만 관리했지만 이번에는 비회원을 관리하는 로직이 필요해졌다.

하지만 구글링을 해도 내가 원하는 방식의 게스트 로그인은 보이지 않았다.

그래서 나는 직접 게스트 로그인 시스템을 설계하고, JWT + Redis를 활용해서 구현해보았다.


목표

  • 비회원도 로그인 없이 게임 이용 가능
  • 별도 회원가입 없이 “게스트 계정”으로 임시 토큰 발급
  • 일정 시간 동안 유효한 로그인 상태 유지
  • 게스트가 임시 회원이므로 DB가 아닌 Redis에 게스트 정보 저장
  • 토큰 기반 인증 시스템 그대로 활용

전반적인 흐름

1. 게스트 로그인 요청 → /auth/guest/sign-in

게스트 로그인 API를 호출하면 아래와 같은 흐름으로 처리된다.

  1. UUID 기반 무작위 게스트 ID와 닉네임 생성
  2. 해당 정보를 Redis에 저장하고 TTL 설정
  3. JWT 토큰 발급 (Access + Refresh)
  4. 클라이언트는 일반 회원처럼 토큰을 받아 게임을 이용할 수 있음
java
public TokenDto guestSignIn() {
    Long guestId;
    do {
        guestId = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE;
    } while(redisUtil.existed(GUEST_KEY_PREFIX + guestId));

    String nickname = RandomNicknameGenerator.generateNickname();

    User guestUser = User.builder()
            .id(guestId)
            .nickname(nickname)
            .role(Role.GUEST)
            .build();

    redisUtil.setData(GUEST_KEY_PREFIX + guestId, guestUser);
    redisUtil.setDataExpire(GUEST_KEY_PREFIX + guestId, GUEST_TTL_SECONDS);

    return jwtHelper.createGuestToken(guestUser);
}

RandomNicknameGenerator는 "귀여운고양이", "졸린펭귄"처럼 무작위로 형용사와 명사가 조합된 닉네임을 생성해준다.

게스트 유저는 DB에 저장하지 않고 Redis에서만 관리하며 일정 시간 후에는 자동 만료된다.


2. 인증 요청 시 → JwtAuthenticationFilter + GuestDetailsService

게스트 유저도 JWT 토큰 기반으로 인증을 받는다.

Spring Security의 커스텀 UserDetailsService를 통해 Redis에서 정보를 불러온다.

java
UserDetails userDetails = guestDetailsService.loadUserByUsername(guestId.toString());
java
User guest = Optional.ofNullable((User) redisUtil.getData(GUEST_KEY_PREFIX + guestId))
        .orElseThrow(() -> new UsernameNotFoundException("Guest not found : " + guestId));

redisUtil.setDataExpire(GUEST_KEY_PREFIX + guestId, GUEST_TTL_SECONDS); // TTL 연장
return new SecurityUserDetails(guest);
  • Redis에 정보가 있으면 TTL을 연장하면서 인증
  • 정보가 없으면 만료된 계정이므로 재로그인을 유도

3. 게스트 회원 전환 → /auth/guest/sign-up

게스트가 마음에 들어 게임을 더 즐기고 싶을 때, 회원 전환을 할 수 있다.

  1. 게스트 토큰을 가진 상태에서 회원가입 요청
  2. Redis에서 해당 게스트 정보를 불러와 새로운 유저로 전환
  3. DB에 저장하고 Redis에서는 해당 정보 삭제
java
public TokenDto guestSignUp(SignUpReq signUpReq, SecurityUserDetails userDetails){
    if (!userDetails.getRole().equals(Role.GUEST)) {
        throw new CustomException(AuthExceptionCode.INVALID_GUEST);
    }

    Long guestId = userDetails.getId();

    User guest = Optional.ofNullable((User) redisUtil.getData(GUEST_KEY_PREFIX + guestId))
            .orElseThrow(() -> new CustomException(AuthExceptionCode.INVALID_USERID));

    String encodedPassword = passwordEncoder.encode(signUpReq.password());
    User newUser = userRepository.save(signUpReq.toEntityFromGuest(encodedPassword, guest));

    redisUtil.deleteData(GUEST_KEY_PREFIX + guestId);

    return jwtHelper.createToken(newUser, false);
}
  • 이 과정에서 기존 게스트 경험치/스코어를 그대로 이전할 수 있도록 ID를 유지한다.
  • 회원 전환 후, 일반 유저로서 Access/Refresh 토큰이 재발급된다.

인증 필터는 어떻게 구성했는가?

JwtAuthenticationFilter에서 토큰을 읽고, GUEST인지 USER인지 구분하여 적절한 인증 서비스를 호출한다.

java
String role = tokenProvider.getRole(accessToken);
UserDetails userDetails;
if (role.equals(String.valueOf(Role.GUEST))) {
    Long guestId = tokenProvider.getUserId(accessToken);
    userDetails = guestDetailsService.loadUserByUsername(guestId.toString());
} else {
    String username = tokenProvider.getUsername(accessToken);
    userDetails = userDetailsService.loadUserByUsername(username);
}

Redis TTL은 왜 필요한가?

게스트 계정은 임시로 생성되는 계정이기 때문에, 굳이 DB에 영구적으로 저장할 필요성을 느끼지 못했다.

오히려 DB에 저장하는 순간부터 불필요한 데이터가 쌓일 수 있고 짧은 시간 사용하고 사라지는 계정 특성상 가벼운 저장소인 Redis가 더 적합하다고 판단했다.

그래서 게스트 계정은 로그인 시점부터 일정 시간 동안만 Redis에 존재하며 그 이후엔 TTL 설정을 통해 자동으로 만료된다.

java
redisUtil.setDataExpire(GUEST_KEY_PREFIX + guestId, GUEST_TTL_SECONDS);

또한, 사용자가 계속해서 활동 중이라면 TTL을 갱신하여 접속 상태를 유지시킬 수 있다. 일정 시간 이상 아무 활동이 없다면 자동으로 로그아웃되며 Redis에서도 정보가 삭제된다.

다만, Redis 역시 메모리 기반 저장소이기 때문에 게스트 사용자가 많아질 경우 부담이 될 수 있다는 점도 문제가 될 수 있다.

앞으로 캐시 용량이나 구조를 조정하거나 게스트 상태를 더 가볍게 유지할 수 있는 다른 대안들도 고려해볼 예정이다.


정리하며

비회원 사용자의 흐름을 "게스트"라는 개념으로 통합하고,

회원과 동일한 JWT 인증 시스템을 재사용하면서 DB가 아닌 Redis로 부담을 줄인 구조를 만들 수 있었다.

  • 빠른 체험 진입
  • 적당한 보안 수준 유지
  • 전환율 증가 유도

이번에 구현한 게스트 로그인 시스템이 완벽하거나 정답이라고 생각하지는 않는다.

아직 부족한 부분도 많고, 더 나은 구조로 개선할 여지도 충분히 있다.

앞으로도 계속 리팩토링하고 발전시켜 나가면서 더 나은 방식으로 다듬어갈 계획이다.

함께 읽으면 좋은 글