[SB] 소셜로그인 (OAuth2) - Kakao

0

SpringBoot

목록 보기
5/6

Before ...

[ SpringBoot OAuth2 소셜로그인에 대해 알아보자. ]

Others ...

[ 소셜로그인 (OAuth2) - Google ]
[ 소셜로그인 (OAuth2) - Naver ]

Kakao Developer Console

애플리케이션 등록


  1. 카카오 개발자 페이지 접속 후 로그인
  2. 내 어플리케이션 클릭
  3. 애플리케이션 추가하기
  4. 앱 아이콘, 앱 이름, 사업자명 작성
  5. 앱 키 -> REST API 키 저장
  6. 보안 -> Client Secret 생성 후 코드도 저장 + 활성화
  7. 카카오 로그인 -> 활성화 설정 둘 다 ON으로 변경 + Redirect URI 설정
    Redirect URI는 http://localhost:8080/login/oauth2/code/kakao로 지정
  8. 플랫폼 -> Web 플랫폼 등록 -> http://localhost:8080 설정
  9. 동의항목 -> 가져올 정보값 선택 (일부 항목은 검수를 해야 필수 동의 설정 가능)
    닉네임, 이메일만 선택하고 진행해 봄

GetAttributes 전달 객체

<kakao>
{
    id=2632890179, 
    connected_at=2023-01-22T08:17:54Z, 
    properties = {nickname=안창범}, 
    kakao_account = {
        profile_nickname_needs_agreement=false, 
        profile={nickname=안창범}, 
        has_email=true, 
        email_needs_agreement=false, 
        is_email_valid=true, 
        is_email_verified=true, 
        email=chb2005@naver.com
    }
}

googlefacebook과는 달리 ...

kakaonaver 에서 받아온 정보는 객체 안에 객체가 있는 형식이라 추출할 때, 다른 처리 방식이 필요함


데이터 변환

위에서 확인한 값들을 우리가 만든 User 엔티티에 맞게끔 변환해서 가입을 시켜줘야 한다.


  • provider = "google", "kakao", "naver", "facebook"
  • providerId = google의 sub값, kakao와 facebook의 id값, naver의 response의 id 값
  • loginId = provider_providerId 로 설정
  • nickname = 각 사이트에 등록한 이름으로 설정
  • email = google과 facebook의 email값, kakao의 kakao_account의 email값, naver의 response의 email값

구현 예제

application.yml

spring:
  # OAuth 로그인
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 발급받은 클라이언트 ID
            client-secret: 발급받은 클라이언트 보안 비밀번호
            scope:
              - email
              - profile

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

          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

          facebook:
            client-id: 발급받은 앱 ID
            client-secret: 발급받은 앱 시크릿 코드
            scope:
              - email
              - public_profile

        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

OAuth2UserInfo

사이트별로 값을 추출하는 방식은 다르지만 추출해야 하는 값은 같기 때문에 통일성을 위해 interface 생성

OAuth2UserInfo.java

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

KakaoUserInfo.java

@AllArgsConstructor
public class KakaoUserInfo implements OAuth2UserInfo{

    private Map<String, Object> attributes;

    @Override
    public String getProviderId() {
        // Long 타입이기 때문에 toString으로 변호나
        return attributes.get("id").toString();
    }

    @Override
    public String getProvider() {
        return "kakao";
    }

    @Override
    public String getEmail() {
        // kakao_account라는 Map에서 추출
        return (String) ((Map) attributes.get("kakao_account")).get("email");
    }

    @Override
    public String getName() {
        // kakao_account라는 Map에서 추출
        return (String) ((Map) attributes.get("properties")).get("nickname");
    }
}

OAuth2UserService

OAuth2UserService.java

@Service
@RequiredArgsConstructor
@Slf4j
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder encoder;

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

        OAuth2UserInfo oAuth2UserInfo = null;

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

        if(provider.equals("google")) {
            log.info("구글 로그인 요청");
            oAuth2UserInfo = new GoogleUserInfo( oAuth2User.getAttributes() );
        } else if(provider.equals("kakao")) {
            log.info("카카오 로그인 요청");
            oAuth2UserInfo = new KakaoUserInfo( (Map)oAuth2User.getAttributes() );
        } else if(provider.equals("naver")) {
            log.info("네이버 로그인 요청");
            oAuth2UserInfo = new NaverUserInfo( (Map)oAuth2User.getAttributes().get("response") );
        } else if(provider.equals("facebook")) {
            log.info("페이스북 로그인 요청");
            oAuth2UserInfo = new FacebookUserInfo( oAuth2User.getAttributes() );
        }

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


        Optional<User> optionalUser = userRepository.findByLoginId(loginId);
        User user = null;

        if(optionalUser.isEmpty()) {
            user = User.builder()
                    .loginId(loginId)
                    .nickname(nickname)
                    .provider(provider)
                    .providerId(providerId)
                    .role(UserRole.USER)
                    .build();
            userRepository.save(user);
        } else {
            user = optionalUser.get();
        }

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

결과

참조

https://chb2005.tistory.com/183

profile
꿈을 계속 간직하고 있으면 반드시 실현할 때가 온다. - 괴테.

0개의 댓글

관련 채용 정보