[Spring Boot] OAuth2 + JWT + Redis 기반 로그인 구현

sarah·2025년 4월 28일
0

OAuth 인증 후 모바일 앱에서 안전하게 사용자 식별 정보를 받아 JWT로 최종 로그인 처리

  • OAuth 인증이 끝나면 백엔드가 임시 토큰(UUID)을 발급해 Redis에 저장
  • 앱은 딥링크를 통해 임시 토큰만 전달받아, 민감한 인증 코드를 직접 다루지 않음
  • 임시 토큰을 검증한 뒤에야 실제 JWT Access/Refresh 토큰을 발급

로그인 플로우

1. 앱 → 브라우저 (OAuth 인증 요청)

  • 앱에서 구글/카카오 간편 로그인을 제공
  • 로그인 버튼 클릭 시 웹뷰로 동의 화면 이동

2. 브라우저 → OAuth 인증서버

  • 사용자가 아이디/비밀번호 입력 및 권한 동의
  • 완료되면 OAuth 서버가 redirect_uri(백엔드)로 인가 코드(code) 전달

3. OAuth 서버 → 백엔드 (Callback)

  • 백엔드에서 /oauth/{provider}/callback?code={authorization_code} 호출
  • 프로바이더별 로그인 로직은 OAuthCallbackService에서 추상화

4. 백엔드: 임시 토큰 생성·저장

  1. 받은 code로 OAuth 서버에 토큰 교환(Access/Refresh 토큰 발급)
  2. 사용자 정보 조회 후, 회원 가입/로그인 처리
  3. UUID 기반 임시 토큰(tempToken) 생성
  4. Redis에 아래 형태로 저장
key   = temp_token:{UUID}
value = memberSeq
TTL   = 5분

5. 백엔드 → 앱: 임시 토큰 전달

  • response.sendRedirect("{딥링크}?temp_token={UUID}")
  • 앱은 딥링크를 받아 내부 로직으로 전달

6. 앱 → 백엔드: 임시 토큰으로 최종 로그인 요청

// 요청 예시
{
"tempToken": "{UUID}",
"deviceId": "디바이스식별자"
}
  • deviceId를 함께 보내면 기기별 토큰 관리가 가능

7. 백엔드: 임시 토큰 검증 → JWT 발급

  1. Redis에서 getAndDelete("temp_token:{UUID}") 호출
  2. 값이 존재하면 memberSeq 획득, 없으면 에러(만료 또는 중복 사용)
  3. JwtTokenProvider.createTokens(memberSeq)로 Access/Refresh JWT 생성
  4. Redis에 아래 형태로 저장
key   = refresh:{memberSeq}:{deviceId}
value = refreshToken
TTL   = (config에 설정된 기간)
  1. 응답으로 토큰 및 회원정보 전송
  2. 앱은 받은 JWT를 로컬에 저장하고, 보호된 화면으로 이동하여 로그인 완료

왜 OAuth 콜백에서 바로 JWT를 주지 않고 임시 토큰을 쓰는가?

OAuth 콜백 단계에서 JWT와 회원정보를 바로 내려주지 않고, 임시 토큰(temp token) 만 발급해 앱에서 다시 호출하도록 만든 이유

1. 브라우저 URL 노출 방지

  • JWT를 쿼리 파라미터나 리다이렉트 URL로 직접 전달하면
    • URL 히스토리에 남아 악의적 접근에 노출
    • 중간자 공격(MITM) 또는 XSS에 의한 탈취 위험 증가
  • 임시 토큰
    • UUID 형태의 랜덤 값
    • TTL(예: 5분)이 짧음
    • Redis에만 저장 → 노출돼도 즉시 만료

2. 앱↔백엔드 보안 채널 분리

  • 콜백: 브라우저/WebView 컨텍스트
  • 로그인 API: 네이티브 앱 컨텍스트에서 HTTPS 호출
  • JWT를 콜백 URL에 담으면 딥링크 과정에서 또 노출 위험
  • 해결책
    1. 콜백 단계에서는 임시 토큰만 전달
    2. 앱에서 임시 토큰 + deviceId를 HTTPS POST로 전송 → 최종 JWT 발급
  • 이로써 민감한 JWT 전송 구간을 앱↔백엔드 통신으로 한정, 보안을 강화

3. 디바이스 바인딩 & 토큰 수명 관리

  • Refresh Token은 memberSeq + deviceId 조합으로 Redis에 저장하여 특정 기기에서만 유효
  • 콜백 단계에선 deviceId가 없으므로
    1. 콜백 → 임시 토큰 발급 (기기 정보 없이)
    2. 앱 → 임시 토큰 + deviceId로 로그인 요청 → “이 기기만 유효한 Refresh Token” 생성
  • 이중 단계 처리를 통해 기기별 토큰 관리를 깔끔하게 구현

0개의 댓글