[OAuth 2.0&Session] 카카오 API를 활용한 로그인 구현

4 분 소요

OAuth (Open Authorization)란?

구글, 페이스북, 트위터와 같은 다양한 플랫폼의 특정한 사용자 데이터에 접근하기 위해 제3자 클라이언트(우리의 서비스)가 사용자의 접근 권한을 위임(Delegated Authorization)받을 수 있는 표준 프로토콜이다.

쉽게 말하자면, 우리의 서비스가 우리 서비스를 이용하는 유저의 타사 플랫폼 정보에 접근하기 위해서 권한을 타사 플랫폼으로부터 위임 받는 것 이다.

출처: https://hudi.blog/oauth-2.0/

카카오 API 인증 흐름 (로그인)

2cfa3d9d-897d-44dd-b25a-edd437eabb80

  1. 사용자 인증 요청: 사용자가 카카오 로그인 버튼을 클릭하면, 카카오 인증 페이지로 리디렉션됩니다.
  2. 사용자 동의: 사용자가 카카오 인증 페이지에서 권한 요청에 동의합니다.
  3. Authorization Code 발급: 카카오가 인가 코드를 서비스에 전달합니다.
  4. Access Token 발급 요청: 서비스는 인가 코드를 사용하여 카카오에 액세스 토큰을 요청합니다.
  5. Access Token 발급: 카카오가 액세스 토큰을 서비스에 전달합니다.
  6. 사용자 정보 요청: 서비스는 액세스 토큰을 사용하여 카카오로부터 사용자 정보를 가져옵니다.
  7. 로그인 완료: 서비스가 받은 사용자 정보를 기반으로 로그인 절차를 완료합니다.

상세 설계

프론트엔드 (React):

  • 카카오 로그인 버튼: 사용자가 클릭하면 카카오 인증 페이지로 이동하도록 설정.
  • 콜백 페이지: 인증 완료 후 리디렉션될 페이지로, 인가 코드를 처리.
  • 프로필 페이지: 세션에서 받아온 유저 정보를 화면에 보여줌.

백엔드 (Java Spring):

  • 인가 코드 처리 엔드포인트: 콜백 페이지에서 받은 인가 코드를 처리하고, 액세스 토큰을 요청.
  • 액세스 토큰 처리: 액세스 토큰을 받아 사용자 정보를 요청.
  • 사용자 정보 저장: 세션을 생성.
    • + 사용자 정보를 데이터베이스에 저장

개발하기

1. 카카오 디벨로퍼스에서 카카오 API 사용을 위한 Key 발급

Kakao Developers: https://developers.kakao.com/console/app

참고 문서 : Kakao i 기술문서

1) 애플리케이션 추가하기

Untitled

2) 두 가지 Key 발급

  • REST API KeyClient ID 로 사용
  • Secret KeyClient Secret 로 사용
    • 보안을 강화하기 위해 선택적으로 Client Secret 값을 사용

⇒ 탈취 방지를 위해 환경 변수로 노출을 방지한다.

3) Redirect URL 설정

Untitled 1

  • 카카오 로그인 탭 → 활성화 → Redirect URL 등록
  • 사용자가 카카오 로그인을 성공하면, 카카오 측에서 Client를 Redirect 해줄 URI를 설정

!에러 주의!

Untitled 2

💡 React 연동시 localhost:3000 으로 redirect url을 설정

  • React 애플리케이션은 프론트엔드 클라이언트이며, 보통 개발 중에 http://localhost:3000에서 실행됩니다.
  • Spring Boot 애플리케이션은 백엔드 서버이며, 보통 개발 중에 http://localhost:8080에서 실행됩니다.
  • React 애플리케이션에서 카카오 로그인 프로세스를 처리
    • URL에서 인증 코드를 추출하여 백엔드 서버(http://localhost:8080/callback)로 전송 (POST)
  • API 요청을 받은 백엔드 서버(http://localhost:8080/callback)는 토큰 교환 및 사용자 정보를 처리합니다.
    • 카카오 API를 호출하여 액세스 토큰 및 사용자 정보를 받아와 세션에 저장하거나 데이터베이스에 저장

추가

  • 동의항목 설정

    카카오 로그인 과정에서 사용자로부터 받을 정보를 설정

Untitled 3

2. 로그인 페이지 구현

http://localhost:3000 으로 접속시 React 기본 화면으로 이동

Untitled 3

  • 로그인 버튼을 간단히 구현하고 버튼 클릭시 로그인 요청을 보낼 URL을 연결
  • URL 형식

      private final static String KAUTH_TOKEN_URL_HOST = "https://kauth.kakao.com";
      public String getKakaoLogin() {
          return KAUTH_TOKEN_URL_HOST + "/oauth/authorize"
                  + "?client_id=" + KAKAO_CLIENT_ID
                  + "&redirect_uri=" + KAKAO_REDIRECT_URL
                  + "&response_type=code";
      }
    
  • 백에서 /login 엔드 포인트로 GetMapping
  • 프론트에서 axios를 통해 URL을 get

      axios.get('http://localhost:8080/login')
        .then(response => {
            setLoginUrl(response.data.location);
        })
        .catch(error => {
            console.error('There was an error fetching the login URL!', error);
        });
    
  • 프론트엔드에서 Axios 또는 Fetch API를 사용하여 데이터를 가져올 때 JSON 형식의 응답을 처리하는 것이 일반적
  • 확장성, 유연성 : JSON 형식을 사용하면 추가적인 정보를 쉽게 포함할 수 있습니다. 예를 들어, 로그인 URL 외에도 메시지, 상태 코드 등 다른 데이터를 함께 전송할 수 있습니다.

3. Callback 페이지 구현

엑세스 토큰 요청

  • HTTP 요청 (POST)

https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id={KAKAO_CLIENT_ID}&client_secret={KAKAO_CLIENT_SECRET}&redirect_uri={KAKAO_REDIRECT_URL}&code={code}

Untitled 4

Postman으로 사전 확인

1. WebClient를 사용한 POST 요청

KakaoTokenResponseDto kakaoTokenResponseDto = WebClient.create(KAUTH_TOKEN_URL_HOST).post()
                .uri(uriBuilder -> uriBuilder
                        .scheme("https")
                        .path("/oauth/token")
  • Kakao 인증 서버의 토큰 발급 URL에 POST 요청을 보냅니다.

2. HTTP 헤더 및 요청 본문 설정

.queryParam("grant_type", "authorization_code")
.queryParam("client_id", KAKAO_CLIENT_ID)
.queryParam("client_secret", KAKAO_CLIENT_SECRET)
.queryParam("redirect_uri", KAKAO_REDIRECT_URL)
.queryParam("code", code)
.build(true))
.header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())
  • 요청의 콘텐츠 타입을 application/x-www-form-urlencoded로 설정하고, 요청 본문에 인증 코드 및 기타 필요한 정보를 포함합니다.

3. 응답 처리 및 예외 처리

.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, clientResponse -> {
    log.error("Client error while requesting token: {}", clientResponse.statusCode());
    return Mono.error(new RuntimeException("Invalid authorization code or parameters."));
})
.onStatus(HttpStatusCode::is5xxServerError, clientResponse -> {
    log.error("Server error while requesting token: {}", clientResponse.statusCode());
    return Mono.error(new RuntimeException("Kakao server error. Please try again later."));
})
.bodyToMono(KakaoTokenResponseDto.class)
.block();
  • 서버의 응답 상태 코드를 확인하고, 4xx 오류가 발생하면 클라이언트 측 오류로 간주하여 에러 로그를 남기고 사용자 정의 예외를 발생시킵니다.
  • 5xx 오류가 발생하면 서버 측 문제로 간주하여 다른 오류 메시지를 로그에 남깁니다.
  • 최종적으로 응답 본문을 KakaoTokenResponseDto 객체로 변환합니다.

