소셜 로그인, 처음이라면? (백엔드- 카카오, 구글, 네이버)

동동주·2024년 12월 13일
0

이번 글에서는 OAuth2를 활용한 소셜 로그인에 대해서 정리해보려고 합니다. 해당 방법은 기초에 해당되는 방법이라고 생각하기 때문에 소셜 로그인을 처음 적용해보는 분들에게 도움이 될 것 같습니다.

OAuth2란?

제 3자 애플리케이션이 사용자 대신 안전하게 리소스에 접근할 수 있도록 허용하는 인증 프로토콜입니다. 이를 통해 사용자는 비밀번호를 공유하지 않고도 애플리케이션에 접근 권한을 부여할 수 있습니다.

🔷 주요 장점
1. 사용자는 비밀번호를 직접 입력하지 않아도 됩니다.
2. 앱은 사용자의 소셜 계정 정보를 안전하게 받아올 수 있습니다.
3. 사용자는 언제든 앱의 접근 권한을 취소할 수 있습니다.

👉 OAuth2 인증 과정의 주요 단계


1. 프로젝트 설정

1️⃣ 의존성 추가

  • build.gradle 파일에 해당 코드 추가합니다.
dependencies {
	
		// ... 기존 의존성 ...
		
    // Spring Security OAuth2 클라이언트 의존성 추가
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}

2️⃣ 개발자 계정 생성 및 애플리케이션 등록

구글, 카카오, 네이버 세 가지 모두 진행할 예정입니다. 각각의 해당 사이트에 들어가 계정 생성 및 애플리케이션 등록을 완료해야합니다. 해당 내용은 구글링하면 쉽게 찾을 수 있으니 생략하도록 하겠습니다.
🔷 참고로, 계정 생성 및 애플리케이션 등록 과정을 거치면서 전달받을 정보의 범위를 선택할 수 있는 창이 나오는데 이때 선택하는 정보에 따라 가지고 올 수 있는 정보가 한정됩니다. 또한, 구글, 카카오, 네이버에 따라 가지고 올 수 있는 개인정보가 다릅니다.

3️⃣ application.yml 파일에 OAuth2 정보 추가

개인적으로, 이 부분에서 실수가 많이 나올 수 있다고 생각합니다! 사이트에 따라 특성이 조금씩 다를 수도 있다는 것을 인지한 상태로 문제를 해결해보는 것을 추천합니다.

  • application.yml 파일에 해당 내용을 추가합니다.
spring:
	# --- 이전 설정 ---
  security:
    oauth2:
      client:
        registration:
         
          kakao:
            client-name: Kakao
            client-authentication-method: client_secret_post
            client-id: { 본인 client-id }
            client-secret: { 본인 client-secret}
            redirect-uri: { 본인 도메인 }/login/oauth2/code/kakao
            authorization-grant-type: authorization_code
            scope: profile_nickname

          google:
            client-name: Google
            client-id: { 본인 client-id }
            client-secret: { 본인 client-secret}
            redirect-uri: { 본인 도메인 }/login/oauth2/code/google
            authorization-grant-type: authorization_code
            scope: profile,email

          naver:
            client-name: Naver
            client-id: { 본인 client-id }
            client-secret: { 본인 client-secret}
            authorization-grant-type: authorization_code
            redirect-uri: { 본인 도메인 }/login/oauth2/code/naver
            scope: name

        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

          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

‼️여기서 scope이란?

앱이 요청하는 권한으로, 즉 해당 소셜 사이트에서 가져올 수 있는 개인 정보를 의미합니다. 이는 naver 소셜 로그인을 통해서는 이름을, google에서는 profile과 email을 가져올 수 있다는 것을 의미합니다.

redirect-uri

인증 후 소셜 사이트에서 사용자를 리다이렉트할 URI입니다. 사이트에서 설정한 URI와 일치해야 합니다.

client-name은 통일해서 적기

이 클라이언트 등록의 사람이 읽을 수 있는 이름입니다. 나중에 CustomOAuth2UserService와 연동되니 형식을 통일하여 적는 걸 추천합니다. 처음에 그걸 모르고 소문자, 대문자 섞어썼다가 오류가 떴습니다..ㅎㅎ

