본문 바로가기

spring

Springboot Oauth2 KaKaoLogin, NaverLogin

이번 포스팅은 저번 포스팅에 이어 카카오와 네이버 로그인을 구현하려 한다.

기본적인 OAuth2 동작과 사용에 대해서는 이전에 기술했기 때문에 이번 포스팅에서는 구현을 보이고 끝을 맺도록 하겠다.

 

이번엔 카카오에서 애플리케이션을 추가해 주자.

https://developers.kakao.com/

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

새로운 애플리케이션을 추가해 주도록 하자.

보안 탭에서 코드 생성을 해주도록 하자.

 

생성된 코드 값은 사용해야 하니 복사해 주도록 하자.

 

활성화 설정 상태를 on으로 바꾸고 redirect URI을 http://localhost:8080/login/oauth2/code/kakao 으로 저장하자.

그 후 웹 플랫폼에 도메인을 등록해야 한다.

 

대시보드의 동의항목 탭에서 반환받을 값을 설정하자.

 

동의 단계는 필수로 두고 목적은 자유롭게 작성하면 된다. 이외에 여러 정보가 필요할 땐 개인정보 동의항목 심사 신청과 비즈앱으로 전환하면 된다. 테스트용으로 사용할 것이기 때문에 별도의 설정 없이 간단히 nickname만 사용하도록 하겠다.

 

이제 코드로 넘어가자 기본 설정은 이전과 같고 application.yml에 카카오 설정을 추가해 주자.

spring:
  security:
    oauth2:
      client:
        registration:
          kakao:
            client-id: [요약 정보 탭 > 앱 키 에서 확인 가능한 REST API 키]
            client-secret: [보안 탭에서 확인 가능한 Client Secret 코드]
            scope:
              - account_email
              - profile_nickname
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8080/login/oauth2/code/kakao
            client-name: Kakao
            client-authentication-method: client_secret_post

        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

 

위와 같이 추가했다.

 

이후 필요한 코드를 작성해 보자.

google과 별개로 kakao를 구성하자.

package com.example.oauth2.userdetails;

import com.example.oauth2.service.Oauth2UserInfo;
import lombok.AllArgsConstructor;

import java.util.Map;

@AllArgsConstructor
public class KakaoUserDetails implements Oauth2UserInfo {
    private Map<String, Object> attributes;
    @Override
    public String getProvider() {
        return "kakao";
    }

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

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

    @Override
    public String getName() {
        return (String) ((Map) attributes.get("properties")).get("nickname");
    }
}

 

카카오가 반환하는 값을 받기 위한 클래스이다.

이후에 CustomOauth2UserService에 if문을 사용해 구글 데이터를 바인딩하게 만들고 이번에는 kakao로 바인딩하게 만들 것이다.

package com.example.oauth2.service;

import com.example.oauth2.domain.Member;
import com.example.oauth2.domain.MemberRole;
import com.example.oauth2.repository.MemberRepository;
import com.example.oauth2.userdetails.CustomOauth2UserDetails;
import com.example.oauth2.userdetails.GoogleUserDetails;
import com.example.oauth2.userdetails.KakaoUserDetails;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class CustomOauth2UserService extends DefaultOAuth2UserService {
    private final MemberRepository memberRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(userRequest);
        log.info("getAttributes : {}", oAuth2User.getAttributes());

        String provider = userRequest.getClientRegistration().getRegistrationId();

        Oauth2UserInfo oauth2UserInfo = null;

        if (provider.equals("google")){
            log.info("구글 로그인");
            oauth2UserInfo = new GoogleUserDetails(oAuth2User.getAttributes());
        } else if (provider.equals("kakao")) {
            log.info("카카오 로그인");
            oauth2UserInfo = new KakaoUserDetails(oAuth2User.getAttributes());
        }


        String providerId = oauth2UserInfo.getProviderId();
        String email = oauth2UserInfo.getEmail();
        String loginId = provider + "_" + providerId;
        String name = oauth2UserInfo.getName();

        Member findMember = memberRepository.findByLoginId(loginId);
        Member member;

        if (findMember == null) {
            member = Member.builder()
                    .loginId(loginId)
                    .name(name)
                    .provider(provider)
                    .providerId(providerId)
                    .role(MemberRole.USER)
                    .build();
            memberRepository.save(member);
        } else {
            member = findMember;
        }
        return new CustomOauth2UserDetails(member, oAuth2User.getAttributes());
    }
}

 