4. 응답 처리

if(kakaoTokenResponseDto != null) {
            log.info(" [Kakao Service] Access Token ------> {}", kakaoTokenResponseDto.getAccessToken());
            log.info(" [Kakao Service] Refresh Token ------> {}", kakaoTokenResponseDto.getRefreshToken());
            log.info(" [Kakao Service] Id Token ------> {}", kakaoTokenResponseDto.getIdToken());
            log.info(" [Kakao Service] Scope ------> {}", kakaoTokenResponseDto.getScope());
        } else {
            throw new RuntimeException("Failed to obtain access token");
        }
  • 액세스 토큰 수신 여부를 확인하고, 성공적으로 수신한 경우 각 토큰 및 정보(리프레시 토큰, ID 토큰, 스코프)를 로그에 남깁니다.
  • 만약 응답이 null이라면, 액세스 토큰을 얻는 데 실패했다는 예외를 발생시킵니다.

5. 액세스 토큰 반환

return kakaoTokenResponseDto.getAccessToken();
  • 성공적으로 수신한 액세스 토큰을 반환합니다.

사용자 정보 요청

  • HTTP 요청 (GET)

https://kapi.kakao.com/v2/user/me?access_token={access_token}

Untitled 5

세션에 사용자 정보 저장 (HttpSession)

session.setAttribute("userId", userInfo.getId()); //key, value
session.setAttribute("userInfo", userInfo);
  • 인가 코드의 경우 단일 사용, 즉 단 한 번만 사용할 수 있다. 그래서 새로고침을 할 경우 유저 정보가 사라지는 이슈가 있다.
    • 단일 사용: 인증 코드는 한 번만 사용할 수 있습니다. 이미 액세스 토큰을 요청하는 데 사용된 경우, 해당 코드는 더 이상 유효하지 않습니다. 새로운 인증 코드를 사용해야 합니다.
  • 그렇기 때문에 로그인 유지하는 방법을 적용해야 한다.
    • 로컬 스토리지 or 세션

세션 → 서버에서 저장 (서버 구축x, DBx)

  • 별도로 저장해주지 않는다면 저장하지 않는다.

쿠키 → 클라이언트에 저장

Untitled 6

  • 도메인별로 쿠키 저장
  • /user 에서 쿠키를 받는다하면 아래 저장

4. Profile 페이지 구현

Java Spring

KakaoUserInfoResponseDto userInfo = (KakaoUserInfoResponseDto) session.getAttribute("userInfo");
  • session에 저장한 user 정보를 key 값을 이용해서 가져오기

React

axios.get('http://localhost:8080/profile', { withCredentials: true })
  .then(response => {
      setUserInfo(response.data);
      console.log("프로필 정보 가져오기 성공:", response.data);
  })
  .catch(error => {
      console.error('프로필 정보 가져오기 중 오류 발생!', error);
  });
  • 프로필을 get 방식으로 프론트에서 받아오기

참고 블로그

https://ddonghyeo.tistory.com/16

https://shxrecord.tistory.com/290


코드 및 시연 영상

2024-07-177 00 29-ezgif com-video-to-gif-converter

백엔드 : https://github.com/KimGyeongLock/kakao_login_backend

프론트엔드 : https://github.com/KimGyeongLock/kakao_login_frontend

카테고리:

업데이트: