[Java]/Spring Boot

[Java] Spring Boot: 카카오 로그인 기능 추가하기

기록자_Recordian 2025. 4. 21. 16:09
728x90
반응형
이전 내용
 

[Java] Spring Boot: 구글 로그인 기능 추가하기

이전 내용 전반적인 순서1. Gradle 또는 Maven 의존성 추가2. 구글 API Console 설정 - 구글 개발자 콘솔 프로젝트 생성 → OAuth 2.0 클라이언트 ID 생성3. application.yaml 설정 (보안 문제로 해당 파일 .gitignore

puppy-foot-it.tistory.com


카카오 로그인 기능 추가하기 소개

 

이전 시간에는 구글 로그인 기능을 추가하였으므로, 이번에는 카카오 로그인 기능도 추가해 본다.

카카오 로그인은 카카오계정으로 다양한 서비스에 로그인할 수 있도록 하는 OAuth 2.0 기반의 소셜 로그인 서비스다.

카카오 로그인 사용자는 별도의 회원가입 없이 카카오톡이나 카카오계정으로 서비스에 로그인할 수 있다. 서비스 제공자는 사용자의 개인정보를 직접 저장하고 관리할 필요 없이, 카카오 API 플랫폼으로부터 검증된 사용자 인증 정보를 안전하게 전달받아 활용할 수 있다.

 

상세 절차 및 내용은 하단의 링크(카카오 디벨로퍼)를 참고하면 더 큰 도움이 될 수 있다.

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

 

Kakao Developers

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

developers.kakao.com

출처: 카카오 디벨로퍼


REST API 방식의 카카오 로그인 구현

 

다음은 카카오 디벨로퍼 사이트에 나와있는 카카오 로그인 과정을 나타낸 시퀀스 다이어그램이다.

(보다 자세한 내용은 https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api 에서 확인)

 

Step1. 인가 코드 받기

  • 서비스 서버가 카카오 인증 서버로 인가 코드 받기를 요청.
  • 카카오 인증 서버가 사용자에게 인증을 요청.
  • 사용자 클라이언트에 유효한 카카오계정 세션이 있거나, 카카오톡 인앱 브라우저에서의 요청인 경우 4단계로 넘어감.
  • 사용자가 카카오계정으로 로그인.
  • 카카오 인증 서버가 사용자에게 동의 화면을 출력하여 인가를 위한 사용자 동의를 요청. ▶ 동의 화면은 서비스 앱(애플리케이션)의 동의항목 설정으로 구성.
  • 사용자가 필수 동의항목과, 이 외의 원하는 동의항목에 동의한 뒤 [동의하고 계속하기] 버튼을 누름.
  • 카카오 인증 서버는 서비스 서버의 Redirect URI로 인가 코드를 전달.

 

Step2. 토큰 받기

  • 서비스 서버가 Redirect URI로 전달받은 인가 코드로 토큰 받기를 요청.
  • 카카오 인증 서버가 토큰을 발급해 서비스 서버에 전달.

Step3. 사용자 로그인 처리 (서비스에서 자체 구현 필요)

  • 서비스 서버가 발급받은 액세스 토큰으로 사용자 정보 가져오기를 요청해 사용자의 회원번호 및 정보를 조회하여 서비스 회원인지 확인.
  • 서비스 회원 정보 확인 결과에 따라 서비스 로그인 또는 회원 가입.
  • 이 외 서비스에서 필요한 로그인 절차를 수행한 후, 카카오 로그인한 사용자의 서비스 로그인 처리를 완료.

카카오 로그인 추가하기

 

먼저 카카오 디벨로퍼에 회원가입을 해야 한다. (카카오 계정으로 바로 가입 가능)

로그인 후, [시작하기] 버튼 클릭

 

[애플리케이션 추가하기] 버튼을 눌러 애플리케이션을 생성한다.

 

그리고나서 앱 설정 > 플랫폼 - Web 플랫폼 등록 클릭

 

사이트 도메인을 등록 후, Redirect URI를 등록한다.

활성화 설정을 한 후, Redirect URI를 설정.

 

 

제품 설정 > 카카오 로그인 > 동의항목 에서는 카카오 로그인 시, 카카오의 어떤 정보를 받아 사용할 것인지를 설정하는 부분으로, 필자의 경우 현재는 로그인 서비스만 구현해볼 것이기 때문에 시간 관계상 패스.

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

 

▶ 동의항목은 필수이므로, 미리 설정하고 넘어가야 한다.

 

 

앱 설정 > 앱 키에 들어가서 네이티브 앱 키와 REST API 키를 복사하여 메모해 둔다.


카카오 클라이언트 ID와 SecretKey 발급받아
properties에 추가하기

 

application.properties에 들어가서 하단의 내용을 추가한다.

(오류가 해결된 최종 버전이므로, 해당 버전으로 추가하면 된다.)

spring.security.oauth2.client.registration.kakao.client-name=kakao-account
spring.security.oauth2.client.registration.kakao.client-id=[카카오 클라이언트 id]
spring.security.oauth2.client.registration.kakao.client-secret=[카카오 클라이언트 SecrectKey]
spring.security.oauth2.client.registration.kakao.scope=profile, account_email
spring.security.oauth2.client.registration.kakao.redirect-uri=http://localhost:9091/login/oauth2/code/kakao
spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code

spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize
spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token
spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me
spring.security.oauth2.client.provider.kakao.user-name-attribute=id
spring.security.oauth2.client.registration.kakao.client-authentication-method=client_secret_post

 

카카오 클라이언트 ID는 앞서 받은 앱 키에서 REST API 키를 사용하면 되고,

SecretKey는 카카오 로그인 > 보안에서 발급 받을 수 있다. 

 


카카오 로그인 버튼 이미지

 

하단의 링크에서 [리소스 다운로드] 클릭

https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api

 

[전체 다운로드(.zip)] 클릭하여 이미지 다운로드

 

다운 받은 파일의 압축을 풀고, 이 중 마음에드는 파일을 하나 골라서

main / resources / static / img 파일에 옮겨놓고 파일명을 kakao로 변경

 

그리고 기존의 oauthLogin.html 파일에서 구글 로그인 구현 밑에 카카오 로그인 버튼 이미지를 넣는 코드를 넣어준다.

<div class = "mb-2">
  <a href="/oauth2/authorization/google">
    <img src="/img/google.png">
  </a>
  <a href="/oauth2/authorization/kakao">
    <img src="/img/kakao.png">
  </a>
</div>

※ 필자의 경우, 카카오 버튼이 구글 버튼 사이즈에 비해 커서 카카오 버튼 사이즈를 조정해 주었다.

 

또한, OAuth2SuccessHandler.java 파일 역시 수정해주었다.

package com.example.spro01.config.oauth;

import com.example.spro01.config.jwt.TokenProvider;
import com.example.spro01.domain.RefreshToken;
import com.example.spro01.domain.User;
import com.example.spro01.repository.RefreshTokenRepository;
import com.example.spro01.service.UserService;
import com.example.spro01.util.CookieUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.time.Duration;
import java.util.Map;

@RequiredArgsConstructor
@Component
public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    // 리프레시 토큰 쿠키의 이름
    public static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken";
    // 리프레시 토큰 유효 기간 (14일)
    public static final Duration REFRESH_TOKEN_DURATION = Duration.ofDays(14);
    // 액세스 토큰의 유효 기간(1일)
    public static final Duration ACCESS_TOKEN_DURATION = Duration.ofDays(1);
    // 인증 성공 후 리다이렉트할 경로
    public static final String REDIRECT_PATH = "/articles";


    // JWT 토큰 생성을 위한 TokenProvider
    private final TokenProvider tokenProvider;
    // 리프레시 토큰을 저장하기 위한 리포지토리
    private final RefreshTokenRepository refreshTokenRepository;
    // OAuth2 인증 요청 관련 쿠키를 관리하는 리포지토리
    private final OAuth2AuthorizationRequestBasedOnCookieRepository authorizationRequestRepository;
    // 사용자 정보를 조회하기 위한 서비스
    private final UserService userService;

    // OAuth2 인증 성공 시 호출
    @Override
    public void onAuthenticationSuccess(
            HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        // 인증된 사용자의 OAuth2User 객체 가져오기 *principal: 인증된 사용자 정보를 가지고 있음
        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();

        // 사용자 이메일 속성을 사용해 사용자 정보 조회
        String email = extractEmail(oAuth2User);

        if (email == null) {
            throw new IllegalArgumentException("Email not found in OAuth2 user attributes");
        }

        User user = userService.findByEmail(email);

        // 리프레시 토큰 생성 -> 저장 -> 쿠키에 저장
        String refreshToken = tokenProvider.generateToken(user, REFRESH_TOKEN_DURATION); // 유효 기간 14일
        saveRefreshToken(user.getId(), refreshToken);
        addRefreshTokenToCookie(request, response, refreshToken);

        // 액세스 토큰 생성 -> 패스에 액세스 토큰 추가
        String accessToken = tokenProvider.generateToken(user, ACCESS_TOKEN_DURATION); // 유효기간 1일
        String targetUrl = getTargetUrl(accessToken);

        // 인증 관련 설정값, 쿠키 제거
        clearAuthenticationAttributes(request, response);

        // 클라이언트를 지정된 URL로 리다이렉트
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }

    private String extractEmail(OAuth2User oAuth2User) {
        // 카카오 계정에서 이메일 추출
        if (oAuth2User.getAttributes().containsKey("kakao_account")) {
            Map<String, Object> kakaoAccount = (Map<String, Object>) oAuth2User.getAttributes().get("kakao_account");
            if (kakaoAccount != null && kakaoAccount.containsKey("email")) {
                return (String) kakaoAccount.get("email"); // 이메일 값을 올바르게 반환
            }
        }

        // 구글 계정에서 이메일 추출
        if (oAuth2User.getAttributes().containsKey("email")) {
            return (String) oAuth2User.getAttributes().get("email");
        }

        // 네이버 계정에서 이메일 추출
        if (oAuth2User.getAttributes().containsKey("response")) {
            Map<String, Object> responseAttributes = (Map<String, Object>) oAuth2User.getAttributes().get("response");
            return (String) responseAttributes.get("email");
        }

        return null; // 이메일을 찾을 수 없는 경우 null 반환
    }

    // 생성된 리프레시 토큰을 전달받아 데이터베이스에 저장
    private void saveRefreshToken(Long userId, String newRefreshToken) {
        RefreshToken refreshToken = refreshTokenRepository.findByUserId(userId)
                // 기존 토큰 있으면 업데이트
                .map(entity -> entity.update(newRefreshToken))
                // 없으면 새 RefreshToken 객체 생성
                .orElse(new RefreshToken(userId, newRefreshToken));

        // 리프레시 토큰 저장
        refreshTokenRepository.save(refreshToken);
    }

    // 생성된 리프레시 토큰을 쿠키에 저장
    private void addRefreshTokenToCookie(
            HttpServletRequest request, HttpServletResponse response, String refreshToken) {
        int cookieMaxAge = (int) REFRESH_TOKEN_DURATION.toSeconds();
        CookieUtil.deleteCookie(request, response, REFRESH_TOKEN_COOKIE_NAME);
        CookieUtil.addCookie(response, REFRESH_TOKEN_COOKIE_NAME, refreshToken, cookieMaxAge);
    }

    // 인증 관련 설정값, 쿠키 제거
    private void clearAuthenticationAttributes(HttpServletRequest request, HttpServletResponse response) {
        super.clearAuthenticationAttributes(request);
        authorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
    }

    // 액세스 토큰을 패스에 추가
    private String getTargetUrl(String token) {
        return UriComponentsBuilder.fromUriString(REDIRECT_PATH)
                .queryParam("token", token)
                .build()
                .toUriString();
    }
}

 

