QR 코드 기반 게스트 계정 자동 생성 및 로그인 구현 (Spring Boot + JPA)
QR 코드를 통해 서비스에 접근하는 게스트 사용자를 위해 인증 없이 계정을 자동 생성하고 바로 로그인시키고자 했습니다.
기존 회원가입 로직은 이메일 인증과 key/code 검증이 필수라 게스트 흐름과 맞지 않았습니다.
우선 자동 회원가입 -> 로그인 흐름을 맞추기 위해, 비밀번호 생성과 아이디 생성을 위한 Sequence Table 을 구현했습니다.
해당 테이블로 유저가 동시 접속할 때 자동 +1 을 부여
-- 시퀀스 생성
CREATE SEQUENCE guest_seq
START 1
INCREMENT 1
MINVALUE 1
NO MAXVALUE
CACHE 1;
-- 기존 guest_sequence_tb 제거 가능
-- DROP TABLE guest_sequence_tb;
- CACHE 1 → DB가 메모리 캐싱 없이 1씩 증가
- nextval('guest_seq') 호출 시마다 새로운 값 반환
[환경 정보]
| 구성 요소 | 세부 정보 |
|---|---|
| Backend | Spring Boot (JPA 기반) |
| Database | PostgreSQL (guest_sequence_tb 활용, 시퀀스 관리) |
| Frontend | Next.js (QR 스캔 후 API 호출) |
| 브라우저 | Chrome 최신 버전 |
| 트랜잭션 | @Transactional + FOR UPDATE로 동시성 제어 |
[문제 재현 & 진단 과정]
- 로컬 환경: 게스트 생성 API 호출 → 계정 생성 → JWT 발급 정상
- 동시 요청 상황: 여러 사용자가 동시에 접근 →
next_num중복 가능성 존재 - 진단:
FOR UPDATE+@Transactional로 시퀀스를 잠금 처리 → 동시성 문제 방지
잠금 누락 시 동일 next_num이 여러 계정에 할당될 위험 존재
[게스트 계정 자동 생성 흐름]
QR->>Controller: POST /api/guest/qr-login
Controller->>Service: registerAndLoginGuestNoAuth()
Service->>DB: SELECT next\_num FOR UPDATE
DB-->>Service: next\_num (잠금)
Service->>UserRepo: save(UserEntity)
Service->>DB: UPDATE next\_num + 1
Service->>Auth: login(email, password)
Auth-->>Service: JWT + refresh token
Service-->>Controller: LoginRes
Controller-->>QR: RestResponse(LoginRes)
[핵심 코드]
Repository
@Repository
public interface GuestSequenceRepository extends JpaRepository<GuestSequenceEntity, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT g FROM GuestSequenceEntity g WHERE g.id = :id")
GuestSequenceEntity findByIdForUpdate(@Param("id") Long id);
}
해당 코드에서 사용된 LockModeType.PESSIMISTIC_WRITE
"내가 이 데이터를 수정하는 동안, 다른 사람은 절대 건드리지 마(읽지도 쓰고 쓰지도 마)!"라고 데이터베이스 수준에서 강력하게 락(Lock)을 거는 것입니다. 현재 트랜잭션이 데이터를 읽어올 때, 해당 행(Row)에 배타적 락(Exclusive Lock)을 겁니다. 그렇기 때문에 동시성 제어는 잘 되지만 , 만약 접근 유저가 많다면 로딩이 걸리는 환경이 생기기 때문에 추후 Nactive DB 로 변환해야 될 거 같습니다.

해당 락에 대한 설명은 아래의 블로그 글을 통해 확인하실 수 있습니다.
https://ye-seul0-0.tistory.com/11
[JPA] 동시성 제어 : LockModeType.PESSIMISTIC_WRITE
Spring Data JPA를 사용하여 동시성 이슈를 해결하려 할 때, 가장 강력하면서도 확실한 방법인 LockModeType.PESSIMISTIC_WRITE(비관적 쓰기 락)에 대해 정리해 봅니다.1. PESSIMISTIC_WRITE란?LockModeType.PESSIMISTIC_WRIT
ye-seul0-0.tistory.com
GuestAutoAuthService.java
@Transactional
public UserModel.LoginRes registerAndLoginGuestNoAuth(HttpServletResponse response) {
GuestSequenceEntity seq = guestSequenceRepository.findByIdForUpdate(1L);
String email = String.format("temp%03d@temp.com", seq.getNextNum());
String name = "테스터" + seq.getNextNum();
UserEntity userEntity = UserEntity.builder()
.userId(UUID.randomUUID().toString())
.email(email)
.name(name)
.password(passwordEncoder.encode("1234"))
.roles(Collections.singletonList("ROLE\_USER"))
.build();
userRepository.save(userEntity);
seq.setNextNum(seq.getNextNum() + 1);
guestSequenceRepository.save(seq);
return userService.login(new UserModel.LoginReq(email, "1234"), response);
GuestAutoAuthController.java
@PostMapping("/qr-login")
public ResponseEntity<RestResponse> qrLogin(HttpServletResponse response) {
return ResponseEntity.ok(
RestResponse.builder()
.success(guestAutoAuthService.registerAndLoginGuestNoAuth(response))
.build()
);
}
[장점]
- 동시성 안전: FOR UPDATE + @Transactional로 시퀀스 중복 방지
- 간결한 로직: join() 호출 없이 UserRepository로 직접 계정 생성
- 재사용성: 기존 UserService.login() 재사용 → JWT 발급 로직 통합
- 관리 용이: 게스트 계정 식별 필드(
gubun등) 활용 → 추후 삭제/통계 가능
[주의사항]
- 임시 비밀번호("1234") 사용 → 운영 환경에서는 랜덤 생성 후 첫 로그인 시 변경 권장
- 게스트 계정 만료 배치 운영 필요
- 멀티서버 환경에서 시퀀스 동기화 고려 → DB Native 시퀀스로 전환 가능
[마무리]
이번 구현으로 QR 기반 게스트 로그인 기능을 안전하고 간단하게 만들 수 있었으며, DB 시퀀스 잠금 처리로 동시성 문제까지 안전하게 해결했습니다.
DB Native 시퀀스 전환은 다음 장에서 진행해보겠습니다.
'ServerDev > SpringBoot' 카테고리의 다른 글
| [SpringBoot & FormData] JSON + 파일 업로드: HttpMediaTypeNotSupported 에러 해결 (0) | 2025.12.09 |
|---|---|
| [JPA] 동시성 제어 : LockModeType.PESSIMISTIC_WRITE (0) | 2025.12.07 |
| [Spring Boot] 권한 검증 - 커스텀 AOP와 Redis로 해결하기 (@IsCouncil) (0) | 2025.12.06 |
| [SpringBoot - SSR] 파일 업로드 에러: "Failed to convert String to MultipartFile" 원인과 해결 (0) | 2025.12.06 |
| [Spring Boot] 수정 기능 구현 시, 멀쩡한 데이터가 NULL 로 덮어씌워지는 문제 (0) | 2025.12.06 |