코드상으로 많이 추가할 것은 없다. 이전에 Oauth2 인증과 시큐리티 설정을 했기 때문이다.

위 코드를 따라 하게 된다면 위 설정에서 Email에 대한 것은 빼야 한다. 왜냐하면 설정할 때 이메일에 대한 부분은 필수 값으로 두지 않았기 때문에 application.yml에선 email에 대한 것을 빼야 하며 KakaoUserDetails에서는 빼도 되고 빼지 않아도 된다.

 

아래에 카카오로 로그인 진행하는 것을 확인할 수 있다.

DB에 각 카카오와 구글 모두 로그인이 잘 되어 값이 저장되는 것을 확인할 수 있다.

c.e.o.service.CustomOauth2UserService    : getAttributes : {id=3625179478, connected_at=2024-07-16T21:04:19Z, properties={nickname=한지훈}, kakao_account={profile_nickname_needs_agreement=false, profile={nickname=한지훈, is_default_nickname=false}}}
c.e.o.service.CustomOauth2UserService    : 카카오 로그인

 

로그로 카카오 로그인 시 어떤 값이 들어오는지 확인할 수 있다.

따로 디버깅은 진행하지 않을 것이다. 왜냐하면 이전 구글과 같은 흐름이기 때문이다.

 

다음으로 네이버 로그인 구현해 보도록 하자.

https://developers.naver.com/main/

 

NAVER Developers

네이버 오픈 API들을 활용해 개발자들이 다양한 애플리케이션을 개발할 수 있도록 API 가이드와 SDK를 제공합니다. 제공중인 오픈 API에는 네이버 로그인, 검색, 단축URL, 캡차를 비롯 기계번역, 음

developers.naver.com

위 페이지에서 애플리케이션을 등록할 수 있다.

 

애플리케이션 등록 후 

사용 API는 네이버 로그인을 선택 후 제공 정보와 URI를 설정해 주도록 하자.

 

환경은 PC웹으로 하고 URL은 아래와 같이 기입하자.

애플리케이션 설정을 마쳤다면 application.yml을 작성하자.

spring:
  security:
    oauth2:
      client:
        registration:
          naver:
            client-id: [애플리케이션 정보에서 확인 가능한 Client ID]
            client-secret: [애플리케이션 정보에서 확인 가능한 Client Secret]
            scope:
              - name
              - email
            client-name: Naver
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8080/login/oauth2/code/naver

        provider:
          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

 

설정을 마쳤다면 카카오와 같이 클래스를 작성하고 실행해 보자.

package com.example.oauth2.userdetails;

import com.example.oauth2.service.Oauth2UserInfo;
import lombok.AllArgsConstructor;

import java.util.Map;

