OPEN HYPER STEP
← 목록으로 (Java+Spring)
JAVA · 72 / 99
java
CHAPTER 72 / 99
읽기 약 2
FUNCTION

OAuth2 소셜 로그인


핵심 개념

OAuth2 흐름·Google/Kakao 연동·Spring Security OAuth2 Client + JWT 발급.

본문

OAuth2 Authorization Code Flow

📋 코드 (5줄)
1. 사용자 → /oauth2/authorization/google → Google 로그인
2. Google → 인증 코드 → /login/oauth2/code/google
3. 서버가 코드를 access token으로 교환
4. Google API → 사용자 정보 (email, name)
5. 자체 JWT 발급 → 클라이언트

application.yml

YAML📋 코드 (28줄)
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
            scope:
              - email
              - profile
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
          kakao:
            client-id: ${KAKAO_CLIENT_ID}
            client-secret: ${KAKAO_CLIENT_SECRET}
            client-authentication-method: client_secret_post
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            scope:
              - profile_nickname
              - account_email
            client-name: Kakao
        provider:
          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

Security 설정

JAVA📋 코드 (23줄)
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class OAuth2SecurityConfig {
    private final CustomOAuth2UserService oAuth2UserService;
    private final OAuth2SuccessHandler successHandler;

    @Bean
    public SecurityFilterChain filter(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/login/**", "/oauth2/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth -> oauth
                .userInfoEndpoint(userInfo -> userInfo
                    .userService(oAuth2UserService)
                )
                .successHandler(successHandler)
            );
        return http.build();
    }
}

CustomOAuth2UserService

JAVA📋 코드 (33줄)
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest request) {
        OAuth2User oAuth2User = super.loadUser(request);

        String registrationId = request.getClientRegistration().getRegistrationId();
        OAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(
            registrationId,
            oAuth2User.getAttributes()
        );

        // DB에 저장 또는 갱신
        User user = userRepository.findByEmail(userInfo.getEmail())
            .map(existing -> updateUser(existing, userInfo))
            .orElseGet(() -> createUser(userInfo, registrationId));

        return new CustomOAuth2User(user, oAuth2User.getAttributes());
    }

    private User createUser(OAuth2UserInfo info, String provider) {
        User user = User.builder()
            .email(info.getEmail())
            .name(info.getName())
            .provider(provider)
            .role(Role.USER)
            .build();
        return userRepository.save(user);
    }
}

OAuth2 Provider별 사용자 정보 추출

JAVA📋 코드 (50줄)
public abstract class OAuth2UserInfo {
    protected Map<String, Object> attributes;

    public abstract String getId();
    public abstract String getName();
    public abstract String getEmail();
}


public class GoogleUserInfo extends OAuth2UserInfo {
    public GoogleUserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

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


public class KakaoUserInfo extends OAuth2UserInfo {
    public KakaoUserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

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

    @Override public String getName() {
        Map<String, Object> properties = (Map<String, Object>) attributes.get("properties");
        return (String) properties.get("nickname");
    }

    @Override public String getEmail() {
        Map<String, Object> account = (Map<String, Object>) attributes.get("kakao_account");
        return account != null ? (String) account.get("email") : null;
    }
}


public class OAuth2UserInfoFactory {
    public static OAuth2UserInfo getOAuth2UserInfo(String provider, Map<String, Object> attrs) {
        return switch (provider.toLowerCase()) {
            case "google" -> new GoogleUserInfo(attrs);
            case "kakao"  -> new KakaoUserInfo(attrs);
            default -> throw new IllegalArgumentException("지원하지 않는 provider: " + provider);
        };
    }
}

로그인 성공 핸들러 — JWT 발급

JAVA📋 코드 (24줄)
@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    private final JwtUtil jwtUtil;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest req,
                                         HttpServletResponse res,
                                         Authentication auth) throws IOException {
        CustomOAuth2User oAuth2User = (CustomOAuth2User) auth.getPrincipal();
        User user = oAuth2User.getUser();

        String token = jwtUtil.generate(user.getEmail(), user.getRole().name());

        // 프론트엔드로 리다이렉트 + 토큰 전달
        String redirectUrl = UriComponentsBuilder
            .fromUriString("https://openhyperstep.com/auth/callback")
            .queryParam("token", token)
            .build()
            .toUriString();

        res.sendRedirect(redirectUrl);
    }
}

다음 챕터

CH.4 "Spring Data JPA 심화" — N+1 문제 해결.


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 Spring 코드의 OAuth2 소셜 로그인 부분을 분석해서
보안 헤더·토큰 처리와 개선 우선순위를 알려줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

OAuth2 소셜 로그인 vs 다른 패턴 비교를
실전 사례 5개로 보여주고 Google vs Kakao vs Naver 비교를 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 코드베이스 전체를 분석해서
OAuth2 소셜 로그인 관련 인증 흐름의 약점를 보고해줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 한국 기업의 OAuth2 소셜 로그인 채택률과
한국 시장 소셜 로그인 트렌드를 솔직히 알려줘.

⭐ 이것만 기억하세요
OAuth2 소셜 로그인 이 3가지만 확실히 잡으세요
1.OAuth2는 표준 — Google/Kakao/Naver 모두 같은 흐름 + provider별 attribute 추출만 분기
2.OAuth2 로그인 후 자체 JWT 발급 — 클라이언트가 모든 API에 같은 토큰 사용
3.다음 챕터 CH.4에서 JPA 심화 — N+1 문제와 페이지네이션


공유하기
진행도 72 / 99