본문 바로가기
[ Project ]/Team

[ Team ] OAuth - Kakao API 로그인 구현

by 환이s 2023. 6. 17.
728x90


담당 기능

 

 

 

오늘은 이전 포스팅에 이어서 소셜 로그인 Kakao API 구현을 해보겠습니다.

API 흐름은 Naver API와 동일한 방식으로 구현이 돼서 앞서 언급했던 것과 동일하니 생략하도록 하겠습니다. 

 

알아보시는 분들은 아래 포스팅 참고해 주시면 도움이 될 거 같습니다.

 

 

[ Team ] OAuth - NAVER API 로그인 구현

담당 기능 이전 포스팅까지 담당 기능으로 관리자 페이지 [회원관리] 기능 구현 포스팅을 했습니다. 해당 기능에 대해서 알아보시는 분들은 아래 링크 참고해 보시면 도움이 되실 거 같습니다. [

drg2524.tistory.com

 

프로젝트 구조

 

 

 

 

카카오 API 이용 신청

 

소셜 로그인 기능 구현을 하려면 먼저 카카오 Open API 애플리케이션 등록을 해야 합니다.

 

https://developers.kakao.com/

 

Kakao Developers

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

developers.kakao.com

 

로그인 후 "내 애플리케이션" 페이지로 이동해서 <애플리케이션 추가하기> 버튼을 클릭합니다.

 

 

클릭하시면 모달창에서 <앱 아이콘>, <앱 이름>, <사업자명> 텍스트 칸이 나오는데, <앱 아이콘>은 설정하실 분들은 하셔도 되고 안 해도 상관없습니다.

 

<앱 이름>, <사업자명>을 입력하고 저장해 줍니다.

 

 

저장하기 버튼을 클릭하면 아래 사진처럼 애플리케이션이 추가됩니다. 

 

 

 

생성된 애플리케이션을 클릭해서 들어가면

 

 

앱 키 값이 생성됩니다.

 

앱 키값은 클라이언트 ID값이라서 메모장에 별도로 저장해 줍니다.

저는 REST API 기반으로 구현하기 때문에 REST API 키 값으로 진행합니다.

 

 

그다음으로는 플랫폼을 설정해주셔야 합니다.

 

사진에서 하단부를 보시면 "설정된 플랫폼 정보가 없습니다." 안내문구 옆에 "플랫폼 설정하기" 페이지 안내 태그가 있습니다.

 

클릭하시면

 

 

플랫폼을 설정할 수 있는 페이지로 이동하는데, 여기서 저희는 Web 플랫폼을 등록해야 합니다.

 

 

사이트 도메인은 첫 번째 사이트 도메인으로, API를 통해 발송되는 메시지의 Web 링크 기본값으로 사용됩니다.

 

다음으로는 카테고리로 오셔서  제품설정 -> 카카오 로그인 페이지로 이동합니다.

 

 

카카오 로그인 페이지로 이동하셔서 활성화를  OFF -> ON으로 설정해 주시고 Redirect URI 경로를 지정해 줍니다.

 

 

활성화 설정을 ON으로 설정하시면 "OpenID Connect 활성화 설정" 칸이 생성되는데, 해당 활성화를 설정하면 카카오 로그인 시 액세스 토큰과 ID 토큰을 함께 발급받을 수 있습니다.

 

TIP )  OpenID Connect 비활성화

 

[OpenID Connect 활성화] 설정을 [OFF]로 변경하면, 설정 변경 시점부터 ID 토큰이 발급되지 않습니다. 서비스에서 ID 토큰을 사용 중인 경우에는 로그인 기능에 문제를 일으킬 수 있으므로 주의합니다.

 

 

소셜 로그인을 알아보시는 분들이라면 궁금하신 부분이 Redirect URI 설정일 거 같습니다.

REST API 또는 Kakao SDK for JavaScript로 카카오 로그인을 구현하시려면 Redirect URI를 반드시 등록해야 합니다.

 

Redirect URI는 OAuth 2.0을 기반으로 동작하는 카카오 로그인의 핵심 요소입니다. 카카오 로그인은 서비스 로그인 과정에서 Redirect URI를 통해 서비스에서 요청한 인가 코드와 토큰을 전달합니다. 

 

