본문 바로가기

국비과정/팀프로젝트

[팀플] DongneBook_ 로그인구현 (카카오)

https://notspoon.tistory.com/47

 

구글 로그인 쉽게 구현하기 3편 - 로그인 구현하기 (SpringBoot + Vue.js)

로그인 처리 플로우는 아래와 같다. 인가 코드 받기 -> 토큰 받기 -> 사용자 정보 가져오기 해당 글에서는 인가 코드 받기는 클라이언트 페이지에서, 토큰 받기와 사용자 정보 가져오기는 서버에

notspoon.tistory.com

 

아이디저장 

자동로그인(아이디 & 비밀번호 저장)

 

시간남으면 해볼것

> 비밀번호노출 변환

> 로그인창에 베스트셀러 이미지 띄우기 (랜덤으로)

 


[ 카카오 로그인 연동 ]

 

1. 클라이언트 쪽에서 로그인을 한다

2. 카카오 서버는 redirect url code를 전달해준다

3,4. code를 이용하여 access_token을 발급받는다

5. access_token을 서버로 전송한다

6,7. 서버에서는 받은 access_token을 이용하여 카카오 서버에서 사용자 정보를 받는다

8. 받은 사용자 정보를 이용하여 회원가입 또는 로그인을 진행한다

9. JWT등과 같이 사용자 식별 정보를 클라이언트로 보낸다

 


카카오 참고블로그

 

https://velog.io/@shwncho/Spring-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-APIoAuth-2.0

 

[Spring] 카카오 로그인 API(oAuth 2.0)-(1)

예전에 oAuth 2.0 개념에 대해서 공부를 하고, 지금은 그것에 대한 실전편이라고 보면 된다.개념적인 부분은 이전에 설명했기 때문에 링크로 대체한다.\-> https://velog.io/@shwncho/UMC-7%EC%A3%BC%EC%B0%A8 사

velog.io

 

[2021.03.17] 인턴 +16 카카오 로그인(REST API) - 정리 완료 (tistory.com)

 

[2021.03.17] 인턴 +16 카카오 로그인(REST API) - 정리 완료

[2021.03.17] 인턴 +16 카카오 로그인(REST API) - 정리 완료 developers.kakao.com/docs/latest/ko/kakaologin/rest-api Kakao Developers 카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지

injekim97.tistory.com


https://developers.kakao.com/

 

 

Kakao Developers

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

developers.kakao.com

회원가입한다.

 

애플리케이션 생성

 

 

도메인은 localhost로 지정.

 

 

Redirect URI 는 일단 아래의 두개로 설정해줬다.

(Controller에서 아래의 Getmapping으로 login/kakao 를 받아 실행하도록 해줬다.)

 

아래의 앱 키를 이용해 사용자의 코드를 카카오로 보내고 인증토큰을 받아온다.

받아올 사용자 정보를 지정해줄 수 있는데

일단은 닉네임과 카카오계정(이메일)을 선택동의로 받아온다.


[ 테스트_토큰받아오기 ]

 

( login.jsp )

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://developers.kakao.com/sdk/js/kakao.js"></script>
<script type="text/javascript">

function kakaoLogin(){
	location.href="https://kauth.kakao.com/oauth/authorize?client_id=*****REST API*****&redirect_uri=http://localhost/login/kakao&response_type=code"
	//window.open("https://kauth.kakao.com/oauth/authorize?client_id=*****REST API*****&redirect_uri=http://localhost/login&response_type=code",
	//		"_blank", "width=500, height=500");
}
	
</script>	

</head>
<body>
	<h1>로그인화면</h1>
	
....생략....

		<div>
			<button type="submit" onclick="kakaoLogin()">
				<img src="img/login_kakaoBtn.png" alt="kakaoBtn">
			</button>
		</div>
</body>
</html>

 

 

 

( LoginController )

	@ResponseBody
	@GetMapping("/login/kakao")
	public String kakaoLogin(@RequestParam(required = false) String code) {
	        
		// URL에 포함된 code를 이용하여 액세스 토큰 발급
	        System.out.println(code);
            // UDfF6AQkRhxPqI_Cmx5-QDppFvtEb6Y1BLU0rer3QatyoUBv2RPbc1VuPoEkypVeyjtcywoqJQ8AAAGKWi2e0w
	        String access_Token = loginService.getKakaoToken(code);
	        System.out.println(access_Token);
            //r3k9TVZu6kFJSlBiskRRWLVic5TIm30JLxUHRFZFCj1zGAAAAYpaLaCn

	       return "";
	}

