이번 포스팅에서는 지난 팀 프로젝트에서 개발했던 커피 구독 서비스에서의 경험을 정리해보려 한다
당시에는 구글 로그인만 구현했었지만, 이후 더 깊이 공부하기 위해 카카오와 네이버 로그인도 추가로 구현했다
그 과정에서 구글과 카카오, 네이버의 사용자 정보 JSON 구조가다르다는 점을 알게 되었고,
이를 정리하고자 이번 포스팅을 작성하게 되었다
oAuth2User.getAttribute("email")처럼 바로 추출 가능 예시 (구글 사용자 정보)
{
"sub": "1234567890",
"name": "홍길동",
"email": "hong@example.com"
}
사용자 정보가 중첩된(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;
}
{
"id": 123456789,
"kakao_account": {
"email": "user@kakao.com",
"profile": {
"nickname": "홍길동"
}
}
}
| Key | Value | 설명 |
|---|---|---|
id | 123456789 | 카카오에서 제공하는 고유 ID |
kakao_account.email | user@kakao.com | 사용자 이메일 |
kakao_account.profile.nickname | 홍길동 | 사용자 닉네임 |
👉 구글은 최상위 JSON에 email이 있어 attributes.get("email")로 바로 가져올 수 있지만,
카카오는 Map을 두 번 풀어서(email → profile → nickname) 가져와야 함
kakao_account와 profile은 Map 객체(중첩된 JSON) 구조로 되어 있어서,Map으로 변환하지 않으면 get으로 데이터를 꺼낼 수 없음attributes.get("email")만으로도 가져올 수 있음그럼 각 소셜네트워크 마다 어떻게 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가 각각 다른이유 최상의 키가 다르기때문
추가로, 구글 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
이렇게 구성할 수 있다!