Redirect URI를 올바르게 등록하지 않은 경우, 카카오 로그인 시 에러가 발생합니다.

 

다음으로는 카카오 로그인 카테고리에서 동의항목 페이지로 이동합니다.

 

 

 

위 사진처럼 개인정보 설정을 해줍니다.

 

 

필요한 개인 정보를 설정하셨다면 우측 상단에 [동의 화면 미리 보기] 버튼을 클릭하셔서 확인해보고 다음으로 넘어갑니다.

 

 

다음으로는 클라이언트 시크릿 토큰을 생성해야 합니다.

제품설정 -> 보안 페이지로 이동하시면 아래 사진처럼 생성 페이지로 이동합니다.

 

 

코드 생성을 하시면 

 

 

토큰이 생성됩니다. 

클라이언트 ID와 동일하게 메모장에 작성해서 보관해 둡니다.

 

여기까지 Open API 생성하는 과정이었습니다.

 

그럼 Spring으로 이동하겠습니다.

 

 

라이브러리 설정

 

소셜 로그인에 필요한 라이브러리를 pom.xml에 추가해줘야 합니다.

 

필요한 라이브러리는 OAuth 2.0 프로토콜과 Controller에서 JSON 데이터 형식인 사용자 정보를 처리하기 위한 JSON Parsing 라이브러리도 추가합니다.

 

		<!--JSON-->
		<dependency>
			<groupId>com.googlecode.json-simple</groupId>
			<artifactId>json-simple</artifactId>
			<version>1.1.1</version>
		</dependency>
        
		<!--OAuth 2.0-->
		<dependency>
			<groupId>com.github.scribejava</groupId>
			<artifactId>scribejava-core</artifactId>
			<version>2.8.1</version>
		</dependency>

 

 

 

KakaoLoginApi

 

Kakao Login 구현체 추가(provider 설정)

 

package com.example.trakker.oauth.model;

import com.github.scribejava.core.builder.api.DefaultApi20;

public class KakaoLoginApi extends DefaultApi20 {

    protected KakaoLoginApi(){ }

    private static class InstanceHolder{
        private static final KakaoLoginApi INSTANCE = new KakaoLoginApi();
    }

    public static KakaoLoginApi instance(){
     return InstanceHolder.INSTANCE;
    }

    @Override
    public String getAccessTokenEndpoint() {
        return "https://kauth.kakao.com/oauth/token";
    }

    @Override
    protected String getAuthorizationBaseUrl() {
        return "https://kauth.kakao.com/oauth/authorize";
    }
}

 

 

 

KakaoLoginBO

 

앞서 애플리케이션 생성할 때 메모장에 적어둔 클라이언트 ID, 클라이언트 시크릿 토큰 번호를 BO 클래스에 적용합니다.

 

카카오 로그인 비즈니스 로직을 처리하기 위한 BO 클래스를 생성하여, 인증 요청문을 구성해 줍니다.

 

package com.example.trakker.oauth.bo;

import com.example.trakker.oauth.model.KakaoLoginApi;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.UUID;

public class KakaoLoginBO {

    private final static String KAKAO_CLIENT_ID = "클라이언트 ID";
    private final static String KAKAO_CLIENT_SECRET = "클라이언트 시크릿토큰";
    private final static String KAKAO_REDIRECT_URI = "http://localhost:9090/trakker/callbackKakao";
    private final static String SESSION_STATE = "kakao_oauth_state";
    /* 프로필 조회 API URL */
    private final static String PROFILE_API_URL ="https://kapi.kakao.com/v2/user/me";


    /* 네아로 인증  URL 생성  Method */
    public String getAuthorizationUrl(HttpSession session) {

        /* 세션 유효성 검증을 위하여 난수를 생성 */
        String state = generateRandomString();
        /* 생성한 난수 값을 session에 저장 */
        setSession(session,state);

        /* Scribe에서 제공하는 인증 URL 생성 기능을 이용하여 네아로 인증 URL 생성 */
        OAuth20Service oauthService = new ServiceBuilder()
                .apiKey(KAKAO_CLIENT_ID)
                .apiSecret(KAKAO_CLIENT_SECRET)
                .callback(KAKAO_REDIRECT_URI)
                .state(state) //앞서 생성한 난수값을 인증 URL생성시 사용함
                .build(KakaoLoginApi.instance());

        return oauthService.getAuthorizationUrl();
    }

