[Spring] 구글 소셜 로그인 구현하기

icebox127·2026년 1월 16일

TO:DAY 서버

목록 보기
5/8

토큰 발급받기

1.구글 클라우드 콘솔 접속

https://console.cloud.google.com/welcome/new
1. 콘솔 접속

2. 프로젝트 선택>새 프로젝트 생성


3. API 및 서비스>사용자 인증 정보>동의 화면 구성


4. 사용자 타입 > 외부로 설정

5. OAuth 클라이언트 생성


** 승인된 리디렉션 URI를 꼭 설정해야한다!
(해당 링크로 요청을 해야 google 로그인 화면이 뜸)

구현 과정

  • 이미 진행하던 프로젝트에 소셜 로그인을 추가한 과정입니다.

1. 설정 추가

build.gradle
oauth2 의존성 추가.

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

나는 진행하던 프로젝트에 소셜로그인을 추가하는거라 이미 있지만, 첫 설정이라면 spring security도 추가해야함

implementation 'org.springframework.boot:spring-boot-starter-security'

application.yml

spring:
	...
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
            scope:
              - email
              - profile
  • client-id: 인증 서버가 클라이언트에 할당한 고유 식별자 (필수)
  • scope: 제공받고자 하는리소스 오너의 정보 목록

2. OAuth2UserCustomService

@RequiredArgsConstructor
@Service
public class OAuth2UserCustomService extends DefaultOAuth2UserService {

    private final MemberRepository memberRepository;

    @Transactional
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User user = super.loadUser(userRequest);
        Map<String, Object> attributes = user.getAttributes();

        OAuth2UserInfo userInfo = null;

        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        SocialType provider = SocialType.valueOf(registrationId.toUpperCase());

        if (provider.equals(SocialType.GOOGLE)){
            userInfo = new GoogleUserInfo(attributes);
        }

        if (userInfo == null) {
            throw new AuthException(AuthErrorCode.INVALID_SOCIAL_TYPE);
        }

        Member member = saveOrUpdate(userInfo);
        return new DefaultOAuth2User(
                Collections.emptySet(),
                user.getAttributes(),
                "sub"
        );
    }

    private Member saveOrUpdate(OAuth2UserInfo userInfo){
        String email = (String) userInfo.getEmail();

        Member member = memberRepository.findByEmail(email)
                .map(m -> {
                    if (!m.getSocialType().equals(userInfo.getProvider())) {
                        throw new MemberException(MemberErrorCode.DUPLICATE_SOCIAL);
                    }
                    m.updateFromOAuth(userInfo);
                    return m;
                })
                .orElse(
                        MemberConverter.toOAuthMember(email, userInfo)
                );
        return memberRepository.save(member);
    }
}

Spring이 OAuth2 과정 전반을 다 다뤄주기에 받은 정보들을 저장하는 로직을 짜고, 그걸 config에 등록하면 된다.
DefaultOAuth2UserService를 상속받는 서비스 클래스를 하나 생성한 뒤 위와같이 작성한다.

기본 개념
OAuth 인증이 완료된 후, 리소스 서버에서 받은 OAuth2User(외부 사용자 정보)를 기반으로 우리 서비스의 도메인 사용자(Member)를 생성하거나 갱신한다.

  • loadUser(): 리소스 서버에서 가져온 사용자 정보를 우리 서비스가 사용할 수 있도록 처리하는 로직을 정의
    (예. 구글에서 사용자 정보 받아올 때, saveOrUpdate로 서비스 db에 사용자 정보를 등록하도록 구현)
  • saveOrUpdate(): userInfo로부터 Member를 조회하여 이미 해당 소셜 타입으로 가입된 계정이 있으면 정보 업데이트, 없을 시 가입 시킨다. 단, 일반 이메일 회원가입등 다른 방식으로 이미 가입된 이메일일 경우 예외를 발생시킨다.

2-1. OAuth2UserInfo

public interface OAuth2UserInfo {
    String getProviderId();
    SocialType getProvider();
    String getEmail();
    String getName();
    String getProfileImage();
}

만약 여러 소셜 로그인을 사용할 경우, OAuth2UserCustomService에서 다룰 attribute를 반환하는 객체용 인터페이스를 만들었다.
가령, 소셜마다 get("profile"), get("profile_photo) 이런 식으로 정보를 받아오는 방식이 다른데 이를 정의해둔다.

public class GoogleUserInfo implements OAuth2UserInfo{
    private Map<String, Object> attributes;

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

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

    @Override
    public SocialType getProvider() {
        return SocialType.GOOGLE;
    }

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

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

    @Override
    public String getProfileImage() {
        return String.valueOf(attributes.get("picture"));
    }
}

3. OAuth2SuccessHandler

@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    private final TokenService tokenService;
    private final MemberRepository memberRepository;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException {

        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
        Member member = memberRepository.findByEmail((String) oAuth2User.getAttributes().get("email"))
                .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND));

        TokenDto tokenDto = tokenService.issueTokens(member);

        response.sendRedirect(
                "http://localhost:3000/oauth/success?token=" + tokenDto.getAccessToken()
        );
    }
}

인증 성공 시 진행할 로직을 작성한다.
1. 토큰 발급
2. 프론트엔드 리다이렉트

4. SecurityConfig 설정

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, OAuth2UserCustomService oAuth2UserCustomService, OAuth2SuccessHandler oAuth2SuccessHandler) throws Exception {
        http
           .authorizeHttpRequests(requests -> requests
                     .requestMatchers(allowUris).permitAll()
                     .anyRequest().authenticated()
              )
              .formLogin(AbstractHttpConfigurer::disable)
              .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class)
              .csrf(AbstractHttpConfigurer::disable)
              // 추가
              .oauth2Login(oauth2 -> oauth2
                     .userInfoEndpoint(userInfo -> userInfo.userService(oAuth2UserCustomService))
                     .successHandler(oAuth2SuccessHandler));

      return http.build();
  }

필터 체인에 oauth2Login 관련 설정을 추가한다.

  • userInfoEndPoint: OAuth 인증이 끝난 뒤, 리소스 서버에서 사용자 정보를 가져오는 단계
  • successHandler: 성공 시 실행할 핸들러
    이 두 항목에 앞서 만든 서비스와 핸들러를 넣는다.

실행 결과


OAuth2SuccessHandler에 정의한 리다이렉트 링크로 이동되었다.

profile
감자의 공부기록🥔

0개의 댓글