IT/java

Spring 카카오 로그인 REST API

토희 2022. 10. 2. 21:21
728x90

구현 전 체크사항 ✔️

 

☑️ 카카오 로그인 REST API은 단지 사용자 정보(닉네임, 이메일 등)를 제공하는 API이다. 실제 로그인이 되는건 아님!!!!!
☑️ 실제 자동 로그인되고 하는건, 카카오싱크라고 따로 있는것 같다. 카카오싱크는 사업자로 등록되어 있는 사람만 신청할수있음
☑️ 우리는 받은 사용자 정보를 토대로 로그인을 시키거나 회원가입을 시키거나 하는건 따로 구현해줘야한다.
☑️ https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api 에서 설정해줘야 하는 부분이 몇 군데 있다
   1. REST API 키 발급
   2. Redirect URI 등록
   3. 동의 항목 체크 (나는 닉네임, 카카오계정(이메일))만 체크했다. 

☑️ login.jsp => kakaoController(컨트롤러) => IKakaoLoginService(인터페이스) => KakaoLoginService(서비스) 이렇게 구성함,

    폴더명은 자유롭게 하면 된다!

☑️ 구글링을 하다 보면, 다른 블로그들은 카카오에서 받은 데이터를 Gson 라이브러리를 사용하여, JSON을 파싱 하는데,
   ex) JsonParser parser = new JsonParser();
         JsonElement element = parser.parse(result);
  나는 이미 프로젝트에서 Jackson 라이브러리를 사용하고 있기 때문에, JSON string의 데이터를 map으로 변환해서 사용하였다.

 

 

pom.xml에 추가해주면 된다!

 

Jackson 라이브러리 사용법 참고하기

https://4urdev.tistory.com/92

 

Jackson 라이브러리를 이용한 JSON String, Map 간 변환 (How to convert JSON String to Map and Map to JSON String)

일반적으로는 Spring 을 이용한 서버 통신이든, 그렇지 않은 경우에도 마찬가지고 String 으로 된 JSON 을 변경하는 경우가 좋은 상황은 아닙니다. 하지만 경우에 따라서 부득이하게 Plain Text 형태로

4urdev.tistory.com

 

흐름 이해✔️

1. 카카오에 코드 요청

2. 받은 코드로 access_token 요청

3. 받은 access_token으로 사용자 정보 요청

 

Start 👊

1. login.jsp에 카카오 로그인 만들기

화면

코드

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>로그인 페이지</title>
</head>
<style>
.wrap {
   width: 490px;
   padding: 40px 20px 20px 20px;
   background-color: #f5f6f7;
   position: absolute;
   top: 50%;
   left: 50%;
   transform: translate(-50%,-50%);
   border-radius: 30px;
   box-sizing: border-box;
}

.title {
   margin: 0 auto;
    width: 240px;
    height: 44px;
    text-align: center;
    font-size: 25px;
    background-repeat: no-repeat;
    background-position: 0 0;
    background-size: 240px auto;
    margin-bottom: 20px;
}
.kakao{
   margin-top: 15px;
   height: 60px;
   border: solid 1px #FEE500;
   background: #FEE500;
   color: #3c1d1e;
   font-size: 18px; 
   box-sizing: border-box;
   border-radius: 5px;
   cursor: pointer;
   width: 450px;
   display: flex;
}
.kakao_i{
   background: url(resources/icons/kakao.png) no-repeat;
   width: 40px;
   height: 100%;
   background-size: 90%;
   background-position: 50%;
   margin: 0 20px;
}
.kakao_txt{
   width: 100%;
   display: flex;
   justify-content: center;
   align-items: center;
   font-size: 16px;
   padding-right: 60px;
}

a {
   text-decoration: none;
}

</style>

<body>
<div class="wrap">
   <div class="title">로그인</div>
     <a class="kakao" href="https://kauth.kakao.com/oauth/authorize?client_id=REST_API키&redirect_uri=REDIRECT_URI&response_type=code">
     	<!-- REST_API키 및 REDIRECT_URI는 본인걸로 수정하세요 -->
        
      	<div class="kakao_i"></div>
      	<div class="kakao_txt">카카오톡으로 간편로그인 </div>
   	</a>
</div>
</body>
</html>

 

스타일 적인 부분은 제외에도 무관하다

a 태그에 href만 본인꺼에 맞춰서 잘 쓰면 된다

 

 

2. Controller에 code를 받을 메서드 만들기

kakaoController

package com.gdj51.MyTodo.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;


@Controller
public class kakaoController {

	// 1번 카카오톡에 사용자 코드 받기(jsp의 a태그 href에 경로 있음)
	@RequestMapping(value = "/kakaoLogin", method = RequestMethod.GET)
	public ModelAndView kakaoLogin(@RequestParam(value = "code", required = false) String code) throws Throwable {

		// 1번
		System.out.println("code:" + code);
		return null;	
		// return에 페이지를 해도 되고, 여기서는 코드가 넘어오는지만 확인할거기 때문에 따로 return 값을 두지는 않았음

	}
}

