본문 바로가기

Develop/Back-End

스프링 부트 카카오 로그인 API 기능 추가하기 😳

 

공모전을 진행하면서

카카오 API를 이용하면 가산점이 있었기에 😛

만들고 있는 기능 중 회원가입하는 부분이 있었고

카카오는 많은 사용자들이 이용중인 플랫폼이기에 사용자 측면에서 편의성을 주고자

 

카카오 로그인 기능을 추가하게 되었다.

 

당시 api 서버 환경은 

 

jdk 17

spring 6.0.8

spring boot 3.0.6

jpa 

security

oauth2

 

 

시큐리티를 사용하고 있으니

oauth2를 이용하여 로그인까지 연결 시키고 싶었는데

이 방법대로는 내가 원하는 대로 흘러가지 않아서 결국에는 api식을로 개발했던 것 같네 지금 코드를 보니 😇

 

시간이 더 많았으면 스프링 환경을 더 봐서 코드를 깔끔하게 할 수 있었을 것 같은데

조금 아쉬움이 남네 


 

진행상황

1.카카오 디벨로퍼에서 카카오 로그인 프로세스 이해하기

2. oauth2 개념 이해하기 

3. api vs security oauth2 


이슈 & 고민 및 해결

 

프론트에서 카카오 로그인 시 백엔드로 통신하고 인증/인가 

확인 후 jwt 토큰 내려주는 프로세스로 개발하고 싶었는데 

api 서버에 oauth2까지 같이 사용하고 있다보니

필터에 불필요 예외처리 코드가 너무 늘어나는 것 같아서

카카오 로그인 api로 분리하게 되었다. 😥

 


참고 레퍼런스

 

1. 카카오 로그인 이해하기

https://developers.kakao.com/docs/latest/ko/kakaologin/common

 

Kakao Developers

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

developers.kakao.com

2. 카카오 로그인 설정하기

https://developers.kakao.com/docs/latest/ko/kakaologin/prerequisite

 

Kakao Developers

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

developers.kakao.com

 

3. 인증&인가

https://dev.gmarket.com/45

 

인증/인가는 어디에 어떻게 구현해야 할까?

안녕하세요 Post-tx & Accounting팀 권우석입니다. 최근 온보딩 프로젝트를 함께했던 Shopping Service API팀의 김도훈님의 제안으로 회원가입/로그인 API를 간단하게 구현하는 토이 프로젝트를 진행하고

dev.gmarket.com

 

 

4. 사용한 코드 github

https://github.com/DongJu-Na/Nadry

 

GitHub - DongJu-Na/Nadry: 여행

여행. Contribute to DongJu-Na/Nadry development by creating an account on GitHub.

github.com


TO DO List

1. 스프링 환경 공부

2. 네이버,구글,github login 기능 추가해보기


Example Code

카카오 로그인 api 예제 코드

 

1. 컨트롤러

package com.nadeul.ndj.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.nadeul.ndj.dto.ApiResponse;
import com.nadeul.ndj.dto.KakaoDto;
import com.nadeul.ndj.dto.MemberDto;
import com.nadeul.ndj.service.KakaoApiService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@Tag(name = "KakaoService", description = "카카오 API")
@RequestMapping("/api/v1/kakao")
@RequiredArgsConstructor
@RestController
public class KakaoApiController<T> {
	
	private final KakaoApiService<T> service;
	
	@PostMapping("/authenticate")
	@Operation(summary = "카카오 로그인", description = "카카오 계정으로 로그인")
    public ResponseEntity<ApiResponse<MemberDto>> kakaoAuthenticate(@RequestBody KakaoDto request) {
        return ResponseEntity.ok(service.kakaoAuthenticate(request));
    }

}

2. 서비스

@value 어노테이션 부분에는

카카오에서 발급받은 클라이언트 id와  설정해놓은 redirect url 할당해서 사용하면 된다.

