OAuth2와 JWT – 카카오 vs 구글 OAuth2 차이점

NTbell·2025년 1월 4일

이번 포스팅에서는 지난 팀 프로젝트에서 개발했던 커피 구독 서비스에서의 경험을 정리해보려 한다
당시에는 구글 로그인만 구현했었지만, 이후 더 깊이 공부하기 위해 카카오와 네이버 로그인도 추가로 구현했다
그 과정에서 구글과 카카오, 네이버의 사용자 정보 JSON 구조가다르다는 점을 알게 되었고,
이를 정리하고자 이번 포스팅을 작성하게 되었다

1️⃣ 구글 OAuth2

  • 구조가 단순(flat)하게 사용자 정보를 제공함
  • oAuth2User.getAttribute("email")처럼 바로 추출 가능

예시 (구글 사용자 정보)

{
  "sub": "1234567890",
  "name": "홍길동",
  "email": "hong@example.com"
}

2️⃣ 카카오 OAuth2

사용자 정보가 중첩된(JSON Map) 형태로 제공됨
kakao_account 안에 email, nickname 등이 포함되어 있어 한 단계 더 깊이 들어가야 함

예시 (카카오 사용자 정보)

{
  "id": 1234567890,
  "kakao_account": {
    "email": "pizza@example.com",
    "profile": {
      "nickname": "피자는역시bhc"
    }
  }
}

👉 oAuth2User.getAttribute("email")로는 바로 추출되지 않고,
Map으로 매핑하는 과정이 필요함

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
    OAuth2User oAuth2User = new DefaultOAuth2UserService().loadUser(userRequest);

    String provider = userRequest.getClientRegistration().getRegistrationId();
    Map<String, Object> attributes = oAuth2User.getAttributes();

    String providerId;
    String email;
    String name;

    // 🔹 구글 사용자 정보 매핑
    if (provider.equals("google")) {
        providerId = (String) attributes.get("sub");
        email = (String) attributes.get("email");
        name = (String) attributes.get("name");
    }
    // 🔹 카카오 사용자 정보 매핑
    else if (provider.equals("kakao")) {
        Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
        Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
        providerId = attributes.get("id").toString();
        email = (String) kakaoAccount.get("email");
        name = (String) profile.get("nickname");
    }
    return oAuth2User;
}

🛠️ JSON 중첩 구조 쉽게 이해하기

{
  "id": 123456789,
  "kakao_account": {
    "email": "user@kakao.com",
    "profile": {
      "nickname": "홍길동"
    }
  }
}
KeyValue설명
id123456789카카오에서 제공하는 고유 ID
kakao_account.emailuser@kakao.com사용자 이메일
kakao_account.profile.nickname홍길동사용자 닉네임

👉 구글은 최상위 JSON에 email이 있어 attributes.get("email")로 바로 가져올 수 있지만,
카카오는 Map을 두 번 풀어서(email → profile → nickname) 가져와야 함

🚀 왜 Map으로 변환해야 하나요?

  • kakao_accountprofile은 Map 객체(중첩된 JSON) 구조로 되어 있어서,Map으로 변환하지 않으면 get으로 데이터를 꺼낼 수 없음
  • 구글은 email, name이 최상위에 있으므로 attributes.get("email")만으로도 가져올 수 있음
  • 카카오는 계층이 깊어서 Map으로 변환 후 단계별로 가져와야

그럼 각 소셜네트워크 마다 어떻게 json구조를 확인하냐? 라고 생각할 수 있다

🔍 각 사이트에서 JSON 데이터 구조 확인하는 법

1. 공식 문서 확인

2. Postman으로 OAuth2 사용자 정보 요청하는 방법
1. Postman 실행
2. 새 요청(Request) 생성
3. HTTP 메서드: GET
4. Authorization 탭 → OAuth2 설정
5. 클라이언트 ID/시크릿 및 리다이렉트 URI 입력6.
액세스 토큰 발급 후 사용자 정보 요청


📊 OAuth 제공자별 API 구조 정리 (국내/외 주요 OAuth 제공자)

제공자국내/해외주요 사용처API 데이터 구조
구글해외/국내전반적인 서비스, 웹/모바일 앱flat (단일 계층)
네이버국내포털, 쇼핑, 예약, 커뮤니티중첩 (Map 구조)
카카오국내소셜 로그인, 커머스, 게임중첩 (Map 구조)
애플해외/국내iOS 앱, 글로벌 서비스flat (단일 계층)
깃허브해외개발자 서비스, 커뮤니티, IT 플랫폼flat (단일 계층)
페이스북해외SNS, 커머스, 광고 플랫폼중첩 (Map 구조)

😺 : 필자의 경우 이렇게 map을 사용해서 CustomOAuth2userService

 if (provider.equals("google")) {
            providerId = (String) attributes.get("sub");
            email = (String) attributes.get("email");
            name = (String) attributes.get("name");
        } else if (provider.equals("naver")) {
            Map<String, Object> response = (Map<String, Object>) attributes.get("response");
            providerId = (String) response.get("id");
            email = (String) response.get("email");
            name = (String) response.get("name");
        } else if (provider.equals("kakao")) {
            Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
            Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
            providerId = Optional.ofNullable(attributes.get("id"))
                    .map(Object::toString)
                    .orElseThrow(() -> new IllegalArgumentException("Kakao providerId is missing"));
            email = (String) kakaoAccount.get("email");
            name = (String) profile.get("nickname");
        } else {
            throw new IllegalArgumentException("지원되지 않은 제공자 : " + provider);
        }

이렇게 구현했었다

하지만 application-oauth.yml에서 따로 provider를 설정해주지않아 삽질을 좀 했었다

결론적으로, 각 제공자의 OAuth2 인증 서버 URL사용자 정보 API URL이 서로 다르기 때문에,
이런 방식으로 각 제공자(provider)의 설정을 별도로 명시해줘야 함!!

Authorization URI – 사용자를 로그인 페이지로 리디렉트하는 URL
Token URI – Authorization Code로 Access Token을 교환하는 URL
User Info URI – 사용자 정보를 조회하는 API 엔드포인트

        provider:
          naver:
            authorization-uri: https://nid.naver.com/oauth2.0/authorize
            token-uri: https://nid.naver.com/oauth2.0/token
            user-info-uri: https://openapi.naver.com/v1/nid/me
            user-name-attribute: response
          kakao:
            authorization-uri: https://kauth.kakao.com/oauth/authorize
            token-uri: https://kauth.kakao.com/oauth/token
            user-info-uri: https://kapi.kakao.com/v2/user/me
            user-name-attribute: id

user-name-attribute가 각각 다른이유 최상의 키가 다르기때문

  • 네이버: 사용자 정보를 response라는 키를 통해 감싸서 반환
  • 카카오: 사용자 정보에서 id가 최상위 레벨에 위치해 직접 접근 가능

추가로, 구글 OAuth2는 spring security에 기본 내장되어 있어서 따로 설정필요없다
그럼에도 굳이 해야한다면

provider:
  google:
    authorization-uri: https://accounts.google.com/o/oauth2/auth
    token-uri: https://oauth2.googleapis.com/token
    user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
    user-name-attribute: sub

이렇게 구성할 수 있다!

profile
어떤 개발자가 되고싶나

0개의 댓글