OAuth2 - Kakao Login 구현

2024. 1. 30. 23:52
728x90

네이버 로그인도 성공했으니,,,,! 카카오 로그인 도전!

 

1) 애플리케이션 등록. (API 이용신청)

: https://developers.kakao.com/

 

Kakao Developers

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

developers.kakao.com

- 내 애플리케이션 > 애플리케이션 추가하기

 

- 생성 후 애플리케이션에 들어가보면 카카오 로그인 부분이 있다. 

활성화를 해두면 됨 ON! 동의화면 미리 보기도 가능하다.

근데 여기서 팁은 왼쪽 동의항목 메뉴를 눌러보면 닉네임, 프로필 사진, 카카오계정(이메일), 카카오 서비스 내 친구목록 제외하고 나머지는 모두 권한 없음.이라고 나온다... 하지만 지는 이름과,,,, 전화번호가 필요한디유.......

- 그럴때 비즈니스 메뉴에 들어가 비즈 앱으로 전환시켜준다!! 

문서에 확인해보면 테스트 계정 혹은 권한 신청을 위해 비즈니스 정보 인증을 신청해야 한다...! 

그래서 나는 비즈 앱으로 전환 후 테스트 앱을 따로 만들어주었다. 

- 비즈앱 전환

- 일반 > 테스트 앱 생성 후 테스트 앱으로 들어가서 설정을 마무리한다. 

 

- 카카오 로그인 > Redirect URI 설정 : http://localhost:8080/login/oauth2/code/kakao

- 동의항목 체크 (필요한 것들 체크하기)

테스트 앱으로 들어가서 동의항목을 보면 이렇게 나머지도 설정이 가능하게 되었다!!

 

- 로그인 환경 플랫폼 등록.

Web으로 사이트 도메인 http://localhost:8080

 

- Client Id, Client Secret 확인

: Client Id = 앱 키 > REST API 키

 Client Secret = 보안 > 코드 활성화하여 코드 발급받기

 

2) application.yaml 구성

똑같다. 

authorization-uri, token-uri, user-info-uri 다 찾기 겁나 어려움...ㅎㅎㅎ...다 찾아왔다..

 

// application.yaml
spring:
//...
  security:
    oauth2:
      client:
        # OAuth2 서비스 제공자를 사용하는데 필요한 정보
        provider:
          naver:
            # 인증 요청 url
            authorization-uri: https://nid.naver.com/oauth2.0/authorize
            # Access Token요청 url
            token-uri: https://nid.naver.com/oauth2.0/token
            # 사용자 정보 조회 url
            user-info-uri: https://openapi.naver.com/v1/nid/me
            # 응답받은 사용자 정보 중 사용자 이름이 무엇인지 담겨있는 JSON Key
            user-name-attribute: response
          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: kakao_account

        #
        registration:
          naver:
            client-id: <아이디>
            client-secret: <시크릿>
            redirect-uri: http://localhost:8080/login/oauth2/code/naver
            authorization-grant-type: authorization_code
            client-authentication-method: client_secret_post
            client-name: Naver
            # 이 부분은 없어도 되긴 함.
            scope:
              - name
              - nickname
              - email
              - profile_image
              - mobile
          kakao:
            client-id: <REST API 키>
            client-secret: <시크릿 키>
            redirect-uri: http://localhost:8080/login/oauth2/code/kakao
            authorization-grant-type: authorization_code
            client-authentication-method: client_secret_post
            client-name: Kakao
            scope:
              - profile_nickname
              - account_email
              - name
              - phone_number

 

3) 프론트에서 링크 추가

// login-form.html
<div style="margin-top: 16px; text-align: right">
        <a href="/oauth2/authorization/naver">네이버로 로그인</a>
        <a href="/oauth2/authorization/kakao">카카오로 로그인</a>
        <a href="/users/register">회원가입</a>
 </div>

 

4) OAuth2UserServiceImpl 구현

얘도 이미 구현해둔게 있어서 비슷하게 따라적기 수준이었다.

// OAuth2UserServiceImpl class

@Slf4j
@Service
public class OAuth2UserServiceImpl
        extends DefaultOAuth2UserService {
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest)
            throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(userRequest);

        if (oAuth2User == null) {
            throw new OAuth2AuthenticationException(new OAuth2Error("invalid_user"), "OAuth2User is null");
        }
        // 어떤 서비스 제공자를 사용했는지?
        String registrationId = userRequest
                .getClientRegistration()
                .getRegistrationId();
        // OAuth2 제공자로부터 받은 데이터를 원하는 방식으로 다시 정리하기 위한 Map
        Map<String, Object> attributes = new HashMap<>();

        String nameAttributes = "";

        log.info("RegistrationId: {}", registrationId);

        // naver 관련 처리 로직!!
        if (registrationId.equals("naver")) {
            // Naver에서 받아온 정보다.
            attributes.put("provider", "naver");

            // naver가 반환한 JSON에서 response를 회수
            Map<String, Object> responseMap
                    = oAuth2User.getAttribute("response");
            attributes.put("id", responseMap.get("id"));
            attributes.put("email", responseMap.get("email"));
            attributes.put("nickname", responseMap.get("nickname"));
            attributes.put("name", responseMap.get("name"));
            attributes.put("profile_image", responseMap.get("profile_image"));
            attributes.put("phone", responseMap.get("mobile"));
            nameAttributes = "email";
        }
        
        // 카카오 관련 처리 로직
        if (registrationId.equals("kakao")) {
            // kakao에서 받아온 정보
            attributes.put("provider", "kakao");
            attributes.put("id", oAuth2User.getAttribute("id"));
            Map<String, Object> responseMap
                    = oAuth2User.getAttribute("kakao_account");
            Map<String, Object> kakaoProfile = (Map<String, Object>) responseMap.get("profile");
            log.info(responseMap.toString());
            attributes.put("name", responseMap.get("name"));
            attributes.put("phone", responseMap.get("phone_number"));
            attributes.put("email", responseMap.get("email"));
            attributes.put("nickname", kakaoProfile.get("nickname"));
            nameAttributes = "email";
            log.info((String) responseMap.get("email"));
        }

        log.info(attributes.toString());
        return new DefaultOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")),
                attributes,
                nameAttributes
        );
    }
}

 