package com.nadeul.ndj.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.List;
import java.util.Optional;

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

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nadeul.ndj.dto.ApiResponse;
import com.nadeul.ndj.dto.KakaoDto;
import com.nadeul.ndj.dto.MemberDto;
import com.nadeul.ndj.entity.Member;
import com.nadeul.ndj.entity.Token;
import com.nadeul.ndj.enums.ApiResponseEnum;
import com.nadeul.ndj.model.Role;
import com.nadeul.ndj.model.TokenType;
import com.nadeul.ndj.repository.MemberRepository;
import com.nadeul.ndj.repository.TokenRepository;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class KakaoApiService<T> {
	
	private final MemberRepository memberRepository;
    private final TokenRepository tokenRepository;
    private final JwtService jwtService;
    
    @Value("${spring.security.oauth2.client.registration.kakao.client-id}")
    private String CLIENT_ID;
    @Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}")
    private String REDIRECT_URI;
    
		
      @SuppressWarnings("unchecked")
	  public ApiResponse<MemberDto> kakaoAuthenticate(KakaoDto request) {
		  
    	  String kakaoAccessToken = getKaKaoAccessToken(request.getCode());
    	  HashMap<String, Object> kakaoInfo = getUserKakaoInfo(kakaoAccessToken);
    	  
    	  
  		  String email = (String) kakaoInfo.get("email");
  		  
  		  Optional<Member> existingMember = memberRepository.findByEmail(email);
  		  
  		if(existingMember.isPresent()) {
  			Member member = Member.builder()
  				  .memId(existingMember.get().getMemId())
  				  .name(kakaoInfo.get("name").toString())
  		          .email(kakaoInfo.get("email").toString())
  		          .profileUrl(kakaoInfo.get("profileUrl").toString())
  		          .build();
  			
  			var savedUser = memberRepository.save(member);
  			
  			String jwtToken = jwtService.generateToken(existingMember.get());
        	String refreshToken = jwtService.generateRefreshToken(existingMember.get());
        	List<Token> validUserTokens = tokenRepository.findAllValidTokenByUser(existingMember.get().getMemId());
      	    validUserTokens.forEach(token -> {
      	      token.setExpired(true);
      	      token.setRevoked(true);
      	    });
      	    tokenRepository.saveAll(validUserTokens);
      	    
      	    Token token = Token.builder()
    	            .member(existingMember.get())
    	            .token(jwtToken)
    	            .tokenType(TokenType.BEARER)
    	            .expired(false)
    	            .revoked(false)
    	            .build();
    	        tokenRepository.save(token);   
    	    
    	     MemberDto memberDto = new MemberDto();
  			  memberDto.setMemId(existingMember.get().getMemId());
  			  memberDto.setName(existingMember.get().getName());
  			  memberDto.setEmail(existingMember.get().getEmail());
  			  memberDto.setPassword(existingMember.get().getPassword());
  			  memberDto.setBirthDay(existingMember.get().getBirthDay());
  			  memberDto.setRole(existingMember.get().getRole());
  			  memberDto.setProfileUrl(existingMember.get().getProfileUrl());
  			  
  			return ApiResponse.successResponse(ApiResponseEnum.SUCCESS,memberDto,jwtToken,refreshToken);
  		}else {
  			 Member member = Member.builder()
  				
  		          .name(kakaoInfo.get("name").toString())
  		          .email(kakaoInfo.get("email").toString())
  		          .password(null)
  		          .role(Role.USER)
  		          .birthDay(null)
  		          .profileUrl(kakaoInfo.get("profileUrl").toString())  
  		          .build();
  		      var savedUser = memberRepository.save(member);
  		      var jwtToken = jwtService.generateToken(member);
  		      var refreshToken = jwtService.generateRefreshToken(member);
  		      
  		      MemberDto memberDto = new MemberDto();
			  memberDto.setMemId(member.getMemId());
			  memberDto.setName(member.getName());
			  memberDto.setEmail(member.getEmail());
			  memberDto.setPassword(member.getPassword());
			  memberDto.setBirthDay(member.getBirthDay());
			  memberDto.setRole(member.getRole());
			  memberDto.setProfileUrl(member.getProfileUrl());
  		      
  		    return ApiResponse.successResponse(ApiResponseEnum.SUCCESS,memberDto,jwtToken,refreshToken);
  		}
  		
	  }
      
      
      public String getKaKaoAccessToken(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();

    	        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=" + CLIENT_ID);
    	        sb.append("&redirect_uri=" + REDIRECT_URI);
    	        sb.append("&code=" + code);
    	        bw.write(sb.toString());
    	        bw.flush();

    	        int responseCode = conn.getResponseCode();
    	        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    	        String line = "";
    	        String result = "";

    	        while ((line = br.readLine()) != null) {
    	            result += line;
    	        }
    	        
    	        ObjectMapper objectMapper = new ObjectMapper();
    	        JsonNode rootNode = objectMapper.readTree(result);
    	        
    	        access_Token = rootNode.get("access_token").asText();
    	        refresh_Token = rootNode.get("refresh_token").asText();

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

    	    return access_Token;
    	}
      
      public HashMap<String, Object> getUserKakaoInfo(String access_Token) {

    	    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");

    	        conn.setRequestProperty("Authorization", "Bearer " + access_Token);

    	        int responseCode = conn.getResponseCode();

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

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

    	        while ((line = br.readLine()) != null) {
    	            result += line;
    	        }

    	        ObjectMapper objectMapper = new ObjectMapper();
    	        JsonNode rootNode = objectMapper.readTree(result.toString());

    	        String id = rootNode.path("id").asText();

    	        JsonNode propertiesNode = rootNode.path("properties");
    	        JsonNode kakaoAccountNode = rootNode.path("kakao_account");

    	        String nickname = propertiesNode.path("nickname").asText();
    	        if (kakaoAccountNode.has("email")) {
    	            String email = kakaoAccountNode.path("email").asText();
    	            userInfo.put("email", email);
    	        }

    	        userInfo.put("name", nickname);
    	        userInfo.put("id", id);
    	        userInfo.put("profileUrl", propertiesNode.path("thumbnail_image").asText());
    	        userInfo.put("role", Role.USER);

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

 

반응형