팀프로젝트/SpringBoot

스프링부트 팀플) 20240328 카카오톡 로그인

일일일코_장민기 2024. 3. 28. 14:35
728x90

https://innovation123.tistory.com/181

 

[Spring] 카카오 로그인 API 사용 방법

Kakao Developers 이미지 설명에 각 이미지 내용에 대한 위치(경로) 적어뒀으니 참고해 주세요. 1. KakaoDevelopers 접속 https://developers.kakao.com/ Kakao Developers 카카오 API를 활용하여 다양한 어플리케이션을

innovation123.tistory.com

https://innovation123.tistory.com/181

 

[Spring] 카카오 로그인 API 사용 방법

Kakao Developers 이미지 설명에 각 이미지 내용에 대한 위치(경로) 적어뒀으니 참고해 주세요. 1. KakaoDevelopers 접속 https://developers.kakao.com/ Kakao Developers 카카오 API를 활용하여 다양한 어플리케이션을

innovation123.tistory.com

https://developers.kakao.com/

 

Kakao Developers

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

developers.kakao.com

https://developers.kakao.com/docs/latest/ko/kakaologin/trouble-shooting#code

 

Kakao Developers

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

developers.kakao.com

 

 

application.properties

#Kakao Login

client_id=REST API 키

redirect_uri= Redirect URI

secret_code= Client Secret(보안에서 설정되어 있지 않으면 필요 없음) 

 

 

KakaoLoginController
package com.moonBam.controller.member.OpenApi;
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.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.moonBam.controller.member.DebugBoardController;
import com.moonBam.controller.member.SecurityController;
import com.moonBam.dto.MemberDTO;
import com.moonBam.service.member.OpenApiService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
public class KakaoLoginController {

    @Value("${client_id}")
    private String client_id;

    @Value("${redirect_uri}")
    private String redirect_uri;
    
    @Value("${secret_code}")
    private String secret_code;
    
    @Autowired
    OpenApiService serv;
    
    @Autowired
    DebugBoardController dbc;
    
    @Autowired
    SecurityController sc;
    
    @Autowired
    OpenApiController oac;

    // 카카오에서 토큰 받아오기
    @GetMapping("/getKakaoAuthUrl")
    public ResponseEntity<Object> getKakaoAuthUrl() throws URISyntaxException {
    	String reqUrl = "https://kauth.kakao.com/oauth/authorize?client_id="+client_id+"&redirect_uri="+redirect_uri+"&response_type=code";
        URI redirectUri = new URI(reqUrl);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(redirectUri);
        return new ResponseEntity<>(httpHeaders, HttpStatus.SEE_OTHER);
    }
    