jsp에서 이동한 페이지의 code를 잡아와 서비스로 보낸다.

 

( LoginService )   _ gson 사용(import주의) _ gradle 의존성추가

package com.book.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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.book.web.dao.LoginDAO;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;

@Service
public class LoginService {

	@Autowired
	private LoginDAO loginDAO;
	

      public String getKakaoToken(String code){
    	  
    	  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=3ecca13d973c6d11e752a114a1e14922"); // TODO REST_API_KEY 입력
          sb.append("&redirect_uri=http://localhost/login/kakao"); // TODO 인가코드 받은 redirect_uri 입력
          sb.append("&code=" + 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);

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

          access_Token = element.getAsJsonObject().get("access_token").getAsString();
          refresh_Token = element.getAsJsonObject().get("refresh_token").getAsString();

          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;
	
      }
}

build.gradle에 Gson 의존성 추가

	// Gson
	implementation'com.google.code.gson:gson:2.8.7'

그런데 2.8.7 버전에서는 parse() 메소드 대신 다른걸 사용한단다... 재확인필요


이렇게 해주면 카카로 로그인 버튼을 눌렀을 때 카카오로그인 & 정보제공동의를 하면

(사용자) code를 포함한 아래의 설정주소로 이동한다.

위의 code 부분을 controller에서 잡아서 서비스에 넘기고

서비스에서도 이런저런 처리를 통해 카카오로 보내 인증토큰을 받아온다.

성공하면 아래처럼 응답코드 200과 함께 토큰을 잘 가져온다. 

responseCode : 200
response body : {"access_token":"r3k9TVZu6kFJSlBiskRRWLVic5TIm30JLxUHRFZFCj1zGAAAAYpaLaCn","token_type":"bearer","refresh_token":"jGpcwO6bO9Q3cpns8yR08jDdK9UPo3tbVI6HMvOdCj1zGAAAAYpaLaCm","id_token":"eyJraWQiOiI5ZjI1MmRhZGQ1ZjIzM2Y5M2QyZmE1MjhkMTJmZWEiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzZWNjYTEzZDk3M2M2ZDExZTc1MmExMTRhMWUxNDkyMiIsInN1YiI6IjMwMDI3NTE0ODMiLCJhdXRoX3RpbWUiOjE2OTM3MzAwNTQsImlzcyI6Imh0dHBzOi8va2F1dGgua2FrYW8uY29tIiwibmlja25hbWUiOiJQWU8iLCJleHAiOjE2OTM3NTE2NTQsImlhdCI6MTY5MzczMDA1NCwiZW1haWwiOiJnb2d1czIyOEBoYW5tYWlsLm5ldCJ9.OgfFrEmgHDhOLn23ERlPc3XHELkoQuYSfimR60qaE9innzlY617Xtnyn-9-_NbwLJP4nwJiCHKluNdemqxiz7qxrda2IYE9Wv_6aWvVConMuIWgDPShvSYdy9sdUuYnRo6IjkUW64t8NGTw1BhJFokLQ23XhZb0DWee8ULKPLKklZyZ4DsknESBtKJB1wUlMfJOP8753Wf2TXjtrtTVCy8GO0oehRMi4Ny-CV205_5S1JewvvmdN612oN1y0oMydXgp7jHFimmS9QyEozbWpXQFgsUoOrjQFemads3BrpUfhp49FQK1o4ZnKEdDkQccGaSfmw-UOidYkUW08ByY6zQ","expires_in":21599,"scope":"account_email openid profile_nickname","refresh_token_expires_in":5183999}
access_token : r3k9TVZu6kFJSlBiskRRWLVic5TIm30JLxUHRFZFCj1zGAAAAYpaLaCn
refresh_token : jGpcwO6bO9Q3cpns8yR08jDdK9UPo3tbVI6HMvOdCj1zGAAAAYpaLaCm

위에서 받아온 access_Token을 가지고 

서비스에 getKakaoUser() 메소드 실행

	@ResponseBody
	@GetMapping("/login/kakao")
	public String kakaoLogin(@RequestParam(required = false) String code) {
	        
		// URL에 포함된 code를 이용하여 액세스 토큰 발급
	        //System.out.println(code);
	        String access_Token = loginService.getKakaoToken(code);
	        //System.out.println(accessToken);
	        loginService.getKakaoUser(access_Token);
	        
	       return "";
	}

 

