안루피취뽀일기

카카오 api 간편로그인과 회원가입 본문

Spring

카카오 api 간편로그인과 회원가입

안루피 2024. 1. 15. 09:17
728x90

 

카카오 로그인의 흐름은 간단하다. 리소스 오너(사이트)가 카카오 로그인 창을 클라이언트에게 보여주면,

클라이언트는 로그인을 시도하고, 동의 항목을 체크하여 카카오 API 서버에 넘긴다.

전달받은 정보를 가지고 카카오 API 서버는 인가 코드를 발급해주고

인가 코드를 통해 로그인을 유지할 수 있는 토큰을 발급해준다.

 

 

 

 "애플리케이션 추가하기" 를 클릭!

 앱 이름과 사업자명은 필수로 등록

 

 

내 애플리케이션 -> 제품 설정 -> 카카오 로그인

카카오 로그인 활성화 켜주기!!!!

 

 

카카오 로그인을 사용할 클라이언트 도메인 주소를 입력하는 항목이다.

로컬환경에서 테스트 예정이므로 http://localhost:8080으로 설정!

 

 

등록완료

 

카카오 로그인 활성화 설정 화면에서 아래로 스크롤 하면 Redirect URI 입력 항목이 보이는데

카카오 로그인 후 Redirect 되는 URI를 입력해야한다.

http://localhost:8080/kakao/callback으로 설정해줬다.

 

그 다음은 동의 항목 설정 

닉네임, 프로필 사진, 성별, 생일 등 필요한 항목에 대해 동의 설정을 완료하면

카카오 로그인 인증 후에 설정한 동의항목에 대해서 응답데이터로 받을 수 있다.

 

 

 

위 과정을 완료했다면 카카오 로그인 API 사용에 필요한 설정은 끝났다.

API에 필요한 Key만 확인!

 

자자 다음단계 이클립스를 켜봅시다

 

spring starter project 만들어서 

메이븐 프로젝트 하나 만들고 

dependency는 

이렇게!!

7개 설정해주고오

 

 

KakaoVO.java

package com.kh.vo;

import jakarta.persistence.*;
import lombok.*;

@Data
@Entity
public class KakaoUser {
	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="kakaoUser_seq")
	private Long id;
	private String email;
	private String nickname;
	private String name;
	private String birthdate;

}

 

KakaoDTO.java

package com.kh.dto;

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class KakaoDTO {
	private long id;
	private String email;
	private String nickname;
	//add
	private String name;
	private String birthdate;
}

 

KakaoUserRepository.java

package com.kh.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.kh.vo.KakaoUser;

public interface KakaoUserRepository extends JpaRepository<KakaoUser, Long>{
	
}

 

KakaoUserService.java

package com.kh.service;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import com.kh.dto.KakaoDTO;
import com.kh.repository.KakaoUserRepository;
import com.kh.vo.KakaoUser;

@Service
public class KakaoUserService {
	@Value("${kakao.client.id}")
	private String KAKAO_CLIENT_ID;
	
	@Value("${kakao.client.secret}")
	private String KAKAO_CLIENT_SECRET;
	
	//value를 썼기 때문에 각 값을 변수에 넣어서 보관하겠다는 의미
	@Value("${kakao.redirect.url}")
	private String KAKAO_REDIRECT_URL;
	
	//URI는 URL보다 큰 범위 URL은 URI 안에 있는 링크일 뿐
	//카카오 자체에서 인증으로 돌아가는 공식 주소
	private final static String KAKAO_AUTH_URI="https://kauth.kakao.com";
	private final static String KAKAO_API_URI="https://kapi.kakao.com";
	

	private final KakaoUserRepository kakaoUserRepository;
	
	public KakaoUserService(KakaoUserRepository UserRepository) {
		this.kakaoUserRepository = UserRepository;
	}
	
	
	public String getKakaoLogin() {
		return KAKAO_AUTH_URI + "/oauth/authorize?client_id=" + KAKAO_CLIENT_ID + "&redirect_uri=" 
					+ KAKAO_REDIRECT_URL + "&response_type=code";
		
	}
	