패키지 부분 제외!

controller를 위에처럼 작성하고, 화면에서 카카오톡으로 간편로그인 버튼을 누르면, Console창에 코드가 넘어온다

코드 실제로는 길다( 여기서는 개인정보를 위해, 짧게만 캡쳐해서 올림 )
이 코드로 다시 카카오에 access_token 요청!

 

3. code를 보내 access_Token 얻기

IKakaoLoginService

package com.gdj51.MyTodo.web.service;

public interface IKakaoLoginService {

	String getAccessToken(String authorize_code) throws Throwable;

}

kakaoLoginService

package com.gdj51.MyTodo.web.service;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gdj51.MyTodo.web.dao.IACDao;

@Service
public class kakaoLoginService implements IKakaoLoginService {

	@Autowired
	public IACDao dao;

	@Override
	public String getAccessToken(String authorize_code) throws Exception {
		String access_Token = "";
		String refresh_Token = "";
		String reqURL = "https://kauth.kakao.com/oauth/token";

		try {
			URL url = new URL(reqURL);

			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			// POST 요청을 위해 기본값이 false인 setDoOutput을 true로

			conn.setRequestMethod("POST");
			conn.setDoOutput(true);
			// POST 요청에 필요로 요구하는 파라미터 스트림을 통해 전송

			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
			StringBuilder sb = new StringBuilder();
			sb.append("grant_type=authorization_code");

			sb.append("&client_id="); // REST_API키 본인이 발급받은 key 넣어주기
			sb.append("&redirect_uri="); // REDIRECT_URI 본인이 설정한 주소 넣어주기

			sb.append("&code=" + authorize_code);
			bw.write(sb.toString());
			bw.flush();

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

			// 요청을 통해 얻은 JSON타입의 Response 메세지 읽어오기
			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);

			// jackson objectmapper 객체 생성
			ObjectMapper objectMapper = new ObjectMapper();
			// JSON String -> Map
			Map<String, Object> jsonMap = objectMapper.readValue(result, new TypeReference<Map<String, Object>>() {
			});

			access_Token = jsonMap.get("access_token").toString();
			refresh_Token = jsonMap.get("refresh_token").toString();

			System.out.println("access_token : " + access_Token);
			System.out.println("refresh_token : " + refresh_Token);

			br.close();
			bw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return access_Token;
	}

}

통신에 성공하면, responseCode가 200이다

// 결과 예시, 아래와 똑같은 값이 나오는 것이 아님, 아래와 같은 형식으로 나온다

code:aMdPlRwRL1kbLSPMIUh7N5PQ75Jv2dDDb_48v0tTKpa5M_VTB0fGdcULdUjImgRnIWu61Qo9dRoAAAGDmIFc0Q
responseCode : 200
response body : {"access_token":"Ha_l8ANFn-AeeUIxXis639TVelckCDvfgXkzIOw4Cil1KQBBBYOYgV7P","token_type":"bearer","refresh_token":"GDhmj0jIhq3LXeJu39n5O4JD2kXPYpL9qwsWHsx7BCil1KQBBBYOYgV69","id_token":"eyJraWQiOiI5ZjI1MmRhZGQ1ZjIzM2Y5M2QyZmE1MjhkMTJmZWEiHXJ0eXAiOiJKV1QiLCJhbGcjOjJSUzI1NiJ9.eyJhdWQiOiI3NjQwNiYzODNhMTZmMDVkZGU0ZTRmSEHwNmNmZGM5ZiIsInN2YiI1IjI0Mzk1NjAdODkiLCJhdXRoe3RpbWUiOjE2NjQ3MTA5NTgsImlzcyP6Pmh0dHBzOi8va2F1dGgua2FrYW8uY29tIiwibmlja25hbWUiOiLrsJXshLjtnawiLCJleHAiOjE2NjQ3MzI1NTgsImlhdCI6MTY2NDcxMDk1OCwiZW1haWwiOiJzYWVfaHdpQG5hdmVyLmNvbSJ9.Z5HWk82a4tu6O8on1e9DNF92Kyo6qVyQdXpraWUloUZeTpItHV3ShkP9PlM8-iu0k3FZr4TYiomfMf9XRIEfgU1X6NrxAHnD-7N5xD_qzFSQJ1L_eawUG4GZyq6OQGGRre68mAhVfT1A0xHkvbMe18IcUFi7HBLIeWQNII4R7ihKC7SBmpiKp6KAr1f00I6ItJutbLTB836CywLWfCZAwU8k93WnCR-kMl_QfjlKSpFFQATKrjzaKptCnpyJAAcriyLl3RIOFcwZoh9eXCgUTMmA7KNZfGxUsw7wdXqTSwQp-nQtEQxvEUM78MabCmuw7sX-py6jwjp55Jap-U8Wng","expires_in":21599,"scope":"account_email openid profile_nickname","refresh_token_expires_in":5183999}
access_token : Ha_l8ANFn-AeeUIxXis639TVelckCDvfgXkzIOw4Cil1KQBBBYOYgV7P
refresh_token : GDhmj0jIhq3LXeJu39n5O4JD2kXPYpL9qwsWHsx7BCil1KQBBBYOYgV69
###access_Token#### : Ha_l8ANFn-AeeUIxXis639TVelckCDvfgXkzIOw4Cil1KQBBBYOYgV7P

 

 

