TIL(Today I Learned)

TIL - React, Spring Boot로 카카오 소셜 로그인 구현 STEP 3

Happy._. 2024. 6. 3. 06:17

STEP 3 사용자 로그인 처리

https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info

React

STEP 2에서 발급받은 토큰을 서버로 보내는 코드를 작성한다.

export const getJwtToken = async (accessToken) => {
  const jwtToken = fetch(`http://localhost:8080/members/signup/kakao?accessToken=${accessToken}`)
  .then(res => res.json())
  .catch((err) => console.log(err));
}

 

리다이렉트 페이지에 다음 코드를 추가한다.

인가 코드를 받아서 authCode가 변경되면 토큰을 요청하고 토큰을 받으면 서버(Spring Boot)측으로 토큰을 전달하고 Jwt 토큰을 받는다.

import { useEffect } from "react";
import { useSearchParams } from "react-router-dom";
import { getAccessToken, getJwtToken } from "../api/kakaoApi";

const KakaoRedirectPage = () => {
  const [searchParams] = useSearchParams();
  const authCode = searchParams.get("code");

  useEffect(() => {
    getAccessToken(authCode).then((data) => getJwtToken(data.access_token));
  }, [authCode]);
  return (
    <div>
      <p>Kakao Login Reditect</p>
      <p>${authCode}</p>
    </div>
  );
};

export default KakaoRedirectPage;

Spring Boot

프런트엔드(React)에서 보낸 토큰을 받기 위한 Controller를 만든다.

@RestController
@RequestMapping("/members")
class MemberController (
    private  val memberService: MemberService
){    
    @GetMapping("/signup/kakao")
    fun signupKakao(accessToken: String): ResponseEntity<MutableMap<String, Any>> {
        return ResponseEntity.status(HttpStatus.CREATED).body(memberService.getMemberInfoFormKakao(accessToken))
    }
}

 

Controller를 통해 받은 토큰으로 카카오 서버에서 사용자 정보를 가져오기 위해 Service를 만든다.

@Service
class MemberService(
    private val memberRepository: MemberRepository
) {
    private val log = LoggerFactory.getLogger(this::class.java)
    
    fun getMemberInfoFormKakao(accessToken: String): MutableMap<String, Any> { 
        // 토큰 값이 null인 경우, 카카오 서버측에서는 우리가 클라이언트이기 때문에 400을 우리에게 던짐
        // 그러면 우리의 클라이언트 쪽에는 500에러가 전달되므로 여기서 예외를 잡아서 프런트 쪽으로 전달해야 함
        if (accessToken == "undefined") {
            throw IllegalArgumentException("Access token cannot be empty")
        }
        
        val getMemberInfoURL = "https://kapi.kakao.com/v2/user/me"

        val restTemplate = RestTemplate()

        val headers = HttpHeaders()
        headers.add("Authorization", "Bearer $accessToken")
        headers.add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")

        val entity = HttpEntity<String>(headers)

        val responseBody = restTemplate.exchange(getMemberInfoURL, HttpMethod.GET, entity, Map::class.java).body
        val kakaoAccount = responseBody?.get("kakao_account") as Map<*, *>

        val email = kakaoAccount["email"]
        val profile = kakaoAccount["profile"] as Map<*, *>
        val nickname = profile["nickname"]
        val profileImageUrl = profile["profile_image_url"]

        log.info("********** email: {}", email)
        log.info("********** nickname: {}", nickname)
        log.info("********** profileImageUrl: {}", profileImageUrl)
        
        // 회원가입 처리
    }
}