    /* 네아로 Callback 처리 및  AccessToken 획득 Method */
    public OAuth2AccessToken getAccessToken(HttpSession session, String code, String state) throws IOException {

        /* Callback으로 전달받은 세선검증용 난수값과 세션에 저장되어있는 값이 일치하는지 확인 */
        String sessionState = getSession(session);
        if(StringUtils.pathEquals(sessionState, state)){

            OAuth20Service oauthService = new ServiceBuilder()
                    .apiKey(KAKAO_CLIENT_ID)
                    .apiSecret(KAKAO_CLIENT_SECRET)
                    .callback(KAKAO_REDIRECT_URI)
                    .state(state)
                    .build(KakaoLoginApi.instance());

            /* Scribe에서 제공하는 AccessToken 획득 기능으로 네아로 Access Token을 획득 */
            OAuth2AccessToken accessToken = oauthService.getAccessToken(code);
            return accessToken;
        }
        return null;
    }

    /* 세션 유효성 검증을 위한 난수 생성기 */
    private String generateRandomString() {
        return UUID.randomUUID().toString();
    }

    /* http session에 데이터 저장 */
    private void setSession(HttpSession session,String state){
        session.setAttribute(SESSION_STATE, state);
    }

    /* http session에서 데이터 가져오기 */
    private String getSession(HttpSession session){
        return (String) session.getAttribute(SESSION_STATE);
    }

    /* Access Token을 이용하여 네이버 사용자 프로필 API를 호출 */
    public String getUserProfile(OAuth2AccessToken oauthToken) throws IOException{

        OAuth20Service oauthService =new ServiceBuilder()
                .apiKey(KAKAO_CLIENT_ID)
                .apiSecret(KAKAO_CLIENT_SECRET)
                .callback(KAKAO_REDIRECT_URI).build(KakaoLoginApi.instance());

        OAuthRequest request = new OAuthRequest(Verb.GET, PROFILE_API_URL, oauthService);
        oauthService.signRequest(oauthToken, request);
        Response response = request.send();

        // 응답 데이터 확인
        String responseBody = response.getBody();
        System.out.println("Kakao API Response: " + responseBody);

        return response.getBody();
    }


}

 

servlet-context.xml

 

Controller에서 BO 클래스를 이용할 수 있도록 Bean 등록을 해줍니다.

 

	<beans:bean id="kakaoLoginBO" class="com.example.trakker.oauth.bo.KakaoLoginBO">
	</beans:bean>

 

 

Controller

 

Controller는 View단에서 요청을 받거나, 보내주기 위한 코드를 작성하는데, 이전 포스팅에서 Naver API 생성 코드랑 겹치기 때문에 가독성을 생각해서 코드를 구현했습니다.

 

package com.example.trakker.controller;

import com.example.trakker.oauth.bo.KakaoLoginBO;
import com.example.trakker.oauth.bo.NaverLoginBO;

import com.github.scribejava.core.model.OAuth2AccessToken;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;


import javax.servlet.http.HttpSession;
import java.io.IOException;

@Controller
public class OauthController {


    /* NaverLoginBO */
    private NaverLoginBO naverLoginBO;

    private KakaoLoginBO kakaoLoginBO;

    /* NaverLoginBO */
    @Autowired
    private void setNaverLoginBO(NaverLoginBO naverLoginBO){
        this.naverLoginBO = naverLoginBO;
    }
    @Autowired
    private void setKakaoLoginBO(KakaoLoginBO kakaoLoginBO){
        this.kakaoLoginBO = kakaoLoginBO;
    }