client-id, client-secret, redirect-uri 각자 추가해넣기

client-id, client-secret, redirect-uri는 각자 등록한 계정마다 다르기 때문에 해당 코드를 지우고 각자의 것을 작성하면 됩니다. 이는 노출이 되지 않는 것이 좋기 때문에 application-secret.yml로 따로 관리를 하거나 intelliJ 설정을 통해 등록해놓는 것이 좋습니다.

authorization-grant-type

사용할 OAuth 2.0 흐름을 지정합니다. authorization_code는 웹 애플리케이션의 표준 흐름입니다.

client-authentication-method

Spring이 카카오에 클라이언트 자격 증명을 어떻게 보낼지 지정합니다. client_secret_post는 POST 본문으로 보낸다는 의미입니다.

provider 섹션

  • authorization-uri: 사용자가 카카오로 로그인하기 위해 리다이렉트되는 URL입니다.
  • token-uri: 애플리케이션이 인증 코드를 액세스 토큰으로 교환하는 URL입니다.
  • user-info-uri: 애플리케이션이 액세스 토큰을 사용하여 사용자 정보를 가져올 수 있는 URL입니다.
  • user-name-attribute: 사용자 정보 응답에서 주요 이름으로 사용할 속성을 지정합니다.
  • 구글은 카카오와 네이버와 다르게 provider를 개별적으로 적지 않아도 구현 가능합니다.

application 파일은 yml 형식과 properties 형식이 있는데 위와 같은 yml 파일은 들여쓰기가 매우 중요하기 때문에 유의해주세요!



2. 사용자 정보 저장을 위한 CustomOAuth2UserService 구현

소셜 로그인 후 받은 사용자 정보를 우리 DB에 저장하는 CustomOAuth2UserService를 만들어볼 차례입니다.

CustomOAuth2Service 파일 위치: src/main/java/project/config/security

@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    private final MemberRepository memberRepository;
    private final PasswordEncoder passwordEncoder;

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

        String clientName = userRequest.getClientRegistration().getClientName();
        Map<String, Object> attributes = oAuth2User.getAttributes();

        String email = null;
        String name = null;

        // 로그 추가: clientName과 attributes 확인
        System.out.println("Client Name: " + clientName);
        System.out.println("Attributes: " + attributes);

        if ("Kakao".equals(clientName)) {
            Map<String, Object> properties = (Map<String, Object>) attributes.get("properties");
            name = (String) properties.get("nickname");
            email = name + "@kakao.com";  // 임시 이메일 생성
        } else if ("Google".equals(clientName)) {
            name = (String) attributes.get("name");
            email = (String) attributes.get("email");
        } else if ("Naver".equals(clientName)) {
            Map<String, Object> response = (Map<String, Object>) attributes.get("response");
            name = (String) response.get("name");
            email = name + "@naver.com"; // 임시 이메일 생성
        }

        // 로그 추가: name과 email 값 확인
        System.out.println("Name: " + name);
        System.out.println("Email: " + email);

        // 사용자 정보 저장 또는 업데이트
        Member member = saveOrUpdateUser(email, name);

        // 이메일을 Principal로 사용하기 위해 attributes 수정
        Map<String, Object> modifiedAttributes = new HashMap<>(attributes);
        modifiedAttributes.put("email", email);

        return new DefaultOAuth2User(
                oAuth2User.getAuthorities(),
                modifiedAttributes,
                "email" // email Principal로 설정
        );
    }

    private Member saveOrUpdateUser(String email, String name) {
        if (name == null || name.isEmpty()) {
            name = "default_name";  
        }

        Member member = memberRepository.findByEmail(email)
                .orElse(Member.builder()
                        .email(email)
                        .name(name)
                        .password(passwordEncoder.encode("OAUTH_USER_" + UUID.randomUUID()))
                        .gender(Gender.NONE)  
                        .address("소셜로그인")  
                        .specAddress("소셜로그인") 
                        .role(Role.USER)
                        .build());

        return memberRepository.save(member);
    }
}

