🙈

⃝ 동글동글 ⃝

🪐ᐩ˖ 🍎

Spring/Kakao Social Login

[SpringBoot + React] 카카오 소셜 로그인 REST API 방식 구현 - 3. 카카오 소셜 로그인 구현

JONG_UK 2023. 3. 30. 17:08
728x90
반응형
SMALL

[SpringBoot + React] 카카오 소셜 로그인 REST API 방식 구현 - 3. 카카오 소셜 로그인 구현

💡 목차
1. 카카오 소셜 로그인 사전 준비사항
2. 카카오 소셜 로그인 구조 분석
3. 구현 단계

글을 읽기 앞서 저는 React와 SpringBoot를 이용하여 카카오 소셜 로그인을 구현하는 과정을 담았습니다.

SpringBoot에서만 카카오 소셜 로그인을 수행하는 과정을 필요로 하신다면 제 글을 읽으실 필요는 없습니다.

프론트와 서버와의 일렬의 동작 과정을 보시고자 하신다면 첫 글부터 하나하나 읽어 주시면 감사하겠습니다.

 

⚠️ 참고로 저는 백엔드 쪽 작업을 담당하고 있어 백엔드 위주의 설명을 진행할 예정입니다.

 

프론트의 설명을 보고 싶다면 

👀 프론트 (React 엿보고 오기)
프론트 카카오 소셜 로그인 구현 과정 (그림 포함)

 

[React + SpringBoot] 카카오 소셜 로그인 구현 (+ REST API)

백앤드랑 협업해 프로젝트를 진행하면서 카카오 소셜 로그인 구현을 맡게 되었다! 찾아보니 여러 가지 방식들이 있었지만 다 방식이 달라서 엄청 헤맸던.. 소셜 로그인 부분에서 프론트와 백의

su-vin25.tistory.com

 

 

이번 카카오 소셜 로그인은 보면서 여러 블로그들을 참고했었는데 OAuth2를 이용해서 CustomUserDetails을 만들어 진행하는 것 과, Kakao REST API를 이용하는 두 가지가 있는 것 같았습니다. 

 

OAuth2를 이용하면 편하게 소셜 로그인을 처리할 수 있다고 하지만, OAuth2에 대한 충분한 지식이 없다면 사용하기 어려우니 먼저 Kakao REST API를 이용하여 작업해 보시는 것을 추천드립니다. 

 

아래는 제가 참고한 카카오 로그인 REST API 공식 문서입니다.

 

Kakao Developers

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

developers.kakao.com

 

카카오 소셜 로그인 과정은 초보자에게는 굉장히 어렵다고 느껴집니다. 제가 초보라서...

또한 카카오 로그인 REST API문서는 솔직히 조금 읽기가 불편합니다만... 꾸준히 읽다 보면 읽는 방법을 깨달으실 겁니다 ㅎㅎ 

REST API 문서를 꼭 읽어 보시는 것을 추천드리며, 이제 포스팅을 시작해 보도록 하겠습니다. 


3. 카카오 소셜 로그인 구현

앞서 이전 포스트를 보고 마무리하고 오셨다고 가정한 후에 진행하는 포스트입니다.

💡 목차
1. 카카오 소셜 로그인 사전 준비사항
2. 카카오 소셜 로그인 구조 분석
3. 구현 단계

 

주의사항

먼저 주의사항을 말씀드리겠습니다.

1. 카카오 서버에 인증 토큰을 받아올 수 있는 code는 노출을 최대한 피하자!

카카오 서버에 토큰을 받아올 수 있게 하기 때문에 노출을 하면 카카오 리소스 서버에 접근할 수 있게 만들어 주의를 해야 합니다. 

 

2. 카카오 리소스 서버에 접근할 수 있는 카카오 엑세스 토큰은 노출되지 않도록 하자!

카카오 엑세스 토큰을 가지고 카카오 리소스 서버에 요청을 보내면 유저 정보를 받아올 수 있기 때문에 외부로 노출되면 개인 정보를 탈취당할 위험이 있습니다.

 

3. 프로젝트는 Spring Boot Gradle를 이용합니다.

 

백엔드 서버가 할 일

1. 프론트에서 받아온 인가 코드(code)를 이용해 카카오 엑세스 토큰을 발급받는다.

2. 카카오 엑세스 토큰을 이용해 카카오 리소스 서버에 유저 정보를 조회한다.

3. 카카오 리소스 서버에서 받아온 response 데이터를 DTO 클래스에 넣어둔 후 

4. response 데이터를 가진 DTO를 이용해 로그인, 회원가입의 로직을 만들고 DB에 저장한다.

 

Controller
@GetMapping("/login/oauth2/callback/kakao")
public ResponseEntity<LoginResponseDto> kakaoLogin(HttpServletRequest request) {
    String code = request.getParameter("code");
    String kakaoAccessToken = authService.getKakaoAccessToken(code);
    return authService.kakaoLogin(kakaoAccessToken);
}

 

Service

컨트롤러의 authService.getKakaoAccessToken(code) 처리

