Java/환경구성

[Java] OAuth 2.0 카카오 개발자(Kakao Developers) 동의항목 권한 없음 해결 방법 : 테스트 앱 생성

adjh54 2024. 11. 9. 23:26
반응형
해당 글에서는 카카오 개발자(Kakao Developers) 동의항목 중 권한 없음에 대한 해결 방법으로 테스트 앱 생성 방법을 이용하여 권한을 받는 방법에 대해 알아봅니다.


1) 문제점 확인


💡 문제점 확인

- 카카오 로그인을 수행하였을 때, 추가적인 정보를 받고자 할 때 이에 대한 “권한 없음”으로 선택이 되지 않는 문제가 있습니다.
- 이러한 권한을 얻기 위해서는 ‘비즈니스 인증’을 완료해야합니다.

 

 

1. 비즈니스 인증


💡 비즈니스 인증

- 앱 설정 > 앱 권한 신청 경로 탭으로 이동하면 카카오 로그인을 수행했을 때, 추가적인 정보를 받을 수 있도록 비즈니스에 대한 정보를 위해서는 심사가 필요합니다.

 

💡비즈니스 정보 > 신청 버튼 클릭

- 아래와 같이 비즈니스 신청을 하면 필요로 하는 ‘개인정보 동의항목’이 포함되어 있습니다.

 

 💡 다음 버튼을 누릅니다

- 아래와 같이 서비스 정보에 대한 URL에 필요하기에, “테스트”로 사용하는 경우에는 별도의 URL이 존재하지 않기에 적을 수 있는 URL이 없습니다.

 

 

2) 해결 방법 : 테스트 앱 생성


 

💡 해결 방법 : 테스트 앱 생성

- 아래의 공식 사이트를 기반으로 테스트 앱을 생성하여서 이를 해결합니다.
 

Kakao Developers

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

developers.kakao.com

 

1. 내 애플리케이션 > 일반 > 테스트 앱 정보로 이동하여 '테스트앱 생성' 버튼을 누릅니다.


 

 

2. 아래와 같은 팝업이 출력되고 이름을 설정하고 ‘저장’ 버튼을 선택해 줍니다.


 

3. 전체 애플리케이션 페이지로 이동합니다.


💡 전체 애플리케이션 페이지로 이동합니다.

- 아래와 같이 -TEST라는 형태의 애플리케이션이 추가되었습니다. 해당 앱을 선택합니다.

 

 

4. 앱 설정 > 대시보드 페이지에서 카카오 로그인 ‘설정하기’ 버튼을 누릅니다.


 

 

5. 제품 설정 > 카카오 로그인 > 활성화 설정 상태를 'ON'으로 변경합니다.


 

 

6. 제품 설정 > 동의항목 페이지로 이동하면 동의 항목의 권한 없음 문제가 해결되었습니다.


 

3) 실제 테스트


 

💡 실제 테스트

- 해당 계정의 정보와 연결하여서 실제 OAuth 2.0 테스트로 값이 전달되는지 확인해 봅니다.
- 아래와 같이 주요 개인정보에 대해 ‘필수 동의’로 적용하였습니다.

 

💡 [참고] 아래의 설정 방법을 기반으로 설정하였습니다.
 

[Java] Spring Boot 환경에서 OAuth 2.0 설정 -1: 카카오 로그인 설정 및 구성요소 확인

해당 글에서는 Spring Boot 환경에서 OAuth 2.0 설정을 위해 카카오 로그인 설정 및 구성요소에 대해 알아봅니다.1) 최초 카카오 개발자 설정1. Kakao Developers 사이트에 접속하여 로그인하여 ‘애플리케

adjh54.tistory.com

 

2. 권한 요청 페이지 출력


💡 권한 요청 페이지 출력

- 아래와 같이 -TEST 애플리케이션을 바라보게 구성이 되었고, 필수 동의 사항에 위에 설정한 내용을 받아오도록 설정이 되어 있습니다.

 

3. 권한 불러오기 설정


💡 권한 불러오기 설정

- 아래와 같은 Scope의 PropertyKeys 내에 값을 받습니다.
속성 설명
kakao_account.profile 카카오계정의 프로필 소유 여부실시간 닉네임과 프로필 사진 URL
kakao_account.name 카카오계정의 이름 소유 여부, 이름 값
kakao_account.email 카카오계정의 이메일 소유 여부이메일 값, 이메일 인증 여부, 이메일 유효 여부
kakao_account.age_range 카카오계정의 연령대 소유 여부, 연령대 값
kakao_account.birthday 카카오계정의 생일 소유 여부, 생일 값
kakao_account.gender 카카오계정의 성별 소유 여부, 성별 값

 

 

