Back-End/Spring Boot + Kotlin

OAuth2 로그인 시 커스터마이징 포인트

김검정 2025. 6. 18. 15:25

OAuth2가 무엇인가?

OAuth2는 로그인을 대신해주는 표준 방식이다. 사용자가 비밀번호를 입력하지 않고, 구글/카카오 계정으로 로그인하는 것을 가능하게 한다.

 

기본 흐름(단순화)를 살펴보면 

  1. 사용자가 구글 로그인 버튼 클릭
  2. Spring Security → 구글 서버로 리다이렉트
  3. 구글 로그인 → 우리 서버로 code 반환
  4. Spring Security가 code 로 access token 요청
  5. access token으로 사용자 정보 요청
  6. 사용자 정보 기반으로 인증 처리 완료

 

Spring Security OAuth2 구조를 이해해보자

Spring Security는 OAuth2 로그인을 처리하기 위해 다음 컴포넌트를 사용한다.

컴포넌트 역할
OAuth2LoginAuthenticationFilter 로그인 요청 가로채기
DefaultOauth2UserService 사용자 정보 조회
OAuth2User 조회된 사용자 정보 모델
AuthenticationSuccessHandler 로그인 성공 후 처리
ClientReistration 클라이언트 정보(google, kakao 등)

 

 

카카오 / 구글 연동을 위한 기본 설정 예시

// application.yml 예시

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: <GOOGLE_CLIENT_ID>
            client-secret: <GOOGLE_CLIENT_SECRET>
            scope: profile, email
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            client-name: Google
          kakao:
            client-id: <KAKAO_REST_API_KEY>
            client-secret: <KAKAO_CLIENT_SECRET>
            scope: profile_nickname, account_email
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            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

 

 

 

핵심 커스터마이징 포인트

 

1. 사용자 정보 가공 - CustomOAuth2UserService

 

구글 / 카카오에서 가져오는 사용자 정보는 정형화돼 있지 않다. 서비스마다 반환 형식도 다르고, 내가 원하는 도메인 정보와 맞지 않을 수도 있다.

@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

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

        String registrationId = userRequest.getClientRegistration().getRegistrationId(); // google, kakao
        String userNameAttr = userRequest.getClientRegistration()
                .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();

        Map<String, Object> attributes = oAuth2User.getAttributes();

        OAuthAttributes mappedAttributes = OAuthAttributes.of(registrationId, userNameAttr, attributes);

        // DB에 사용자 저장
        Member member = saveOrUpdate(mappedAttributes);

        return new DefaultOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")),
                mappedAttributes.getAttributes(),
                mappedAttributes.getNameAttributeKey());
    }
}

 

 

2. 최초 로그인 시 회원가입 처리

private Member saveOrUpdate(OAuthAttributes attributes) {
    Optional<Member> user = memberRepository.findByEmail(attributes.getEmail());

    if (user.isEmpty()) {
        return memberRepository.save(attributes.toEntity());
    }

    return user.get(); // 기존 사용자
}

 

 

3. 사용자 도메인 모델 매핑

@Getter
public class OAuthAttributes {
    private Map<String, Object> attributes;
    private String nameAttributeKey;
    private String name;
    private String email;

    public static OAuthAttributes of(String provider, String userNameAttributeName, Map<String, Object> attributes) {
        if ("kakao".equals(provider)) {
            return ofKakao(userNameAttributeName, attributes);
        }
        return ofGoogle(userNameAttributeName, attributes);
    }

    private static OAuthAttributes ofGoogle(String userNameAttributeName, Map<String, Object> attributes) {
        return new OAuthAttributes(attributes, userNameAttributeName,
                (String) attributes.get("name"),
                (String) attributes.get("email"));
    }

    private static OAuthAttributes ofKakao(String userNameAttributeName, Map<String, Object> attributes) {
        Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
        Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");

        return new OAuthAttributes(attributes, userNameAttributeName,
                (String) profile.get("nickname"),
                (String) kakaoAccount.get("email"));
    }

    public Member toEntity() {
        return new Member(name, email, Role.USER);
    }
}

 

 

 

4. JWT 발급 연동

OAuth2 로그인 성공 후 JWT를 발급하고 응답에 실어주는 구조도 자주 사용한다.

public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {

    private final JwtUtil jwtUtil;

    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) {
        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
        String email = (String) oAuth2User.getAttributes().get("email");

        String token = jwtUtil.createToken(email);

        response.setHeader("Authorization", "Bearer " + token);
    }
}

 

 

 

전체 흐름을 요약해보면

 

  1. 사용자가 /oauth2/authorication/kakao 클릭 
  2. 카카오 로그인
  3. code 반환
  4. Spring이 token 요청
  5. 사용자 정보 조회
  6. CustomOAuth2UserService 실행
  7. DB 회원가입 처리
  8. SecurityContext 인증 저장
  9. SuccessHandler에서 JWT 발급
  10. 클라이언트로 응답

 

OAuth2 로그인은 단순해 보이지만 실제 서비를 만들 때는 사용자 정보 가공, JWT 발급, 회원가입 처리 등 커스터마이징이 꼭 필요한 포인트들이 많다.