🔐 소셜 로그인 시스템 가이드

📋 목차

  1. 전체 구조 개요
  2. 3단계 자동 판단 로직
  3. 플랫폼별 핵심 차이점
  4. 플랫폼별 주요 차이점 요약
  5. 주요 API 스펙
  6. 핵심 설정 정보
  7. 개발 환경 테스트

1. 전체 구조 개요

1.1 시스템 아키텍처 다이어그램

graph TB
    A[프론트엔드] --> B[AuthService:8082]
    B --> C[Google OAuth]
    B --> D[카카오 OAuth]
    B --> E[네이버 OAuth]
    B --> F[MySQL Database]
    B --> G[Redis Cache]
    
    subgraph "DB Tables"
        F --> H[members]
        F --> I[member_social_accounts]
    end
    
    subgraph "토큰 관리"
        G --> J[Access Token]
        G --> K[Refresh Token]
    end

1.2 전체 처리 플로우

사용자 소셜 로그인 요청
    ↓
소셜 플랫폼에서 토큰 획득 (ID Token/Access Token)
    ↓
AuthService로 토큰 전송
    ↓
3단계 자동 판단 로직 실행
    ↓
결과에 따른 처리:
├─ 기존 회원 → 즉시 로그인 (JWT 토큰 발급)
├─ 신규 회원 → 회원가입 후 로그인
└─ 정보 부족 → 추가 정보 입력 요청

2. 3단계 자동 판단 로직

2.1 판단 순서와 로직

단계 조건 결과 추가 처리
1단계 소셜 ID로 기존 소셜 계정 찾기 기존 소셜 사용자 즉시 로그인
2단계 이메일로 기존 일반 회원 찾기 기존 일반 사용자 소셜 계정 연결
3단계 필수 정보 검증 신규 사용자 회원가입 처리

2.2 핵심 코드 (SocialUserService.java)

public UserSocialResult findOrCreateUserBySocial(SocialUserInfo socialInfo) {
    // 1단계: 소셜 ID로 기존 계정 조회
    Optional<MemberSocialAccount> existingSocial = 
        memberSocialAccountRepository.findByProviderAndSocialId(
            socialInfo.getProvider(), socialInfo.getSocialId());
    
    if (existingSocial.isPresent()) {
        log.info("🔍 1단계: 기존 소셜 계정 발견");
        return UserSocialResult.existingUser(existingSocial.get().getMember());
    }
    
    // 2단계: 이메일로 기존 일반 회원 조회
    if (socialInfo.getEmail() != null) {
        Optional<Member> existingMember = memberRepository.findByEmail(socialInfo.getEmail());
        if (existingMember.isPresent()) {
            log.info("📧 2단계: 기존 이메일 회원 발견 - 소셜 계정 연결");
            return linkSocialAccount(existingMember.get(), socialInfo);
        }
    }
    
    // 3단계: 신규 회원 생성
    log.info("🆕 3단계: 신규 회원 생성");
    return createNewSocialUser(socialInfo);
}

3. 플랫폼별 핵심 차이점

3.1 Google 소셜 인증 절차

3.1.1 Google 인증 플로우 다이어그램

sequenceDiagram
    participant U as 사용자
    participant F as 프론트엔드
    participant G as Google OAuth
    participant A as AuthService
    participant D as Database
    
    U->>F: Google 로그인 버튼 클릭
    F->>G: GSI 라이브러리로 인증 요청
    G->>U: Google 계정 선택/로그인 UI
    U->>G: 계정 선택 및 권한 동의
    G->>F: ID Token 전달 (JWT)
    F->>A: POST /api/auth/social/google<br/>{idToken: "eyJ..."}
    A->>G: ID Token 검증 요청
    G->>A: 사용자 정보 반환
    A->>D: 3단계 자동 판단 로직 실행
    D->>A: 회원 상태 결과
    A->>F: JWT Access/Refresh Token
    F->>U: 로그인 완료 처리

3.1.2 Google 핵심 구현

// 프론트엔드: ID Token 방식
google.accounts.id.initialize({
    client_id: "642010742671-4tnfj6vsfp6aemq7bj4dfgn0m185v6he.apps.googleusercontent.com",
    callback: (response) => {
        // ID Token을 백엔드로 전송
        fetch('/api/auth/social/google', {
            method: 'POST',
            body: JSON.stringify({ idToken: response.credential })
        });
    }
});
// 백엔드: ID Token 검증
public SocialUserInfo getUserInfo(String idToken) {
    GoogleIdToken googleIdToken = verifier.verify(idToken);
    GoogleIdToken.Payload payload = googleIdToken.getPayload();
    
    return SocialUserInfo.builder()
        .provider("google")
        .socialId(payload.getSubject())
        .email(payload.getEmail())
        .name((String) payload.get("name"))
        .build();
}

3.2 카카오 소셜 인증 절차

3.2.1 카카오 인증 플로우 다이어그램