@AllArgsConstructor
public class NaverUserDetails implements Oauth2UserInfo {
    private Map<String, Object> attributes;
    @Override
    public String getProvider() {
        return "naver";
    }

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

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

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

 

package com.example.oauth2.service;

import com.example.oauth2.domain.Member;
import com.example.oauth2.domain.MemberRole;
import com.example.oauth2.repository.MemberRepository;
import com.example.oauth2.userdetails.CustomOauth2UserDetails;
import com.example.oauth2.userdetails.GoogleUserDetails;
import com.example.oauth2.userdetails.KakaoUserDetails;
import com.example.oauth2.userdetails.NaverUserDetails;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class CustomOauth2UserService extends DefaultOAuth2UserService {
    private final MemberRepository memberRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(userRequest);
        log.info("getAttributes : {}", oAuth2User.getAttributes());

        String provider = userRequest.getClientRegistration().getRegistrationId();

        Oauth2UserInfo oauth2UserInfo = null;

        if (provider.equals("google")){
            log.info("구글 로그인");
            oauth2UserInfo = new GoogleUserDetails(oAuth2User.getAttributes());
        } else if (provider.equals("kakao")) {
            log.info("카카오 로그인");
            oauth2UserInfo = new KakaoUserDetails(oAuth2User.getAttributes());
        } else if (provider.equals("naver")) {
            log.info("네이버 로그인");
            oauth2UserInfo = new NaverUserDetails(oAuth2User.getAttributes());
        }


        String providerId = oauth2UserInfo.getProviderId();
        String email = oauth2UserInfo.getEmail();
        String loginId = provider + "_" + providerId;
        String name = oauth2UserInfo.getName();

        Member findMember = memberRepository.findByLoginId(loginId);
        Member member;

        if (findMember == null) {
            member = Member.builder()
                    .loginId(loginId)
                    .name(name)
                    .provider(provider)
                    .providerId(providerId)
                    .role(MemberRole.USER)
                    .build();
            memberRepository.save(member);
        } else {
            member = findMember;
        }
        return new CustomOauth2UserDetails(member, oAuth2User.getAttributes());
    }
}
<!DOCTYPE html>
<html lang="ko">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="|${pageName}|"></title>
</head>
<body>
<div>
    <h1><a th:href="|/${loginType}|">[[${pageName}]]</a></h1> <hr/>
    <h2>로그인</h2>
    <form th:method="post" th:action="|@{/{loginType}/loginProc (loginType=${loginType})}|" th:object="${loginRequest}">
        <div>
            <label th:for="loginId">ID : </label>
            <input type="text" th:field="*{loginId}"/>
        </div>
        <br/>
        <div>
            <label th:for="password">비밀번호 : </label>
            <input type="password" th:field="*{password}"/>
        </div>
        <div th:if="${#fields.hasGlobalErrors()}">
            <br/>
            <div class="error-class" th:each="error : ${#fields.globalErrors()}" th:text="${error}" />
        </div>
        <br/>
        <button type="submit">로그인</button>
        <a href="/oauth2/authorization/google">구글 로그인</a>
        <a href="/oauth2/authorization/kakao">카카오 로그인</a>
        <a href="/oauth2/authorization/naver">네이버 로그인</a>
        <button type="button" th:onclick="|location.href='@{/{loginType}/join (loginType=${loginType})}'|">회원 가입</button> <br/><br/>
    </form>
</div>
</body>
</html>

<style>
    .error-class {
        color: red;
    }
    .error-input {
        border-color: red;
    }
</style>

 

이와 같이 설정하고 이전 카카오와 같으니 스킵하고 실제 동작을 확인해 보자.

 

 

 

c.e.o.service.CustomOauth2UserService    : getAttributes : {resultcode=00, message=success, response={id=U0bdrKsqDsVVV60KZeXAmah75JsLE-AVHEIhXJm_TIM, email=gkswlgns7653@naver.com, name=한지훈}}
c.e.o.service.CustomOauth2UserService    : 네이버 로그인

 

위와 같이 네이버 로그인도 잘 작성되는 것을 확인할 수 있다.

 

 

 

이로써 길고 길었던 쿠키, 세션, 시큐리티, Oauth2 소셜 로그인까지 알아보았다. 기초적으로 알아보았기 때문에 더 많은 보안 설정이 있지만 곧 프로젝트를 설정하고 기획해 시작할 건데 거기서는 좀 더 세밀한 보안설정을 통해 설명할 수 있게 할 것이고 새로운 프로젝트를 진행하며 계속 포스팅할 것이다. 이제 프로젝트를 기획하기 때문에 초기에 할 것이 많아서 아키텍처 구성, 각각 명세 등등 해서 다음 포스팅으로 찾아오도록 하겠다.

 

'spring' 카테고리의 다른 글

Jlaner 개발기록 1 프론트 구성  (0) 2024.07.28
springboot 프로젝트 개요  (0) 2024.07.27
Spring Oauth2 구글 로그인  (2) 2024.07.16
SpringBoot JWT에 대해서  (0) 2024.07.15
Spring Security에 대해  (0) 2024.07.13