[Spring] 카카오톡 로그인 API

손시연·2022년 7월 24일
1

project

목록 보기
4/11

1. 카카오톡 로그인 API 이해

  • 참고 : https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
  • 과정
    • 프론트 : 카카오톡으로부터 인가 코드(code)를 받음
    • 백엔드 : 카카오톡으로부터 code에 대한 토큰(Access Token) 을 받음(String getAccessToken(String code))
    • 백엔드 : Access Token으로부터 필요한 정보 추출(HashMap<String, Object> getUserInfo(String accessToken))
    • 백엔드 : JSON parsing 하여 프론트에게 넘겨줌

2. Thymeleaf 화면 구현

  • index.html : 홈화면
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>치얌이는 코딩 중</title>
</head>
<body>
<li>
<a href="/home">Kakao Login</a>
</li>
</body>
</html>
  • login.html : 로그인 화면

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
</head>
<body>
<h1>Kakao Login</h1>
<span th:if="${userId==null}">
	<a href="https://kauth.kakao.com/oauth/authorize?client_id=${CLITENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code">  //본인 걸로 수정하기
		<img src = "/icon/kakao_login_medium_narrow.png">
	</a>
</span>
<span th:if="${userId!=null}">
	<form name="logout" action="http://localhost:8080/logout">
		<input type="submit" value="로그아웃">
	</form>
</span>
</body>
</html>

3. Service 구현

  • LoginService.java
    • 로그인, 로그아웃 구현
    • getAccessToken :"https://kauth.kakao.com/oauth/token" 로부터 AccessToken 받기
    • getUserInfo : "https://kapi.kakao.com/v2/user/me"로부터 필요한 정보(profile_nickname, profile_image, account_email) 받기
    • logout : "http://kapi.kakao.com/v1/user/logout"로부터 응답 받아내기
package gan.missulgan.service;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.tomcat.util.json.JSONParser;
import org.springframework.stereotype.Service;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

@Service
public class LoginService {

    public String getAccessToken(String code) throws IOException {
        //인가코드로 토큰 받기
        String accessToken = "";
        String refreshToken = "";
        String reqURL = "https://kauth.kakao.com/oauth/token";

        try {
            URL url = new URL(reqURL);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);

            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
            StringBuilder sb = new StringBuilder();
            sb.append("grant_type=authorization_code");
            sb.append("&client_id=${CLITENT_ID}");  //본인 걸로 수정하기            
            sb.append("&redirect_uri=${REDIRECT_URI}");  //본인 걸로 수정하기 
            sb.append("&code="+code);

            bw.write(sb.toString());
            bw.flush();

            //결과 코드가 200이라면 성공
            int responseCode = conn.getResponseCode();
            System.out.println("response code = " + responseCode);

            //JSON 파싱
            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));

            String line = "";
            String result = "";
            while((line = br.readLine())!=null) {
                result += line;
            }
            System.out.println("response body=" + result);

            //Gson 라이브러리에 포함된 클래스로 JSON 파싱 객체 생성
            JsonParser parser = new JsonParser();
            JsonElement element = parser.parse(result);

            accessToken = element.getAsJsonObject().get("access_token").getAsString();
            refreshToken = element.getAsJsonObject().get("refresh_token").getAsString();

            br.close();
            bw.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return accessToken;
    }

    public HashMap<String, Object> getUserInfo(String accessToken) throws IOException {
        HashMap<String, Object> userInfo = new HashMap<String, Object>();
        String reqUrl = "https://kapi.kakao.com/v2/user/me";
        try {
            URL url = new URL(reqUrl);

            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Authorization", "Bearer " + accessToken);

            int responseCode = conn.getResponseCode();
            System.out.println("responseCode =" + responseCode);

            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));

            String line = "";
            String result = "";

            while((line = br.readLine()) != null) {
                result += line;
            }
            System.out.println("response body ="+result);

            JsonParser parser = new JsonParser();
            JsonElement element =  parser.parse(result);

            JsonObject properties = element.getAsJsonObject().get("properties").getAsJsonObject();
            JsonObject kakaoAccount = element.getAsJsonObject().get("kakao_account").getAsJsonObject();

            String profile_nickname = properties.getAsJsonObject().get("nickname").getAsString();
            String profile_image = properties.getAsJsonObject().get("profile_image").getAsString();
            String account_email = kakaoAccount.getAsJsonObject().get("email").getAsString();

            userInfo.put("profile_nickname", profile_nickname);
            userInfo.put("profile_image", profile_image);
            userInfo.put("account_email", account_email);


        } catch (Exception e) {
            e.printStackTrace();
        }
        return userInfo;
    }


    public void logout(String accessToken) {
        String reqURL = "http://kapi.kakao.com/v1/user/logout";
        try {
            URL url = new URL(reqURL);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Authorization", "Bearer " + accessToken);
            int responseCode = conn.getResponseCode();
            System.out.println("responseCode = " + responseCode);

            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));

            String result = "";
            String line = "";

            while((line = br.readLine()) != null) {
                result+=line;
            }
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. Controller 구현

  • LoginController.java 구현
    • createLoginForm : 홈 화면에서 로그인 화면으로 이동
    • login : 인증코드 요청 -> 인증코드로 토큰 접근 -> 로그인 정보 접근
    • logout : 토큰 정보 삭제, model 정보 삭제
package gan.missulgan.controller;

import java.io.IOException;
import java.util.HashMap;

import javax.servlet.http.HttpSession;

import gan.missulgan.service.LoginService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import static org.springframework.web.bind.annotation.RequestMethod.*;

@Controller
public class LoginController {

    LoginService loginService = new LoginService();

    /**
     * 홈에서 로그인 화면으로 이동
     */
    @GetMapping("/home")
    public String createLoginForm() {
        return "login";
    }


    /**
     * 로그인
     */
    @RequestMapping(value = "/login", method = GET)
    public ModelAndView login(@RequestParam("code") String code, HttpSession session) throws IOException {
        ModelAndView mav = new ModelAndView();
        // 1번 인증코드 요청 전달
        String accessToken = loginService.getAccessToken(code);
        // 2번 인증코드로 토큰 전달
        HashMap<String, Object> userInfo = loginService.getUserInfo(accessToken);

        System.out.println("login info : " + userInfo.toString());

        if(userInfo.get("account_email") != null) {
            session.setAttribute("userId", userInfo.get("account_email"));
            session.setAttribute("accessToken", accessToken);
        }
        mav.addObject("userId", userInfo.get("account_email"));
        mav.setViewName("login");
        return mav;
    }
    

    /**
     * 로그아웃
     */
    @RequestMapping(value = "/logout", method = GET)
    public ModelAndView logout(HttpSession session) {
        ModelAndView mav = new ModelAndView();

        loginService.logout((String)session.getAttribute("accessToken"));
        session.removeAttribute("accessToken");
        session.removeAttribute("userId");
        mav.setViewName("login");
        return mav;
    }
}

5. 화면 및 URL 변화

  • 로그인 첫 화면

  • 카카오 로그인 클릭 시

  • 로그아웃 클릭 시

profile
Server Engineer

1개의 댓글

comment-user-thumbnail
2023년 3월 21일

안녕하세요 시연님 좋은 글 감사드립니다. java.io.FileNotFoundException: http://kapi.kakao.com/v1/user/logout 오류가 나오는데 어떤 식으로 해결하셨는지 알 수 있을까요? 로그아웃에 문제가 생겨서 질문 드려요

답글 달기