해당 클래스는 소셜 로그인 후 받은 사용자 정보를 처리하고 데이터베이스에 저장하는 역할을 합니다. 주요 기능은 다음과 같습니다.

1️⃣ OAuth2User 정보 로드

  • super.loadUser(userRequest)를 통해 소셜에서 제공하는 사용자 정보를 OAuth2User 객체로 받아옵니다.
  • 이 객체의 attributes에는 사용자의 기본 정보가 포함되어 있습니다. (소셜 종류에 따라 다르겠죠)

2️⃣ 사용자 정보 추출

  • 소셜 API에서 제공하는 사용자 정보를 추출합니다.
    • 카카오는 nickname을, 구글은 name과 email을, 네이버는 name을 받고 있는 것을 확인할 수 있습니다.
    • 해당 코드에서는 최소한의 정보만 가지고 왔기 때문에 카카오와 네이버에서는 email을 실제 이메일이 아니라 (nick)name을 활용해 임의로 메일을 만들어냈다는 것을 유의해주세요.

3️⃣ 사용자 정보 저장 및 업데이트

  • saveOrUpdateUser 메소드를 통해 사용자 정보를 DB에 저장하거나 업데이트합니다.
  • 이메일을 기준으로 기존 사용자를 찾거나 새 사용자를 생성합니다.
  • 소셜에서 제공하지 않는 정보(성별, 주소 등)은 default 값으로 설정합니다.

4️⃣ Spring Security용 OAuth2User 반환

  • DefaultOAuth2User 객체를 생성하여 반환합니다.
  • 사용자의 권한, 속성, 그리고 주요 식별자(email)를 설정합니다.

System.out.println으로 로그 출력하는 부분은 확인을 위한 코드로 생략하셔도 무방합니다.

if ("Kakao".equals(clientName)) {
            Map<String, Object> properties = (Map<String, Object>) attributes.get("properties");
            name = (String) properties.get("nickname");
            email = name + "@kakao.com";  // 임시 이메일 생성
        } else if ("Google".equals(clientName)) {
            name = (String) attributes.get("name");
            email = (String) attributes.get("email");
        } else if ("Naver".equals(clientName)) {
            Map<String, Object> response = (Map<String, Object>) attributes.get("response");
            name = (String) response.get("name");
            email = name + "@naver.com";
        }
  • 이 부분이 아까 application.yml에서 설명한 client-name이 등장합니다. (Kakao, Google, Naver)

    💡주의사항
    - 실제 서비스에서는 닉네임 대신 고유한 식별자(예: 이메일, 카카오 고유 ID)를 사용하는 것이 더 안정적입니다.
    - 소셜 로그인 후 추가 정보 입력 페이지를 통해 필요한 사용자 정보를 수집하는 것이 좋습니다.



3. Spring Security에 OAuth2 설정 추가

  • SecurityConfig에 OAuth2 관련 설정을 추가합니다.
@EnableWebSecurity
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
						// ... 기존 설정 ...
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")
                .defaultSuccessUrl("/home", true)
                .permitAll()
            );

        return http.build();
    }

    // 기존 빈 설정...
}
  • 로그인 페이지에 소셜 로그인 버튼을 추가합니다.
  • 파일 위치: src/main/resources/templates/login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
<!-- 기존 코드 -->

<!-- 소셜 로그인 -->
<a th:href="@{/oauth2/authorization/kakao}">카카오로 로그인</a><p/>
<a th:href="@{/oauth2/authorization/google}">구글로 로그인</a><p/>
<a th:href="@{/oauth2/authorization/naver}">네이버로 로그인</a><p/>
</body>
</html>

소셜로그인이 잘 작동되는지 확인할 수 있게끔, 로그인이 완료된 후 보여질 home 화면도 만드는 것을 추천드립니다.



4. 결과 페이지 예시

1️⃣ 카카오

2️⃣ 구글


3️⃣ 네이버



전체 Github 코드 https://github.com/Kkimdoyeon/umc_study2/tree/feature/login

Copyright © 2024 김준석(벡스) All rights reserved.

0개의 댓글