Kakao Developers

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

developers.kakao.com

 

3.1. application.oauth2.yml 파일


💡 application.oauth2.yml 파일

- scope 내에서 불러올 내용을 모두 추가하였습니다.

spring:
  security:
    oauth2:
      client:
        # OAuth2 인증 제공자에 대한 설정 정보를 포함합니다.
        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
        # 클라이언트 애플리케이션(Spring Boot)에 대한 설정을 포함합니다.
        registration:
          kakao:
            client-id: xx
            client-secret: xx
            redirect-uri: 
            authorization-grant-type: authorization_code
            client-authentication-method: POST
            client-name: kakao
            scope:
              - kakao_account.profile
              - kakao_account.name
              - kakao_account.email
              - kakao_account.age_range
              - kakao_account.birthday
              - kakao_account.gender

 

 

3.2. 반환 객체 구성 : OAuth2KakaoUserInfoDto


💡 반환 객체 구성 : OAuth2KakaoUserInfoDto

- 기존의 Biz가 아닌 상태에서 받았던 내용 외에 모든 정보를 불러오도록 하였습니다.
package com.adjh.springbootoauth2.dto.oauth2;

import lombok.*;

/**
 * Please explain the class!!
 *
 * @author : jonghoon
 * @fileName : OAuth2KakaoUserInfoDto
 * @since : 11/8/24
 */
@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OAuth2KakaoUserInfoDto {
    private String id;

    private int statusCode;                 // 상태 코드

    private String email;                   // 이메일

    private String nickname;                // 닉네임

    private String profileImageUrl;         // 프로필 이미지 URL

    private String thumbnailImageUrl;       // 썸네일 이미지 URL

    private String name;                    // [Biz] 사용자 이름

    private String ageRange;                // [Biz] 사용자 나이 범위

    private String birthday;                // [Biz] 사용자 생일

    private String gender;                  // [Biz] 사용자 성별

    @Builder
    public OAuth2KakaoUserInfoDto(String id, int statusCode, String email, String nickname, String profileImageUrl, String thumbnailImageUrl, String name, String ageRange, String birthday, String gender) {
        this.id = id;
        this.statusCode = statusCode;
        this.email = email;
        this.nickname = nickname;
        this.profileImageUrl = profileImageUrl;
        this.thumbnailImageUrl = thumbnailImageUrl;
        this.name = name;
        this.ageRange = ageRange;
        this.birthday = birthday;
        this.gender = gender;
    }
}

 

 

3.3. 비즈니스 로직에서 로드


💡 비즈니스 로직에서 로드