>> 아니 처음에는 동의항목에도 id가 적혀있고, 문서 보다보면 적혀있었어서 그대로 email도 account_email이렇게 정확하게 적어주고 했는데, 자꾸 NullPointerException이 나는거다!!!! 아놔...머가 문제지 하고 들여다 보고 로그 찍어보니 

RegistrationId 정확하게 들어갔고,,,,,,, 

저장 형태가 완전 달랐다 그래서 내가 get 한 이름이 안불러들여져서 null이 나왔던 것이었다 ㅠㅠ

다시 이름 제대로 맞춰서 넣어주었다!

 

5) 그리고 OAuth2SuccessHandler는 그대로...! 바꿀 필요가 없다.

// OAuth2SuccessHandler

@Slf4j
@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler
        //인증에 성공했을 때 특정 URL로 리다이렉트 하고 싶은 경우 활용 가능한 SuccessHandler
        extends SimpleUrlAuthenticationSuccessHandler {
    // JWT 발급을 위해 JwtTokenUtils
    private final JwtTokenUtils jwtTokenUtils;
    // 사용자 정보 등록을 위해 USerDetailsManager
    private final UserDetailsManager userDetailsManager;

    // 인증 성공하면 이 메서드가 실행됨.
    @Override
    public void onAuthenticationSuccess(
            HttpServletRequest request,
            HttpServletResponse response,
            Authentication authentication
    ) throws IOException, ServletException {
        // OAuth2UserServiceImpl의 반환값이 할당된다.
        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();


        // 넘겨받은 정보를 바탕으로 사용자 정보를 준비
        String email = oAuth2User.getAttribute("email");
        String provider = oAuth2User.getAttribute("provider"); //가입 했었는지 정보
        String username = String.format("{%s}%s", provider, email);
        String providerId = oAuth2User.getAttribute("id").toString();
        String phone = oAuth2User.getAttribute("phone");

        // 처음으로 이 소셜 로그인으로 로그인을 시도했다면
        if (!userDetailsManager.userExists(username)) {
            CustomUserDetails userDetails = CustomUserDetails.builder()
                    .username(username)
                    .email(email)
                    .phone(phone)
                    .password(providerId)
                    .authorities("ROLE_USER")
                    .build();
            userDetailsManager.createUser(userDetails);
        }

        // 데이터베이스에서 사용자 계정 회수
        UserDetails details = userDetailsManager.loadUserByUsername(username);

        // JWT 생성
        String jwt = jwtTokenUtils.generateToken(details);
        // 어디로 리다이렉트 할지 지정
        String targetUrl = String.format("http://localhost:8080/token/validate?token=%s", jwt);
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }
}

6) 마지막 WebSecurityConfig도 똑같이 맞춰주기

// WebSecurityConfig 
@Configuration
@RequiredArgsConstructor
public class WebSecurityConfig {
    private final JwtTokenUtils jwtTokenUtils;
    private final UserDetailsManager manager;
    // 추가
    private final OAuth2UserServiceImpl oAuth2UserService;
    private final OAuth2SuccessHandler oAuth2SuccessHandler;

    //Bean : 메서드의 결과를 Bean 객체로 관리해주는 어노테이션
    @Bean
    public SecurityFilterChain securityFilterChain(
            HttpSecurity http
    ) throws Exception {
        http
                //...중략
                
                // 여기부터
                .oauth2Login(oauth2Login -> oauth2Login
                        .loginPage("/users/login")
                        .userInfoEndpoint(userInfo -> userInfo
                                .userService(oAuth2UserService))
                        .successHandler(oAuth2SuccessHandler)
                )
                // 여기까지 추가
                
                // JWT를 사용하기 때문에 보안 관련 세션 해제
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                // JWT 필터를 권한 필터 앞에 삽입
                .addFilterBefore(new JwtTokenFilter(jwtTokenUtils, manager),
                        AuthorizationFilter.class)
        ;
        return http.build();
    }
}

 

카카오로 로그인~

동의하고 계속하기

짠! 하고 잘 나왔다...오예.................암쏘 해피 

디비에도 잘 저장이 되었다....!!!! 

딱 되는순간 희열이 ㅠㅠㅠㅠㅠㅠ~

728x90

'Programming > Spring, SpringBoot' 카테고리의 다른 글

Relations (M : N 관계)  (1) 2024.02.01
[JPA] MappedSuperclass / JpaAuditing  (0) 2024.01.31
OAuth2 - 소셜 로그인  (0) 2024.01.30
Authorization  (0) 2024.01.30
JWT  (1) 2024.01.29

BELATED ARTICLES

more