    // 카카오에서 리다이렉션
    @RequestMapping(value = "kakaoLogin")
    public ModelAndView getKakaoCode(String code, HttpSession session) throws IOException{
    	//1. 받은 인가 코드 받기
    	//System.out.println(code);
    	
    	//2. 코드를 통해 토큰 받기
    	String accessToken = getAccessToken(code);
    	//System.out.println("accessToken: "+accessToken);
    	
    	//3. 사용자 정보 받기
    	Map<String, Object> map = getUserInfo(accessToken);
    	ObjectMapper objectMapper = new ObjectMapper();

    	//아이디
    	String id = sc.encrypt(String.valueOf(map.get("id")));
    	
    	//이미 가입한 사람인지 확인
        MemberDTO check  = serv.selectOneAPIMember(id);
        ModelAndView mav = new ModelAndView();
      
        //미가입자일 경우, 자동 가입
        if(check == null) {
        	
            //비밀번호
            String pw = sc.encrypt("Kakao"+dbc.getNum(16));

            //이름
            String jsonString = objectMapper.writeValueAsString(map.get("properties"));
            JsonNode jsonNode = objectMapper.readTree(jsonString);
            String name = jsonNode.get("nickname").asText();

            //닉네임
            String nickname = oac.randomNickname();
            		
            //이메일
            String jsonString2 = objectMapper.writeValueAsString(map.get("kakao_account"));
            JsonNode jsonNode2 = objectMapper.readTree(jsonString2);
            String email = jsonNode2.get("email").asText();
            String[] emailParts = email.split("@");
            
            //유저 가입일
    		Date currentDate = new Date();
    			SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
    			String userSignDate = dateFormat.format(currentDate);
        	
    		MemberDTO dto = new MemberDTO();
	    		dto.setUserId(id);
	          	dto.setUserPw(pw);					
	          	dto.setUserName(name);
	          	dto.setNickname(nickname);
	          	dto.setUserEmailId(emailParts[0]);			
	          	dto.setUserEmailDomain(emailParts[1]);				
	          	dto.setUserSignDate(userSignDate);
	  			dto.setUserType("1");
        	
	  		//회원가입
	  		serv.insertAPIMember(dto);	
	    	
	  		//닉네임 변경하는 화면으로 이동
	  		MemberDTO nDTO  = serv.selectOneAPIMember(dto.getUserId());
	  		session.setAttribute("loginUser", nDTO);
	  		mav.addObject("dto", nDTO);
		    mav.setViewName("member/Login/APILogin");
		    return mav; 
        
	      //가입자일 경우, 메인으로 이동
	      } else {
		    session.setAttribute("loginUser", check);
	        mav.setViewName("redirect:/");
	        return mav;
	      }
    }
    
    
    // 카카오에서 액세스 토큰 받아오기    
    public String getAccessToken(String code) throws IOException {
        String accessToken = "";
        String refreshToken = "";
        String reqUrl = "https://kauth.kakao.com/oauth/token";

            URL url = new URL(reqUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            
            conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
            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=").append(client_id);
            sb.append("&redirect_uri=").append(redirect_uri);
            sb.append("&code=").append(code);
            sb.append("&client_secret=").append(secret_code);		

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

            int responseCode = conn.getResponseCode();
            log.info("[KakaoApi.getAccessToken] responseCode = {}", responseCode);

            System.out.println(responseCode);
            
            BufferedReader br;
            if (responseCode >= 200 && responseCode <= 300) {
                br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            } else {
                br = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
            }

            String line = "";
            StringBuilder responseSb = new StringBuilder();
            while((line = br.readLine()) != null){
                responseSb.append(line);
            }
            System.out.println("responseSb: "+responseSb);
            String resultJson = responseSb.toString();
            ObjectMapper objectMapper = new ObjectMapper();
            String resultString = objectMapper.writeValueAsString(resultJson);
            Map<String, Object> map = objectMapper.readValue(resultJson, Map.class);
            String result = (String) map.get("access_token");   
            
            br.close();
            bw.close();
            
            return result;
    }
    
    // 카카오에서 액세스 토큰에서 유저 데이터 받아오기
    public Map<String, Object> getUserInfo(String accessToken) throws IOException {
        HashMap<String, Object> userInfo = new HashMap<>();
        String reqUrl = "https://kapi.kakao.com/v2/user/me";

        URL url = new URL(reqUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Authorization", "Bearer " + accessToken);
            conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

            int responseCode = conn.getResponseCode();
            log.info("[KakaoApi.getUserInfo] responseCode : {}",  responseCode);

            BufferedReader br;
            if (responseCode >= 200 && responseCode <= 300) {
                br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            } else {
                br = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
            }

            String line = "";
            StringBuilder responseSb = new StringBuilder();
            while((line = br.readLine()) != null){
                responseSb.append(line);
            }
            String resultJson = responseSb.toString();
            ObjectMapper objectMapper = new ObjectMapper();
            String resultString = objectMapper.writeValueAsString(resultJson);
            Map<String, Object> map = objectMapper.readValue(resultJson, Map.class);
            
            br.close();

          return map;
    }
    
    
}

 

OpenApiController

 

//랜덤 이름 생성기
	@PostMapping("/randomNickname")
    public @ResponseBody String randomNickname(){
    	List<String> adjectives = Arrays.asList("행복한", "슬픈", "게으른", "슬기로운", "수줍은",
                "그리운", "더러운", "섹시한", "배고픈", "배부른", "부자", "재벌", "웃고있는", "깨발랄한",
                "못난", "날렵한", "긍정적인", "부정적인", "똑똑한", "멋진");
        List<String> names = Arrays.asList("aa", "bb", "cc", "dd", "ee", "ff",
                "gg", "hh", "ii", "jj");

        String number = (int)(Math.random() * 99)+1 +"";
        
        Collections.shuffle(adjectives);
        Collections.shuffle(names);
        String adj = adjectives.get(0);
        String name = names.get(0);
        
        return adj+"_"+name+"_"+number;
    }
APILogin
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>닉네임 변경</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> 
    <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
</head>

<body>
    <div class="container mt-5">
        <div class="card">
            <div class="card-body">
                <h3 class="card-title">닉네임을 변경하시겠습니까?</h3>
                <form action="<c:url value='/changeNickname'/>" method="post">
                    <input type="hidden" name="userId" value="${dto.userId}">
                    <input type="hidden" id="check" value="${dto.nickname}">
                    <input type="text" id="nickname" name="nickname" value="${dto.nickname}" minlength="2" required autofocus="autofocus" class="form-control mb-3">
                    <span id="confirmNicknameError" style="color: red;"></span>
                    <div class="d-grid gap-2 d-md-flex justify-content-md-end mt-3">
                    	<input type="button" id="autoNickname" name="clickType" value="자동 닉네임 생성" class="btn btn-success me-md-2">
                        <input type="submit" name="clickType" value="변경하기" class="btn btn-primary me-md-2">
                        <input type="submit" name="clickType" value="그대로 사용하기" class="btn btn-secondary">
                    </div>
                </form>
            </div>
        </div>
    </div>

    <script type="text/javascript">

	  	//새로고침, 뒤로가기, 나가기 시 경고창 함수
		function f5Control(event){
			event.preventDefault();
		    event.returnValue = '';
		}
    
    	$(function(){
    		
			//새로고침, 뒤로가기, 나가기 시 경고창 함수 출력
			window.addEventListener('beforeunload', f5Control);
			
			//뒤로가기 단축키을 누르면 로그인 메인으로 이동(Alt + <- 기능)(브라우저 뒤로가기 버튼은 막히지 않음)
			window.history.pushState(null, null, window.location.href);
			window.onpopstate = function(event) {
				window.history.pushState(null, null, window.location.href);
				window.location.href= "<c:url value='/'/>"; 
			};
   		    
			$(".btn").on("click", function(){
				window.removeEventListener('beforeunload', f5Control);
	 		})
        	
        	//자동 닉네임 생성기
        	$("#autoNickname").on("click", function(){
        		var nicknameSpace = $("#nickname");
        		$.ajax({
                    type: "POST",
                    url: "<c:url value='/randomNickname'/>", 
                    success: function (response) {
                    	nicknameSpace.val(response);
                    },
                    error: function (error) {
                        console.error("닉네임 자동 생성기 에러:", error);
                    }
                })
        	})
        	
        	
            //닉네임 중복 확인
            var prevNickname = ""; 
            $("#nickname").on("focusout", function() {
                var nickname = $("#nickname").val();
                var errorSpan = $("#confirmNicknameError");
                var check = $("#check").val()
                
                if(check !== nickname){
                    if (nickname !== prevNickname) {
                        $.ajax({
                            type: "POST",
                            url: "<c:url value='/AjaxNicknameDuplicate'/>", 
                            data: { nickname: nickname },
                            success: function (response) {
                                //닉네임이 DB에 저장된 닉네임과 일치하는 데이터가 있을 경우, ajax 출력
                                if (response === "duplicate") {
                                    errorSpan.text("이미 사용 중인 닉네임입니다.");
                                } else {
                                    errorSpan.text("");
                                } 
                            },
                            error: function (error) {
                                console.error("닉네임 중복 검사 에러:", error);
                            }
                        })
                        prevNickname = nickname;
                    };
                };
            });
            
            //닉네임 중복일 경우, 이벤트 중지
            $("form").on("submit", function(){
                if($("#confirmNicknameError").text() == "이미 사용 중인 닉네임입니다."){
                    event.preventDefault();
                    alert("중복된 닉네임입니다.");
                } else {
                    $("form")[0].submit();
                }
            })
        })
    </script>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>

 

 

자동 닉네임 생성을 누르면 바뀐다. 임의로 닉네임을 변경할 수도 있다.
새로 가입이 잘 되었다.