[8] Spring Security OAuth2: 사용자 정보 추상화와 팩토리 패턴 구현

2025. 12. 16. 05:56·Spring/Security
 

GitHub - HeoJunHyoung/bytemall

Contribute to HeoJunHyoung/bytemall development by creating an account on GitHub.

github.com

1. 들어가며

지난 포스팅에서는 OAuth2 클라이언트 의존성을 추가하고 프로바이더(Google, Kakao) 설정을 마쳤다. 이번 단계에서는 각 소셜 서비스마다 상이한 응답 데이터를 일관된 형태로 처리하기 위한 사용자 정보 추상화와 팩토리 패턴(Factory Pattern) 구현을 다룬다.


2. 문제 상황: 파편화된 응답 데이터

OAuth2 표준은 인증 흐름을 정의하지만, 사용자 정보(User Info)의 JSON 응답 구조까지 엄격하게 통일하지는 않았다.

  • Google: sub (고유 ID), email, name, picture 등의 평면적인 구조를 가진다.
  • Kakao: id (고유 ID), kakao_account 내부에 email, profile 객체가 중첩된 구조를 가진다.
  • Naver: response 객체 안에 사용자 정보가 담겨 있다.

이러한 차이를 비즈니스 로직(Service 계층)에서 if-else문으로 직접 처리하면 코드가 복잡해지고 유지보수가 어려워진다. 따라서 우리는 이들을 아우르는 공통 인터페이스를 정의하고, 각 프로바이더별 구현체를 만들어 데이터를 정규화해야 한다.


3. 사용자 정보 인터페이스 정의 (OAuth2UserInfo)

먼저 모든 소셜 프로바이더가 공통으로 제공해야 하는 핵심 정보를 추려 인터페이스를 정의한다. 우리 서비스에 필요한 것은 식별자(ID), 제공자 이름, 이메일, 이름이다.

src/main/java/.../global/security/oauth/info/OAuth2UserInfo.java

package com.example.bytemallbackend.global.security.oauth.info;

public interface OAuth2UserInfo {

    String getProviderId(); // 소셜 식별자 (Google의 sub, Kakao의 id)

    String getProvider();   // google, kakao

    String getEmail();

    String getName();

}

4. 프로바이더별 구현체 작성

이제 각 소셜 서비스의 응답 맵(Map<String, Object> attributes)을 받아 위 인터페이스에 맞게 데이터를 파싱하는 구현 클래스를 작성한다.

4.1 Google 구현체

구글은 비교적 직관적인 구조를 가진다. attributes 맵에서 필요한 키값을 바로 꺼내면 된다.

src/main/java/.../global/security/oauth/info/impl/GoogleOAuth2UserInfo.java

package com.example.bytemallbackend.global.security.oauth.info.impl;

import com.example.bytemallbackend.global.security.oauth.info.OAuth2UserInfo;
import java.util.Map;

public class GoogleOAuth2UserInfo implements OAuth2UserInfo {

    private final Map<String, Object> attributes;

    public GoogleOAuth2UserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return (String) attributes.get("sub");
    }

    @Override
    public String getProvider() {
        return "google";
    }

    @Override
    public String getEmail() {
        return (String) attributes.get("email");
    }

    @Override
    public String getName() {
        return (String) attributes.get("name");
    }
}

 

4.2 Kakao 구현체

카카오는 데이터가 중첩되어 있으므로 생성자에서 미리 필요한 객체(kakao_account, profile)를 추출해두는 것이 편리하다.

src/main/java/.../global/security/oauth/info/impl/KakaoOAuth2UserInfo.java

package com.example.bytemallbackend.global.security.oauth.info.impl;

import com.example.bytemallbackend.global.security.oauth.info.OAuth2UserInfo;
import java.util.Map;

public class KakaoOAuth2UserInfo implements OAuth2UserInfo {

    private final Map<String, Object> attributes;
    private final Map<String, Object> kakaoAccount;
    private final Map<String, Object> profile;

    public KakaoOAuth2UserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
        this.kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
        this.profile = (Map<String, Object>) kakaoAccount.get("profile");
    }

    @Override
    public String getProviderId() {
        return String.valueOf(attributes.get("id"));
    }

    @Override
    public String getProvider() {
        return "kakao";
    }

    @Override
    public String getEmail() {
        return (String) kakaoAccount.get("email");
    }

    @Override
    public String getName() {
        return (String) profile.get("nickname");
    }
}

5. 팩토리 클래스 구현 (OAuth2UserInfoFactory)