sequenceDiagram
    participant U as 사용자
    participant F as 프론트엔드
    participant K as 카카오 OAuth
    participant A as AuthService
    participant D as Database
    
    U->>F: 카카오 로그인 버튼 클릭
    F->>K: Kakao SDK로 인증 요청
    K->>U: 카카오 로그인 팝업/페이지
    U->>K: 카카오 계정 로그인 및 권한 동의
    K->>F: Access Token 전달
    F->>A: POST /api/auth/social/kakao<br/>{accessToken: "kEWG..."}
    A->>K: Access Token으로 사용자 정보 요청<br/>GET /v2/user/me
    K->>A: 사용자 정보 응답 (JSON)
    A->>D: 3단계 자동 판단 로직 실행
    D->>A: 회원 상태 결과
    A->>F: JWT Access/Refresh Token
    F->>U: 로그인 완료 처리

3.2.2 카카오 핵심 구현

// 프론트엔드: Access Token 방식
Kakao.Auth.login({
    success: (authObj) => {
        // Access Token을 백엔드로 전송
        fetch('/api/auth/social/kakao', {
            method: 'POST',
            body: JSON.stringify({ accessToken: authObj.access_token })
        });
    }
});
// 백엔드: 카카오 API 호출
public SocialUserInfo getUserInfo(String accessToken) {
    String url = "https://kapi.kakao.com/v2/user/me";
    HttpHeaders headers = new HttpHeaders();
    headers.setBearerAuth(accessToken);
    
    ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, 
        new HttpEntity<>(headers), Map.class);
    
    Map<String, Object> kakaoAccount = (Map) response.getBody().get("kakao_account");
    return SocialUserInfo.builder()
        .provider("kakao")
        .socialId(response.getBody().get("id").toString())
        .email((String) kakaoAccount.get("email"))
        .build();
}

3.3 네이버 소셜 인증 절차 (팝업 방식)

3.3.1 네이버 인증 플로우 다이어그램

sequenceDiagram
    participant U as 사용자
    participant F as 프론트엔드
    participant P as 팝업창
    participant N as 네이버 OAuth
    participant A as AuthService
    participant D as Database
    
    U->>F: 네이버 로그인 버튼 클릭
    F->>P: window.open()으로 팝업 창 오픈<br/>GET /naver/login?state=random
    P->>A: 네이버 인증 URL 요청
    A->>P: 네이버 OAuth URL로 리다이렉트
    P->>N: 네이버 로그인 페이지 표시
    U->>N: 네이버 계정 로그인 및 권한 동의
    N->>P: Authorization Code와 함께 콜백<br/>GET /naver/callback?code=xxx&state=xxx
    P->>A: Authorization Code 전달
    A->>N: Authorization Code로 Access Token 요청<br/>POST /oauth2.0/token
    N->>A: Access Token 응답
    A->>N: Access Token으로 사용자 정보 요청<br/>GET /v1/nid/me
    N->>A: 사용자 정보 응답 (JSON)
    A->>D: 3단계 자동 판단 로직 실행
    D->>A: 회원 상태 결과
    A->>P: 결과를 담은 HTML 페이지 반환
    P->>F: postMessage()로 결과 전달
    F->>P: 팝업 창 닫기
    F->>U: 로그인 완료 처리

3.3.2 네이버 핵심 구현 (팝업 + postMessage)

// 프론트엔드: 팝업 + postMessage
function naverLogin() {
    const popup = window.open(
        'http://localhost:8082/api/auth/social/naver/login',
        'naverLogin',
        'width=500,height=600'
    );
    
    // 팝업에서 메시지 수신
    window.addEventListener('message', (event) => {
        if (event.data.type === 'NAVER_LOGIN_SUCCESS') {
            console.log('네이버 로그인 성공:', event.data.result);
            popup.close();
        }
    });
}
// 백엔드: OAuth 2.0 플로우
@GetMapping("/naver/login")
public String naverLogin(@RequestParam String state) {
    String authUrl = buildAuthorizationUrl(state);
    return "redirect:" + authUrl;
}

@GetMapping("/naver/callback")
public String naverCallback(@RequestParam String code, @RequestParam String state) {
    String accessToken = getAccessToken(code, state);
    SocialUserInfo userInfo = getUserInfo(accessToken);
    
    // 팝업에 결과 전달하는 HTML 반환
    return generatePopupResponseHtml(userInfo);
}

4. 플랫폼별 주요 차이점 요약

구분 Google 카카오 네이버
인증 방식 ID Token (JWT) Access Token OAuth 2.0 Code Flow
프론트엔드 구현 GSI 라이브러리 Kakao SDK 팝업 + postMessage
토큰 검증 Google에서 직접 검증 카카오 API로 정보 조회 Code → Token → 정보 조회
보안 수준 높음 (서명된 JWT) 중간 (Bearer Token) 높음 (CSRF 방지)
구현 복잡도 쉬움 쉬움 복잡함 (팝업 통신)
백엔드 API 호출 1회 (토큰 검증) 1회 (사용자 정보) 2회 (토큰 + 정보)