	public KakaoDTO getKakaoInfo(String code, String name, String birthdate) throws Exception {
		if(code==null) throw new Exception("존재하는 인증 코드가 없습니다.");
		
		//로그인이 허용된 토큰이 들어갈 공간
		String accessToken="";
		
		//http HEADER에 내 정보를 흘려 보내겠다 작성 
		try {
			HttpHeaders headers = new HttpHeaders();
			headers.add("Content-type", "application/x-www-form-urlencoded");
			
			/*	
			 * 카카오 로그인 아이디 + 시크릿키 + 코드 + 리다이렉트 url 모두 붙여줄 것
				MultiValueMap Spring에서 제공하는 인터페이스
				여러개의 값을 하나의 키에 연결할 수 있도록 합쳐주는 역할
				http 에서 요청이 여러개일때 자주 사용 
				<String, String> 인 이유는 key, value를 각각 String으로 넣을 것 이기 떄문
				MultiValueMap<String, String> key와 ㅍ미ㅕㄷfmf String으로 설정해서 카카오톡에서 설정한 값을 적고 그 값에 대한 내용들을 갖고 오겠다는 의미
				예를 들어
				MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
				params.add("key1", "value1")
				params.add("key1", "value2")
				params.add("key2", "value3")
				key1 -> [value1, value2]
				key2 -> [value3]
		
			*/
			MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
			params.add("grant_type", "authorization_code");
			params.add("client_id", KAKAO_CLIENT_ID);
			params.add("client_secret", KAKAO_CLIENT_SECRET);
			params.add("code", code);
			params.add("redirect_uri", KAKAO_REDIRECT_URL);
			
			//Spring에서 제공하는 것
			//RestTemplate을 사용해서 Http에 요청을 보내고 요청에 대한 응답을 받아오는 템플릿 
			//HTTP 요청을 생성하고 서버를 전달해주는 역할
			RestTemplate restTemplate = new RestTemplate();
			
			//HTTP 요청이나 응답의 헤더 본문 http 메서드를 포함하는 엔티티
			HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(params, headers);
			
			ResponseEntity<String> response = restTemplate.exchange(
					//요청보낼 URI
					//카카오 OAuth 토큰을 얻기 위해서 /oauth/token POST 요청을 보냄
					KAKAO_AUTH_URI + "/oauth/token",
					HttpMethod.POST,
					httpEntity, //본문에서 요청하는 내용과 헤더 정보를 포함하는 객체
					String.class // 서버에서 오는 응답을 String 형태로 받아오겠다는 의미
					);
			
			JSONParser jsonParser = new JSONParser();
			JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody());
			accessToken = (String) jsonObj.get("access_token");
			
			
		
			
		} catch(Exception err) {
			throw new Exception("api를 불러오지 못했습니다.");
		}
		
		return getUserInfoWithToken(accessToken, name, birthdate);
		
		
	}
	
	//카카오에서 회사에게 로그인 할 수 있도록 허용받은 로그인 허용 토큰을 사용해서 카카오 API에서 사용자 정보를 가져오는 메서드
	
	private KakaoDTO getUserInfoWithToken(String accessToken, String name, String birthdate) throws Exception{
		//토큰용 HttpHeader 생성
		HttpHeaders headers = new HttpHeaders();
		//Bearer : HTTP 요청에서 인증할 때 특정 형태로 변환해서 토큰 타입을 나타내는 것 
		headers.add("Authorization", "Bearer " + accessToken);
		headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
		
		//내용을 담을 템플릿 생성
		RestTemplate rt = new RestTemplate();
		HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(headers);
		ResponseEntity<String> response = rt.exchange(
				KAKAO_API_URI + "/v2/user/me",
				HttpMethod.POST,
				httpEntity,
				String.class
			);
		//Response 데이터를 가지고 오기
		JSONParser jsonParser = new JSONParser();
		JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody());
		JSONObject account = (JSONObject) jsonObj.get("kakao_account");
		JSONObject profile = (JSONObject) account.get("profile");
		
		long id = (long) jsonObj.get("id");
		String email = String.valueOf(account.get("email"));
		String nickname = String.valueOf(profile.get("nickname"));
		
		return KakaoDTO.builder()
				.id(id)
				.email(email)
				.nickname(nickname)
				// 이름과 생년월일 추가
				.name(name)
				.birthdate(birthdate)
				.build();
		
	}
	
	//데이터베이스에 저장하는 메서드 생성 
	public KakaoUser registerUser(KakaoDTO kakaoDTO) {
		KakaoUser user = new KakaoUser();
		user.setEmail(kakaoDTO.getEmail());
		user.setNickname(kakaoDTO.getNickname());
		user.setName(kakaoDTO.getName());
		user.setBirthdate(kakaoDTO.getBirthdate());
		
		//사용자를 데이터베이스에 저장
		return kakaoUserRepository.save(user);
	}
	
}

 