마지막으로, 로그인 요청이 들어온 registrationId(google, kakao 등)에 따라 적절한 OAuth2UserInfo 구현체를 생성해주는 팩토리 클래스를 만든다. 이를 통해 서비스 계층은 구체적인 구현 클래스를 알 필요 없이 인터페이스에만 의존할 수 있다.

src/main/java/.../global/security/oauth/info/OAuth2UserInfoFactory.java

package com.example.bytemallbackend.global.security.oauth.info;

import com.example.bytemallbackend.global.security.oauth.info.impl.GoogleOAuth2UserInfo;
import com.example.bytemallbackend.global.security.oauth.info.impl.KakaoOAuth2UserInfo;
import com.example.bytemallbackend.global.error.BusinessException;
import com.example.bytemallbackend.global.error.GlobalErrorCode;

import java.util.Map;

public class OAuth2UserInfoFactory {
    public static OAuth2UserInfo getOAuth2UserInfo(String registrationId, Map<String, Object> attributes) {
        if (registrationId.equalsIgnoreCase("google")) {
            return new GoogleOAuth2UserInfo(attributes);
        } else if (registrationId.equalsIgnoreCase("kakao")) {
            return new KakaoOAuth2UserInfo(attributes);
        } else {
            throw new BusinessException(GlobalErrorCode.INVALID_INPUT_VALUE);
        }
    }
}

6. 구조적 이점 및 정리

이렇게 전략 패턴(Strategy Pattern)과 팩토리 패턴(Factory Pattern)을 조합하여 사용자 정보를 추상화하면 다음과 같은 장점이 있다.

  1. 확장성 (OCP 준수): 네이버나 페이스북 등 새로운 소셜 로그인을 추가할 때, 기존 비즈니스 로직(CustomOAuth2UserService)을 수정할 필요 없이 새 구현 클래스를 만들고 팩토리에 한 줄만 추가하면 된다.
  2. 단일 책임 원칙 (SRP): 각 소셜 서비스의 복잡한 JSON 파싱 로직이 별도의 클래스로 분리되어 관리된다.
  3. 코드 가독성: 서비스 계층에서는 userInfo.getEmail()과 같이 통일된 메서드로 데이터에 접근할 수 있다.

'Spring > Security' 카테고리의 다른 글

[10] Spring Security OAuth2: 인증 성공 핸들러(SuccessHandler)와 JWT 발급  (0) 2025.12.16
[9] Spring Security OAuth2: 도메인 확장과 CustomOAuth2UserService 구현  (0) 2025.12.16
[7] Spring Security OAuth2: 의존성 설정 및 소셜 프로바이더 준비  (1) 2025.12.16
[6] Spring Security Local: Sequence Diagram  (0) 2025.12.16
[5] Spring Security Local: 로컬 로그인과 토큰 재발급  (0) 2025.12.16
'Spring/Security' 카테고리의 다른 글
  • [10] Spring Security OAuth2: 인증 성공 핸들러(SuccessHandler)와 JWT 발급
  • [9] Spring Security OAuth2: 도메인 확장과 CustomOAuth2UserService 구현
  • [7] Spring Security OAuth2: 의존성 설정 및 소셜 프로바이더 준비
  • [6] Spring Security Local: Sequence Diagram
h6bro
h6bro
백엔드 개발자의 기술 블로그
  • h6bro
    Jun's Tech Blog
    h6bro
  • 전체
    오늘
    어제
    • 분류 전체보기 (250) N
      • Java (18)
        • Core (9)
        • Design Pattern (9)
      • Spring (80)
        • Core (24)
        • MVC (6)
        • DB (10)
        • JPA (26)
        • Monitoring (3)
        • Security (11)
        • WebSocket (0)
      • Database (33)
        • Redis (15)
        • MySQL (18)
      • MSA (25) N
        • MSA 기본 (11)
        • MSA 아키텍처 (14) N
      • Kafka (30)
        • Core (18)
        • Connect (12)
      • ElasticSearch (11)
        • Search (11)
        • Logging (0)
      • Test (4)
        • k6 (4)
      • Docker (9)
      • CI&CD (10)
        • GitHub Actions (6)
        • ArgoCD (4)
      • Kubernetes (18)
        • Core (12)
        • Ops (6)
      • Cloud Engineering (4)
        • AWS Infrastructure (3)
        • AWS EKS (1)
        • Terraform (0)
      • Project (8)
        • LinkFolio (1)
        • Secondhand Market (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • Cloud Engineering 포스팅 정리
  • 인기 글

  • 태그

    ㅈ
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
h6bro
[8] Spring Security OAuth2: 사용자 정보 추상화와 팩토리 패턴 구현
상단으로

티스토리툴바