    @RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.POST})
    public String login(HttpSession session , Model model) {
        /* 네아로 인증 URL을 생성하기 위하여 getAuthorizationUrl을 호출 */
        String naverAuthUrl = naverLoginBO.getAuthorizationUrl(session);

        String kakaoAuthUrl = kakaoLoginBO.getAuthorizationUrl(session);

        System.out.println("naverAuthUrl = " + naverAuthUrl);
        model.addAttribute("urlNaver", naverAuthUrl);
        System.out.println("kakaoAuthUrl = " + kakaoAuthUrl);
        model.addAttribute("urlKakao", kakaoAuthUrl);

        /* 생성한 인증 URL을 View로 전달 */
        return "login";
    }

    @RequestMapping(value = "/callbackNaver" , method = {RequestMethod.GET,RequestMethod.POST})
    public String callbackNaver(Model model,@RequestParam String code, @RequestParam String state, HttpSession session) throws IOException, ParseException {
        System.out.println("로그인 성공 callbackNaver");
        OAuth2AccessToken oauthToken = naverLoginBO.getAccessToken(session, code, state);
        String apiResult = naverLoginBO.getUserProfile(oauthToken);

        JSONParser jsonParser = new JSONParser();
        JSONObject jsonObj;

        jsonObj= (JSONObject) jsonParser.parse(apiResult);
        JSONObject response_obj = (JSONObject) jsonObj.get("response");

        //프로필 조회
        String email = (String) response_obj.get("email");
        String name = (String) response_obj.get("name");



        //세션에 사용자 정보 등록

        session.setAttribute("signIn",apiResult);
        session.setAttribute("email",email);
        session.setAttribute("name",name);

        return "redirect:/loginSuccess.do";
    }

    @RequestMapping(value = "/callbackKakao" , method = {RequestMethod.GET,RequestMethod.POST})
    public String callbackKakao(Model model,@RequestParam String code, @RequestParam String state, HttpSession session) throws IOException, ParseException {
        System.out.println("로그인 성공 callbackKakao");

        OAuth2AccessToken oauthToken = kakaoLoginBO.getAccessToken(session, code, state);
        String apiResult = kakaoLoginBO.getUserProfile(oauthToken);

        JSONParser jsonParser = new JSONParser();
        JSONObject jsonObj = (JSONObject) jsonParser.parse(apiResult);

        // JSON 객체에서 필요한 정보를 추출합니다.
        JSONObject response_obj = (JSONObject) jsonObj.get("kakao_account");
        JSONObject response_obj2 = (JSONObject) response_obj.get("profile");

        // 프로필 조회
        String email = (String) response_obj.get("email");
        String name = (String) response_obj2.get("nickname");

        // 세션에 사용자 정보 등록
        session.setAttribute("signIn", apiResult);
        session.setAttribute("email", email);
        session.setAttribute("name", name);

        System.out.println("카카오email = " + response_obj.get("email"));
        System.out.println("카카오email = " + (JSONObject) jsonObj.get("kakao_account"));
        System.out.println("카카오name = " + name);

        return "redirect:/loginSuccess.do";
    }

    @RequestMapping("/loginSuccess.do")
    public String loginSuccess(){
        return "loginSuccess";
    }
}

 

callback 호출 메서드에서 JSON parsing을 하기 위해 throws Exception으로 예외 처리를 해줘야 합니다.

 

getAccessToken 메서드를 사용해 토큰을 가져와서 Naver, Kakao에 사용자 정보를 요청합니다.

 

받은 정보는 JSON Parsing 처리하여 세션으로 로그인 성공 페이지에 넘겨줍니다.

 

Kakao에서 보낸 JSON 파일의 형식은 다음과 같습니다.

 

Kakao API Response:
{"id":id값, "connected_at":"2023-06-15T 17:38:25Z", "properties":{"nickname":"닉네임"}, "kakao_account":{"profile_nickname_needs_agreement":false,

"profile":{"nickname":"닉네임"  }, "has_email":true, "email_needs_agreement":true, "has_gender":true, "gender_needs_agreement":true}}

 

View 단 페이지는 이전 포스팅과 동일한 URL 설정을 했기 때문에 생략하겠습니다.

 

 

구현 결과

 

-MAIN 페이지 /login 경로 입력

 

 

-login 페이지

 

 

 

 

- 로그인 성공 페이지

 

 

 

 

 


마치며

 

오늘은 NAVER API에 이어서 KAKAO API 기능 구현에 대해서 알아봤습니다.

다음 포스팅에서 뵙겠습니다.

 

728x90