그리고 실행하면,


동의 항목 설정하기

 

필자의 경우, 앞서 동의항목을 설정하지 않았더니 잘못된 요청 에러가 발생했다.

다시 카카오 로그인 - 동의항목에 들어가서 동의항목 설정에서 항목을 확인해 본다.

필자의 경우, 구현하는 앱이 비즈앱이 아니라서 심사를 받아야 하는데, 어려우므로 profile_nickname만 사용하도록 한다.

 

profile_nickname을 '필수 동의'로 바꾸고, 동의 목적을 적고

 

application.properties에서 scope(범위)를 profile_nickname으로 변경 후 서버를 다시 실행하면

spring.security.oauth2.client.registration.kakao.scope=profile_nickname

 

카카오 로그인이 잘 설정된 것을 확인할 수 있다.

 


카카오 동의항목 추가
(개인 개발자 비즈 앱 전환)

 

필자가 구현한 앱에서는 이메일 정보가 추가돼야 하기 때문에,

 

동의 항목에 이메일 등의 항목을 추가해줘야 한다.

그렇기 위해서는 비즈 앱 전환을 해야 하는데, 다행히도 사업자 등록증이 없는 개인 개발자도 비즈 앱 전환이 가능하다.

 

카카오 디벨로퍼에 로그인 - 내 애플리케이션에서 먼저 앱 아이콘을 등록하고

- 비즈니스 - 비즈 앱 정보

로 들어간 다음 [개인 개발자 비즈앱 전환] 클릭하여 사용하고자 하는 비즈 앱 전환 목적을 선택하고 전환을 누르면

 

 

전환이 완료된다.

 

그리고 application.properties에서 scope에도 추가해준다.

spring.security.oauth2.client.registration.kakao.scope=profile_nickname, account_email

 


[참고]

https://imweb.me/faq?mode=view&category=29&category2=47&idx=71729

https://developers.kakao.com/

https://mini-min-dev.tistory.com/121


다음 내용

 

[Java] Spring Boot: 네이버 로그인 구현하기

이전 내용 [Java] Spring Boot: 카카오 로그인 기능 추가하기이전 내용 [Java] Spring Boot: 구글 로그인 기능 추가하기이전 내용 전반적인 순서1. Gradle 또는 Maven 의존성 추가2. 구글 API Console 설정 - 구글 개

puppy-foot-it.tistory.com

728x90
반응형