KakaoUserController.java

package com.kh.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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.RequestParam;

import com.kh.common.MsgEntity;
import com.kh.dto.KakaoDTO;
import com.kh.service.KakaoUserService;
import com.kh.vo.KakaoUser;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;

@Controller
@RequestMapping("kakao")
@RequiredArgsConstructor
public class KakaoUserController {
	private final KakaoUserService kakaoUserService;
	@GetMapping("/callback")
	public String callback(HttpServletRequest request, 
			@RequestParam(required = false) String name, 
			@RequestParam(required = false) String birthdate, Model model)
					throws Exception{
		// kakao/callback을 작성하면 JSON 형식으로 이동했지만 register html 파일로 이동하게 해줄 것 
		KakaoDTO kakaoInfo = kakaoUserService.getKakaoInfo(request.getParameter("code"), name, birthdate);
		model.addAttribute("kakaoInfo", kakaoInfo);
		return "register";
	}
	
	// 프론트엔드에서 가지고 오는 회원가입에 대한 결과를 전달해주는 PostMapping
	@PostMapping("/register")
	public ResponseEntity<MsgEntity> registerUser(@RequestParam String email, 
			@RequestParam String nickname, @RequestParam String name, @RequestParam String birthdate) {
		
		KakaoDTO kakaoDTO = KakaoDTO.builder()
									.email(email)
									.nickname(nickname)
									.name(name)
									.birthdate(birthdate)
									.build();
		KakaoUser registerdUser = kakaoUserService.registerUser(kakaoDTO);
		
		return ResponseEntity.ok()
				.body(new MsgEntity("Success", registerdUser));
	}

}

 

 

추가로 인덱스 창에 들어갈 수 있도록 HomeController 작성해주기

package com.kh.controller;

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 com.kh.service.KakaoUserService;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class HomeController {

    private final KakaoUserService kakaoService;
    /*
    public HomeController(KakaoUserService kakaoService) {
		this.kakaoService = kakaoService;
	}
    */
    @RequestMapping(value="/", method= RequestMethod.GET)
    public String login(Model model) {
        model.addAttribute("kakaoUrl", kakaoService.getKakaoLogin());

        return "index";
    }

	

}

 

 

 

 

그치만 이미 회원가입이 된 유저도 또 회원가입이 되면 안되는 문제가 해결이 안되었으니

KakaoUserController.java에 이걸 추가해줍니다.

//카카오톡에서 인증한 이메일을 가지고 오는 것이지 DB에서 존재하는 email을 가지고 오는 게 아님 
		KakaoUser existingUser = kakaoUserRepository.findByEmail(kakaoInfo.getEmail());
		if(existingUser != null) {
			session.setAttribute("InUser", existingUser);
			return "redirect:/main";
		}

 

그리고 HomeController.java에는

GetMapping 추가!

//현재는 Kakao 로그인하지만 추후에 SNS 인증을 거치지 않은 회원가입일 경우를 생각해서 GetMapping을 찍어주는 것 
    @GetMapping("/main")
    public String home(Model model, HttpSession session) {
        	KakaoUser InUser = (KakaoUser) session.getAttribute("InUser");
          model.addAttribute("InUser", InUser);
        return "main";
    }

 

 

쨘~@^^@!

 

 

728x90