본문 바로가기
Spring

[Spring] 카카오 로그인 구현 2

by windy7271 2024. 12. 16.
728x90
반응형

 

다음은 스프링에서 구현해보겠습니다.

 

일단 yml 파일을 건드려 줍니다.

security:
  oauth2:
    client:
      registration:
        kakao:
          client-id: ${CLIENT_ID}
          client-secret: ${CLIENT_SECRET}
          client-authentication-method: client_secret_post
          client-name: kakao
          authorization-grant-type: authorization_code
          redirect-uri: http://localhost:8080/login/oauth2/code/kakao
          scope:
            - profile_nickname
            - account_email
      provider:
        kakao:
          authorization-uri: https://kauth.kakao.com/oauth/authorize
          token-uri: https://kauth.kakao.com/oauth/token
          user-info-uri: https://kapi.kakao.com/v2/user/me
          user-name-attribute: id

 

client-id 가 Rest-api 키 이고

client-secret 키가 secret 키 입니다.

 

scope는 카카오 서버에서 받을 데이터 입니다.

 

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

build.gradle에 이것도 추가를 해 줍니다.

 

.oauth2Login(oauth2 -> oauth2
        .successHandler(successHandler) // 커스텀 성공 핸들러
        .userInfoEndpoint(userInfo -> userInfo
                .userService(oauth2UserService) // 사용자 정보 서비스 등록
        )
);

 

security config 에 이것을 추가 해줘야합니다.

 

Oauth2UserService

@Service
@RequiredArgsConstructor
@Slf4j
public class Oauth2UserService extends DefaultOAuth2UserService {

    private final JwtTokenProvider jwtTokenProvider;
    private final MemberRepository memberRepository;
    private final TypeChange typeChange;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        log.info("Oauth2UserService.loadUser called");

        OAuth2User oAuth2User = super.loadUser(userRequest);

        // 클라이언트 ID로 OAuth 공급자 확인 (카카오인지 확인)
        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        if (!"kakao".equals(registrationId)) {
            throw new OAuth2AuthenticationException("Now Only Kakao is supported");
        }

        // 카카오 사용자 정보 처리
        Map<String, Object> attributes = oAuth2User.getAttributes();
        log.info("Kakao user attributes: {}", attributes);

        // 카카오 계정 정보 추출
        Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
        String email = (String) kakaoAccount.get("email");
        String nickname = (String) ((Map<String, Object>) attributes.get("properties")).get("nickname");

        // 기존 사용자 체크
        Member member = memberRepository.findByEmail(email).orElse(null);

        // 기존 사용자가 없으면 새로 생성
        if (member == null) {
            member = typeChange.memberCreateDtoToMember(new LoginSignUpReqDto(email, null, nickname), null);
            memberRepository.save(member);
        }

        // 인증 객체 반환
        return new DefaultOAuth2User(
                List.of(new SimpleGrantedAuthority("ROLE_USER")),
                attributes,
                "id"
        );
    }
}

 

이 코드는 Spring Security에서 OAuth 2.0 로그인 처리 중 사용자 정보를 로드하고 커스터마이징하는 OAuth2UserService 구현체입니다. 

주요 역할은 카카오 인증 성공 후 사용자 정보를 처리하고, DB와 동기화하거나 신규 사용자를 생성한 뒤, 인증된 사용자 객체를 반환하는 것입니다. 

 

 

OAuth2User oAuth2User = super.loadUser(userRequest);

기본 구현체를 사용해 OAuth 2.0 인증 서버(카카오)에서 사용자 정보를 가져옵니다.

 

SuccessHandler

package growup.spring.springserver.global.common;

import growup.spring.springserver.global.config.JwtTokenProvider;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.Comment;
import org.springframework.beans.factory.annotation.Autowired;
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 java.io.IOException;
import java.util.Map;
import java.util.Optional;

@Slf4j
@Component
@RequiredArgsConstructor
public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private final JwtTokenProvider jwtTokenProvider;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        // OAuth2User 정보 가져오기
        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();

        // 사용자 정보 추출 (카카오 사용자 정보)
        Optional<Map<String, Object>> kakaoAccountOpt = safeCastToMap(oAuth2User.getAttributes().get("kakao_account"));

        String email = kakaoAccountOpt
                .map(kakaoAccount -> (String) kakaoAccount.get("email"))
                .orElseThrow(() -> new IllegalArgumentException("Kakao account data is missing"));

        Optional<Map<String, Object>> propertiesOpt = safeCastToMap(oAuth2User.getAttributes().get("properties"));

        String nickname = propertiesOpt
                .map(properties -> (String) properties.get("nickname"))
                .orElse("DefaultNickname"); // 대체값을 제공하거나 예외를 던질 수 있음

        String role = "ROLE_USER"; // 기본 사용자 역할

        // JWT 토큰 생성
        String memberSpecification = String.format("%s:%s:%s", email, nickname, role);
        String accessToken = jwtTokenProvider.createAccessToken(memberSpecification);
        log.info("accessToken :{}",accessToken);

        // HTTP 응답 헤더에 토큰 추가
        response.addHeader("Authorization", "Bearer " + accessToken);

        String targetUrl = String.format("http://localhost:3000/oauth/kakao/callback?token=%s", accessToken);
        log.info("Redirecting to: {}", targetUrl);
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }

    @SuppressWarnings("unchecked")
    private Optional<Map<String, Object>> safeCastToMap(Object obj) {
        if (obj instanceof Map) {
            return Optional.of((Map<String, Object>) obj);
        }
        return Optional.empty();
    }
}

 

 

이 코드는 OAuth2 인증이 성공했을 때 실행되는 OAuth2AuthenticationSuccessHandler입니다. Spring Security가 OAuth2 로그인 과정을 처리하고 사용자가 성공적으로 인증되면, 이 핸들러가 호출되어 JWT 토큰을 생성하고, 클라이언트(프론트엔드)로 리디렉션합니다.

 

그러면 프론트에드에서

import React, { useEffect } from "react";

function KakaoLoginCallback() {
    useEffect(() => {
        const queryParams = new URLSearchParams(window.location.search);
        const token = queryParams.get("token");

        if (token) {
            localStorage.clear();
            localStorage.setItem("accessToken", token);

            // 저장된 토큰 확인
            console.log("Token saved in localStorage:", localStorage.getItem("accessToken"));

            window.location.replace("/main");
        } else {
            console.error("Token is missing ");
        }
    }, []);

    return null;
}

export default KakaoLoginCallback;

 

 

프론트에서 토큰을 받아 브라우저에 저장해주면 됩니다.

 

 

 

반응형

댓글