4.1 플랫폼별 핵심 특징

🔵 Google

🟡 카카오

🟢 네이버


5. 주요 API 스펙

5.1 공통 요청 형식

// POST /api/auth/social/{provider}
{
  "idToken": "eyJhbGciOiJSUzI1NiIs...",     // Google만
  "accessToken": "kEWG7tLkJc3eD...",        // 카카오, 네이버
  "deviceInfo": {
    "platform": "WEB_BROWSER",
    "deviceId": "browser-12345",
    "appVersion": "1.0.0"
  }
}

5.2 공통 응답 형식

// 성공 응답
{
  "success": true,
  "data": {
    "member": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "email": "user@example.com",
      "name": "사용자",
      "profileImageUrl": "https://..."
    },
    "token": {
      "accessToken": "eyJhbGciOiJIUzI1NiIs...",
      "refreshToken": "eyJhbGciOiJIUzI1NiIs...",
      "expiresIn": 3600
    },
    "isNewUser": false,
    "needsAdditionalInfo": false
  }
}

5.3 에러 응답

{
  "success": false,
  "error": {
    "code": "INVALID_SOCIAL_TOKEN",
    "message": "소셜 토큰이 유효하지 않습니다"
  }
}

6. 핵심 설정 정보

6.1 application.yml 핵심 설정

server:
  port: 8082

# JWT 설정
jwt:
  secret: akc-b2c-jwt-secret-key-2024-very-long-and-secure
  access-token-expiration: 3600000   # 1시간
  refresh-token-expiration: 604800000 # 7일

# 소셜 로그인 설정
social:
  google:
    client-id: 642010742671-4tnfj6vsfp6aemq7bj4dfgn0m185v6he.apps.googleusercontent.com
    dev-mode: false  # 개발 시 true로 설정
    
  kakao:
    client-id: 62beeadf5fa92745c25ade678f079981
    dev-mode: false
    
  naver:
    client-id: J7MtZsl9898OFMhgKDur
    redirect-uri: http://localhost:8082/api/auth/social/naver/callback
    dev-mode: false

6.2 데이터베이스 핵심 구조

-- 회원 테이블
CREATE TABLE members (
  id bigint PRIMARY KEY AUTO_INCREMENT,
  member_uuid varchar(36) UNIQUE NOT NULL,
  email varchar(100) UNIQUE,
  name varchar(50),
  user_id varchar(30) UNIQUE,
  member_type varchar(20) DEFAULT 'SOCIAL',  -- GENERAL, SOCIAL
  status varchar(20) DEFAULT 'ACTIVE'
);

-- 소셜 계정 테이블  
CREATE TABLE member_social_accounts (
  id bigint PRIMARY KEY AUTO_INCREMENT,
  member_id bigint NOT NULL,
  provider varchar(20) NOT NULL,    -- GOOGLE, KAKAO, NAVER
  social_id varchar(100) NOT NULL,  -- 소셜 플랫폼 고유 ID
  email varchar(100),
  UNIQUE KEY uk_social_account (provider, social_id),
  FOREIGN KEY (member_id) REFERENCES members(id)
);

7. 개발 환경 테스트

7.1 개발 모드 활성화

# application-local.yml
social:
  google:
    dev-mode: true     # 더미 데이터 사용
  kakao:
    dev-mode: true     
  naver:
    dev-mode: true     

7.2 개발용 더미 토큰

// 프론트엔드에서 사용할 개발용 토큰
const DEV_TOKENS = {
  google: 'dummy_google_token_dev_mode',
  kakao: 'dummy_kakao_token_dev_mode', 
  naver: 'dummy_naver_token_dev_mode'
};

// 개발 환경 테스트 함수
const testSocialLogin = (provider) => {
  fetch(`http://localhost:8082/api/auth/social/${provider}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      idToken: provider === 'google' ? DEV_TOKENS[provider] : undefined,
      accessToken: provider !== 'google' ? DEV_TOKENS[provider] : undefined
    })
  });
};

7.3 테스트 페이지

social-login-test.html 파일이 프로젝트 루트에 준비되어 있어 로컬에서 바로 테스트 가능합니다.


🎯 핵심 포인트

  1. 3단계 자동 판단: 소셜 ID → 이메일 → 신규 가입 순서로 처리
  2. 플랫폼별 차이: Google(ID Token), 카카오(Access Token), 네이버(OAuth 팝업)
  3. 개발 편의성: dev-mode로 실제 소셜 로그인 없이 테스트 가능
  4. 일관된 API: 모든 플랫폼이 동일한 응답 형식 사용

이 문서는 실제 구현된 코드를 기반으로 핵심 내용만 정리했습니다. 📋✨