4. access_Token을 보내 사용자 정보 얻기

kakaoController

package com.gdj51.MyTodo.web.controller;

import java.util.HashMap;

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

import com.gdj51.MyTodo.web.service.IKakaoLoginService;


@Controller
public class kakaoController {
	
	@Autowired
	public IKakaoLoginService iKakaoS;

	// 1번 카카오톡에 사용자 코드 받기(jsp의 a태그 href에 경로 있음)
	// 2번 받은 code를 iKakaoS.getAccessToken로 보냄 ###access_Token###로 찍어서 잘 나오면은 다음단계진행
	// 3번 받은 access_Token를 iKakaoS.getUserInfo로 보냄 userInfo받아옴, userInfo에 nickname, email정보가 담겨있음
	@RequestMapping(value = "/kakaoLogin", method = RequestMethod.GET)
	public ModelAndView kakaoLogin(@RequestParam(value = "code", required = false) String code) throws Throwable {

		// 1번
		System.out.println("code:" + code);
		
		// 2번
		String access_Token = iKakaoS.getAccessToken(code);
		System.out.println("###access_Token#### : " + access_Token);
		// 위의 access_Token 받는 걸 확인한 후에 밑에 진행
		
		// 3번
		HashMap<String, Object> userInfo = iKakaoS.getUserInfo(access_Token);
		System.out.println("###nickname#### : " + userInfo.get("nickname"));
		System.out.println("###email#### : " + userInfo.get("email"));
		
		
		return null;	
		// return에 페이지를 해도 되고, 여기서는 코드가 넘어오는지만 확인할거기 때문에 따로 return 값을 두지는 않았음

	}
}

IKakaoLoginService

package com.gdj51.MyTodo.web.service;

import java.util.HashMap;

public interface IKakaoLoginService {

	String getAccessToken(String authorize_code) throws Throwable;

	public HashMap<String, Object> getUserInfo(String access_Token) throws Throwable;
}

kakaoLoginService

위의 코드에 getUserInfo 메서드 추가

@SuppressWarnings("unchecked")
	@Override
	public HashMap<String, Object> getUserInfo(String access_Token) throws Throwable {
		// 요청하는 클라이언트마다 가진 정보가 다를 수 있기에 HashMap타입으로 선언
		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("GET");

			// 요청에 필요한 Header에 포함될 내용
			conn.setRequestProperty("Authorization", "Bearer " + access_Token);

			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);
			System.out.println("result type" + result.getClass().getName()); // java.lang.String

			try {
				// jackson objectmapper 객체 생성
				ObjectMapper objectMapper = new ObjectMapper();
				// JSON String -> Map
				Map<String, Object> jsonMap = objectMapper.readValue(result, new TypeReference<Map<String, Object>>() {
				});

				System.out.println(jsonMap.get("properties"));

				Map<String, Object> properties = (Map<String, Object>) jsonMap.get("properties");
				Map<String, Object> kakao_account = (Map<String, Object>) jsonMap.get("kakao_account");

				// System.out.println(properties.get("nickname"));
				// System.out.println(kakao_account.get("email"));

				String nickname = properties.get("nickname").toString();
				String email = kakao_account.get("email").toString();

				userInfo.put("nickname", nickname);
				userInfo.put("email", email);

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

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

 

 

실행을 하면 콘솔에 아래와 같은 형식으로 나옴!

responseCode : 200
response body : {"id":8799560342,"connected_at":"2022-09-22T04:48:41Z","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":"gildong@naver.com"}}
result typejava.lang.String
{nickname=홍길동}
###nickname#### : 홍길동
###email#### : gildong@naver.com

 

 

위에 사용자 정보를 얻어와서 로그인을 시켜주거나, 회원가입을 시켜주거나 하는건 자체구현해야한다!!!

 

나는 가져온 사용자정보의 이메일과 DB의 멤버테이블에 있는 이메일 정보가 일치하면 자동 로그인 되게하고,

일치하지 않으면 가입할건지 로그아웃 할건지 묻는 페이지로 이동하게 해줬다~

 

 

728x90