서비스에서 이 token을 카카오로 보내 id와 이메일계정을 가져온다.

  public String getKakaoToken(String code){
  	....생략....
  }

  
  
  public void getKakaoUser(String token){

          String reqURL = "https://kapi.kakao.com/v2/user/me";

          //access_token을 이용하여 사용자 정보 조회
          try {
              URL url = new URL(reqURL);
              HttpURLConnection conn = (HttpURLConnection) url.openConnection();

              conn.setRequestMethod("POST");
              conn.setDoOutput(true);
              conn.setRequestProperty("Authorization", "Bearer " + token); //전송할 header 작성, access_token전송

              //결과 코드가 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);

              //Gson 라이브러리로 JSON파싱
              JsonParser parser = new JsonParser();
              JsonElement element = parser.parse(result);
              
              // 받아올값 : profile_nickname(닉네임), kakao_account(이메일)
              Long id = element.getAsJsonObject().get("id").getAsLong();
              boolean hasEmail = element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("has_email").getAsBoolean();
              String email = "";
              
              if (hasEmail) {
                  email = element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("email").getAsString();
              }

              System.out.println("id : " + id);
              System.out.println("email : " + email);

              br.close();

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

성공시 user 정보를 콘솔창에 잘 불러온다.

id : 3002751483
email : gogus228@hanmail.net

닉네임도 가져올 수 있도록 신청해놨는데 왜 못불러올까...다시 해보기

 


서비스에서 잡은 id & email값을 컨트롤러에서 잡아서 db로 보내 기존 members 의 데이터와 비교할 예정 

auto 라는 이름의 필드를 추가해 기본값은 0으로 설정해줬다. 카카오 로그인을 했을 때 auto가 1로 바뀌도록 설정해준다.

카카오로그인 (버튼클릭시)

1. 새로운 계정생성 : 카카오 계정(email)과 일치하는 email이 없고, 있더라도 auto가 0일때

    새로 생성한 계정의 id를 session에 저장.

2. 기존계정으로 처리: 카카오 계정(email)과 일치하는 email이 있고, auto가 1일때

     해당 계정의 id를 session에 저장.


서비스에서는 kmap 을 생성해서 kid, kemail 값을 담아 내보낸다.

package com.book.web.Login;

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.google.gson.JsonElement;
import com.google.gson.JsonParser;

@Service
public class LoginService {

	@Autowired
	private LoginDAO loginDAO;
	
	
	// 카카오에 코드전송 & 인증토큰받기
      public String getKakaoToken(String code){
    	  
    	  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=3ecca13d973c6d11e752a114a1e14922"); // TODO REST_API_KEY 입력
          sb.append("&redirect_uri=http://localhost/login/kakao"); // TODO 인가코드 받은 redirect_uri 입력
          sb.append("&code=" + 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);

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

          access_Token = element.getAsJsonObject().get("access_token").getAsString();
          refresh_Token = element.getAsJsonObject().get("refresh_token").getAsString();

          //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;
      }
      
      
   // 카카오에 토큰전송 & 사용자정보받기
      public  Map<String, Object> getKakaoUser(String access_Token){

          Map<String, Object> kmap = new HashMap<>();

          String reqURL = "https://kapi.kakao.com/v2/user/me";
          String email = "";
          String nickname = "";

          //access_token을 이용하여 사용자 정보 조회
          try {
              URL url = new URL(reqURL);
              HttpURLConnection conn = (HttpURLConnection) url.openConnection();

              conn.setRequestMethod("POST");
              conn.setDoOutput(true);
              conn.setRequestProperty("Authorization", "Bearer " + access_Token); //전송할 header 작성, access_token전송

              //결과 코드가 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);

              //Gson 라이브러리로 JSON파싱
              JsonParser parser = new JsonParser();
              JsonElement element = parser.parse(result);
              
              // 받아올값 : profile_nickname(닉네임), kakao_account(이메일)
              Long id = element.getAsJsonObject().get("id").getAsLong();
              boolean hasEmail = element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("has_email").getAsBoolean();
              //boolean hasName = element.getAsJsonObject().get("profile_nickname").getAsJsonObject().get("has_Name").getAsBoolean();
              
              if (hasEmail) {
                  email = element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("email").getAsString();
              }
              
//              if (hasName) {
//                  nickname = element.getAsJsonObject().get("profile_nickname").getAsJsonObject().get("nickname").getAsString();
//              }

              System.out.println("id : " + id);
              System.out.println("email : " + email);
              //System.out.println("nickname : " + nickname);
              
              kmap.put("kid", id);
              kmap.put("kemail", email);
              
              //System.out.println(kmap); // {kid=3002751483, kemail=gogus228@hanmail.net}

              br.close();

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


	public int hasKakaoUser(Map<String, Object> kUser) {
		return loginDAO.hasKakaoUser(kUser);
	}

	public void setKakaoUser(Map<String, Object> kUser) {
		loginDAO.setKakaoUser(kUser);
	}

	public Map<String, Object> autoLogin(String suserID) {
		return loginDAO.autoLogin(suserID);
	}

}

 

 

컨트롤러에서는 access_Token를 매개변수로 getKakaoUser() 메소드를 실행시켜 

결과값을(kid & kemail) kUser에 담는다. 

kUser를 매개변수로 hasKakaoUser() 메소드를 실행시켜 db에 해당 kemail 계정을 가지고 있으면서 auto값이 1인 데이터를 찾는다. (있다면 1, 없다면 0을 리턴)

 

result = 1 :  db에 kakao 계정정보 있다면 로그인진행 (해당 id를 세션에 저장)

result = 1 :  db에 kakao 계정정보 없다면 db에 kakao계정(kid & kemail)으로 새로운 계정 생성 (INSERT)  & auto 1로 설정

  subjoin 페이지를 띄워서 이름&주소 입력 후 db에 저장(UPDATE)시킨다.

                                                        JoinController에서 subjoin() 메소드로 처리해줬다.

	@GetMapping("/login/kakao")
	public String kakaoLogin(@RequestParam(required = false) String code, HttpSession session, Model model) {

		// URL에 포함된 code를 이용하여 액세스 토큰 발급
		// System.out.println(code);
		String access_Token = loginService.getKakaoToken(code);
		// System.out.println(access_Token);
		Map<String, Object> kUser = loginService.getKakaoUser(access_Token);
		// System.out.println(kUser); // {kid=3002751483, kemail=gogus228@hanmail.net}

		// kakao 로그인기록 확인
		int result = loginService.hasKakaoUser(kUser);

		if (kUser != null) { // kakao연결성공

			if (result == 1) {
				// db에 kakao계정정보 있다면 로그인진행
				return "redirect:/";

			} else {
				loginService.setKakaoUser(kUser); // db에 kakao계정정보 없다면 생성(id&email)

				session.setAttribute("mid", kUser.get("kid")); // kid 세션에 저장
				model.addAttribute("memail", kUser.get("kemail"));

				// 위의 mid, memail은 subjoin에 자동기입
				return "/subjoin";
			}

		} else {
			return "redirect:login";
		}
	}

[ loginMapper ]

	<insert id="setKakaoUser" parameterType="Map">
	 	INSERT INTO members (mid, memail, kakao)
     	VALUES(#{kid} ,#{kemail}, 1)
	</insert>

[ JoinController ]

package com.book.web.join;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class JoinController {
	
	@Autowired
	private JoinService joinService;

	...생략...
	
	
	// 로그인연동 추가정보
	@GetMapping("/subjoin")
	public String subjoin() {
		return "subjoin";
	}
	
	
	@PostMapping("/login/subjoin")
	public String subjoin(JoinDTO joinDTO) {
		
		joinService.subjoin(joinDTO);
		//System.out.println("jsp에서 오는 값 : " + joinDTO);
		System.out.println("k계정넣기성공");
		return "redirect:/";
	}
	
}

[ joinMapper ]

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.book.web.join.JoinDAO">

	...생략...
	
	<update id="subjoin" parameterType="JoinDTO">
		UPDATE members
        SET mname=#{mname}, maddr=#{maddr}, mpw=#{mid}
	    WHERE memail=#{memail} and mid=#{mid}
	</update>

</mapper>

 

[ subjoin.jsp ]

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 연동시 추가정보 입력창</title>
<script src="./js/jquery-3.7.0.min.js"></script>
<script type="text/javascript">
	
</script>

</head>
<body>
		<form action="./subjoin" method="post">
		<div class="join-div" align="center">
			<div>
			
				<h1>추가정보입력</h1>
			</div>
			
			<div>
				<div>아이디</div>
				<div>
					<input type="text" name="mid" id="mid" value="${sessionScope.mid}" />
				</div>
			</div>
					<div>
						<div>이름</div>
						<div>
							<input type="text" name="mname" id="mname" placeholder="이름을 입력해 주세요"/>
						</div>
					</div>
					<div>
						<div>주소</div>
						<div>
							<input type="text" name="maddr" placeholder="주소를 입력해 주세요"/>
						</div>
					</div>
		
					<div>
						<div>이메일</div>
						<div>
							<input type="text" name="memail" id="memail" value="${memail }" />
						</div>
					</div>
					<div>
						<button type="reset">취소</button>
						<button type="submit" id="joinjoin">가입</button>
					</div>

		</div>
	</form>
</body>
</html>