- 해당 메서드 내에서 사용자 정보를 요청하는 파라미터로 “property_keys” 키에 값으로 oAuthRegistration.kakao().scope()에서 불러온 내용을 기반으로 사용자 정보를 요청하고 있습니다.
@Slf4j
@Service("OAuth2ServiceImpl")
public class OAuth2ServiceImpl implements OAuth2Service {
	/**
     * 카카오 로그인 사용자 정보를 가져옵니다.
     *
     * @param accessToken
     * @return
     * @refrence <https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info>
     */
    private OAuth2KakaoUserInfoDto getKakaoUserInfo(String accessToken) {
        log.debug("[+] getKakaoUserInfo을 수행합니다 :: {}", accessToken);

        ResponseEntity<map<string, object="">> responseUserInfo = null;
        OAuth2KakaoUserInfoDto resultDto = null;

        // [STEP1] 필수 요청 Header 값 구성 : ContentType, Authorization
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
        headers.add("Authorization", "Bearer " + accessToken);        // accessToken 추가

        // [STEP2] 요청 파라미터 구성 : 원하는 사용자 정보
        MultiValueMap<string, object=""> userInfoParam = new LinkedMultiValueMap<>();
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            userInfoParam.add("property_keys", objectMapper.writeValueAsString(oAuthRegistration.kakao().scope()));   // 불러올 데이터 조회 (리스트 to 문자열 변환)
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        HttpEntity<multivaluemap<string, object="">> userInfoReq = new HttpEntity<>(userInfoParam, headers);

        // [STEP3] 요청 Header, 파라미터를 포함하여 사용자 정보 조회 URL로 요청을 수행합니다.
        try {
            responseUserInfo = restTemplateConfig
                    .restTemplate()
                    .exchange(oAuthProvider.kakao().userInfoUri(), HttpMethod.POST, userInfoReq, new ParameterizedTypeReference<>() {
                    });
            log.debug("결과 값 :: {}", responseUserInfo);

        } catch (Exception e) {
            log.error("[-] 사용자 정보 요청 중에 오류가 발생하였습니다.{}", e.getMessage());
        }

        // [STEP4] 사용자 정보가 존재한다면 값을 불러와서 OAuth2KakaoUserInfoDto 객체로 구성하여 반환합니다.
        if (responseUserInfo != null && responseUserInfo.getBody() != null && responseUserInfo.getStatusCode().is2xxSuccessful()) {
            Map<string, object=""> body = responseUserInfo.getBody();

            if (body != null) {
                Map<string, object=""> kakaoAccount = this.cvtObjectToMap(body.get("kakao_account"));
                Map<string, object=""> profile = this.cvtObjectToMap(this.cvtObjectToMap(body.get("kakao_account")).get("profile"));
                resultDto = OAuth2KakaoUserInfoDto.builder()
                        .id(body.get("id").toString())                                      // 사용자 아이디 번호
                        .statusCode(responseUserInfo.getStatusCode().value())               // 상태 코드
                        .email(kakaoAccount.get("email").toString())                        // 이메일
                        .profileImageUrl(profile.get("profile_image_url").toString())
                        .thumbnailImageUrl(profile.get("thumbnail_image_url").toString())
                        .nickname(profile.get("nickname").toString())
                        .name(profile.get("name").toString())
                        .ageRange(profile.get("age_range").toString())
                        .birthday(profile.get("birthday").toString())
                        .gender(profile.get("gender").toString())
                        .build();

                log.debug("최종 구성 결과 :: {}", resultDto);

            }
        }
        return resultDto;
    }
}

 

💡 아래와 같은 데이터 구조를 불러옴을 확인하였습니다.
<200 OK OK,
{
    id=3785024526, 
    connected_at=2024-11-09T07:44:14Z, 
    kakao_account={
        profile_nickname_needs_agreement=false, 
        profile_image_needs_agreement=false, 
            profile={
                nickname=이종훈, 
                thumbnail_image_url=http://k.kakaocdn.net/dn/JIQ7w/xxxxxx/j8e0bYvenB7AEIknDSZHmk/img_110x110.jpg, 
                profile_image_url=http://k.kakaocdn.net/dn/JIQ7w/xxxxx/j8e0bYvenB7AEIknDSZHmk/img_640x640.jpg, 
                is_default_image=false, 
                is_default_nickname=false
            }, 
            name_needs_agreement=false, 
            name=이종훈, 
            has_email=true, 
            email_needs_agreement=false, 
            is_email_valid=true, 
            is_email_verified=true, 
            email=adjh54@kakao.com, 
            has_age_range=true, 
            age_range_needs_agreement=false, 
            age_range=30~39, 
            has_birthday=true, 
            birthday_needs_agreement=false, 
            birthday=0501, 
            birthday_type=SOLAR, 
            has_gender=true, 
            gender_needs_agreement=false, 
            gender=male
            }
        }
}

 

 

💡 [참고] 아래와 같이 호출을 하였습니다.
Map<String, Object> kakaoAccount = this.cvtObjectToMap(body.get("kakao_account"));
Map<String, Object> profile = this.cvtObjectToMap(kakaoAccount.get("profile"));

System.out.println("Profile :: " + profile);
System.out.println("ID: " + body.get("id"));
System.out.println("Status Code: " + responseUserInfo.getStatusCode().value());
System.out.println("Profile Image URL: " + profile.get("profile_image_url"));
System.out.println("Thumbnail Image URL: " + profile.get("thumbnail_image_url"));
System.out.println("Email: " + kakaoAccount.get("email"));
System.out.println("Nickname: " + profile.get("nickname"));
System.out.println("Name: " + kakaoAccount.get("name"));
System.out.println("Age Range: " + kakaoAccount.get("age_range"));
System.out.println("Birthday: " + kakaoAccount.get("birthday"));
System.out.println("Gender: " + kakaoAccount.get("gender"));

 

 

4. 최종 결과 확인


💡 최종 결과 확인

- 아래와 같은 추가적인 결과를 반환받음을 확인하였습니다.

 

 

 

 

 

오늘도 감사합니다 😀

 

 

 

반응형