@Transactional
public KakaoTokenDto getKakaoAccessToken(String code) {

    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

    // Http Response Body 객체 생성
    MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
    params.add("grant_type", "authorization_code"); //카카오 공식문서 기준 authorization_code 로 고정
    params.add("client_id", KAKAO_CLIENT_ID); // 카카오 Dev 앱 REST API 키
    params.add("redirect_uri", KAKAO_REDIRECT_URI); // 카카오 Dev redirect uri
    params.add("code", code); // 프론트에서 인가 코드 요청시 받은 인가 코드값
    params.add("client_secret", KAKAO_CLIENT_SECRET); // 카카오 Dev 카카오 로그인 Client Secret

    // 헤더와 바디 합치기 위해 Http Entity 객체 생성
    HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest = new HttpEntity<>(params, headers);

    // 카카오로부터 Access token 받아오기
    RestTemplate rt = new RestTemplate();
    ResponseEntity<String> accessTokenResponse = rt.exchange(
            KAKAO_TOKEN_URI, // "https://kauth.kakao.com/oauth/token"
            HttpMethod.POST,
            kakaoTokenRequest,
            String.class
    );

    // JSON Parsing (-> KakaoTokenDto)
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new JavaTimeModule());
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    KakaoTokenDto kakaoTokenDto = null;
    try {
        kakaoTokenDto = objectMapper.readValue(accessTokenResponse.getBody(), KakaoTokenDto.class);
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }

    return kakaoTokenDto;
}

 

컨트롤러의 return authService.kakaoLogin(kakaoAccessToken) 처리

public ResponseEntity<LoginResponseDto> kakaoLogin(String kakaoAccessToken) {
    Account account = getKakaoInfo(kakaoAccessToken);

    LoginResponseDto loginResponseDto = new LoginResponseDto();
    loginResponseDto.setLoginSuccess(true);
    loginResponseDto.setAccount(account);

    Account existOwner = accountRepository.findById(account.getId()).orElse(null);
    try {
        if (existOwner == null) {
            System.out.println("처음 로그인 하는 회원입니다.");
            accountRepository.save(account);
        }
        loginResponseDto.setLoginSuccess(true);

        return ResponseEntity.ok().headers(headers).body(loginResponseDto);

    } catch (Exception e) {
        loginResponseDto.setLoginSuccess(false);
        return ResponseEntity.badRequest().body(loginResponseDto);
    }
}

 

Service의 kakaoLogin.getKakaoInfo(kakaoAccessToken) 처리

public Account getKakaoInfo(String kakaoAccessToken) {
    RestTemplate rt = new RestTemplate();

    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", "Bearer " + kakaoAccessToken);
    headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

    HttpEntity<MultiValueMap<String, String>> accountInfoRequest = new HttpEntity<>(headers);

    // POST 방식으로 API 서버에 요청 후 response 받아옴
    ResponseEntity<String> accountInfoResponse = rt.exchange(
            KAKAO_USER_INFO_URI, // "https://kapi.kakao.com/v2/user/me"
            HttpMethod.POST,
            accountInfoRequest,
            String.class
    );
    
    // JSON Parsing (-> kakaoAccountDto)
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new JavaTimeModule());
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    KakaoAccountDto kakaoAccountDto = null;
    try {
        kakaoAccountDto = objectMapper.readValue(accountInfoResponse.getBody(), KakaoAccountDto.class);
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }

	// 회원가입 처리하기
    Long kakaoId = kakaoAccountDto.getId();
    Account existOwner = accountRepository.findById(kakaoId).orElse(null);
    // 처음 로그인이 아닌 경우
    if (existOwner != null) {
        return Account.builder()
                .id(kakaoAccountDto.getId())
                .email(kakaoAccountDto.getKakao_account().getEmail())
                .kakaoName(kakaoAccountDto.getKakao_account().getProfile().getNickname())
                .build();
    }
    // 처음 로그인 하는 경우
    else {
        return Account.builder()
                .id(kakaoAccountDto.getId())
                .email(kakaoAccountDto.getKakao_account().getEmail())
                .kakaoName(kakaoAccountDto.getKakao_account().getProfile().getNickname())
                .build();
    }
}

 

DTO
@Data
public class LoginResponseDto {
    public boolean loginSuccess;
    public Account account;
}
@Data
public class KakaoTokenDto {
    private String access_token;
    private String token_type;
    private String refresh_token;
    private String id_token;
    private int expires_in;
    private int refresh_token_expires_in;
    private String scope;
}

 

위 과정은 회원가입 후 로그인 처리입니다. 처음 로그인 하면 회원가입을 진행하면서 DB에 저장하고, 처음 로그인이 아니라면 DB에서 정보를 가져와 로그인을 수행합니다.

 

수행하는 과정을 잘 마쳤다면 로그인 및 회원가입을 할 수 있을 겁니다!!

DB나 다른 과정이 궁금하시다면 댓글 달아주시면 감사하겠습니다!

 

💡 목차
1. 카카오 소셜 로그인 사전 준비사항
2. 카카오 소셜 로그인 구조 분석
3. 구현 단계
728x90
반응형
LIST