Dot Programming/Spring

[Spring] 스프링으로 OAuth2 로그인 구현하기3 - 카카오

루지 2021. 6. 25. 01:55

    1. 카카오 API 서비스 등록

    1) 카카오 Devlopes 사이트로 이동한다. 링크

     

    Kakao Developers

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

    developers.kakao.com

     

     

    2) 내 애플리케이션 > 애플리케이션 추가하기 > 앱 이름 입력 (test-project) > 사업자명 입력 (test) > 저장

    애플리케이션 추가

     

    3)  REST API 키 GET

    REST API키

     

     

    4) 앱 설정 > 플랫폼 > 웹 플랫폼 등록 > 웹 URL 입력 (http://localhost:8080) > 저장

    플랫폼 등록

     

    5) 제품 설정 > 카카오 로그인 > 활성화 설정 ON >Redirect URI (http://localhost:8080/oauth2/code/kakao) 등록

    활성화 설정

     

     

    6) 동의 항목 > 프로필 정보(닉네임/ 프로필 사진) 설정 > 필수 동의 체크 > 동의 목적 입력 (개발 테스트) > 저장

    카카오 닉네임(profile_nickname) 설정
    카카오 프로필 사진(profile_image) 설정

     

    7) 카카오 계정(이메일) 설정 > 선택 동의 체크 > 동의 목적 입력 (개발 테스트) > 저장

    이메일을 필수 입력받으려면 검수가 필요하다. 이부분은 서비스를 배포할 때 고려해줘야할 것 같다.

     

    카카오 계정(이메일) 설정

     

     

    8) 기본값 profile, account_email 정보만 설정

     

     

    9) 카카오는 Secret키는 선택사항이니 패스 & Logout URL은 Spring Security가 기본으로 제공하므로 일단 연동이 목적이기 때문에 패스한다.

     

     

     

    10) application-oauth.yml에 로그인 해당 정보 입력

    스프링은 네이버,카카오는 지원이 안되기 때문에 Provider를 직접 입력해줘야 한다. 클라이언트 ID는 REST API키를 입력해주면 된다. 왜 이 값들을 넣어줘야하는지 다음 카카오 로그인 API 연동과정을 보며 살펴보자. 

    (스프링이 지원하는 서비스 : 구글,깃헙,페이스북,옥타)

    spring:
      security:
        oauth2:
          client:
            registration:
              kakao:
                client-id: 클라이언트 ID
                redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}" # http://localhost:8080/login/oauth2/code/kakao
                client-authentication-method: POST
                authorization-grant-type: authorization_code
                scope: profile_nickname, profile_image, account_email
                client-name: Kakao
            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

     

     

     

    2. 카카오 로그인 API 연동 과정

    application-oauth.yml에 적은 데이터들이 어떤 역할을 하는지 API를 참고하여 알아보자.

     

    1) redirect_uri : 인가 코드 받기 #

    카카오 로그인을 시작하는 단계로써, 카카오 로그인 동의 화면을 호출하고, 사용자 동의를 거쳐 인가 코드 발급을 요청하는 API이다.

     

     

    요청 URL

    GET /oauth/authorize?client_id={REST_API_KEY}&redirect_uri={REDIRECT_URI}&response_type=code
    HTTP/1.1
    Host : kauth.kakao.com

     

    응답

    사용자가 [동의하고 계속하기] 선택 후 로그인 요청이 승인되면 토큰 받기 요청 필요한 인가코드를 전송해준다.

     

    HTTP/1.1 302 Found
    Content-Length: 0
    Location: {REDIRECT_URI}?code={AUTHORIZE_CODE}

     

     

    2) 토큰 받기 #

    인가 코드를 받은 뒤, 인가 코드로 액세스 토큰과 리프레시 토큰을 발급받는 API이다. 인가 코드 받기만으로는 카카오 로그인이 완료되지 않으며, 토큰 받기까지 마쳐야 카카오 로그인을 정상적으로 완료할 수 있다.

     

    client-authentication-method: POST

    다른 서비스와는 달리 카카오는 필수 파라미터 값들을 담아 POST로만 요청이 가능하다. 그래서 yml에 client-authentication-method: POST을 명시해줘야한다.

     

    요청 성공시, 응답은 JSON객체로 Redirect URL에 전달된다. 토큰 받기를 통해 발급받은 액세스 토큰은 사용자 정보 가져오기와 같은 카카오 로그인이 필요한 API를 호출할 때 사용한다.

     

    요청 URL

    POST /oauth/token HTTP/1.1
    Host: kauth.kakao.com
    Content-type: application/x-www-form-urlencoded;charset=utf-8

    요청 Parameter

    토큰을 요청할 때 grant_type, clinet_id, redirect_uri, code의 데이터는 필수로 제공해줘야 한다.

     

    요청 Sample

    curl -v -X POST "https://kauth.kakao.com/oauth/token" \
    -d "grant_type=authorization_code" \
    -d "client_id={REST_API_KEY}" \
    -d "redirect_uri={REDIRECT_URI}" \
    -d "code={AUTHORIZATION_CODE}"

     

     

    3) 사용자 정보 가져오기 #

    로그인한 사용자의 정보를 불러온다. 사용자 정보 요청 REST API는 사용자 액세스 토큰을 사용하는 방법, 앱 어드민 키를 사용하는 방법 두 가지로 제공되는데, 이 글에서는 전자인 사용자 액세스 토큰을 사용하는 방법을 이용한다. 어드민 키는 보안에 유의하여 사용해야 하므로 서버에서 호출할 때만 사용한다.

     

    사용자 액세스 토큰 또는 어드민 키를 헤더(Header)에 담아 GET 또는 POST로 요청한다.

     

    테스트 개발에서는 GET을 사용한다. (POST의 매개 변수로는 이미지 URL 값 HTTPS 여부, HTTPS 사용 여부 등을 설정할 수 있다.)

     

    요청 URL (액세스 토큰 사용)

    GET/POST /v2/user/me HTTP/1.1
    Host: kapi.kakao.com
    Authorization: Bearer {ACCESS_TOKEN}
    Content-type: application/x-www-form-urlencoded;charset=utf-8

     

    응답

    카카오는 응답을 회원번호인 id로 제공한다. 사용자 로그인 정보를 획득하기 위해서는 프로필 정보 조회 API를 먼저 호출해야 한다.

     

    user_name_attribute: id

    그리고 내가 받을 데이터 email, nickname,  profile_image는 해당 정보는 kakao_account라는 JSON객체로 주어진다.

    아래 구현 코드에서 필요한 데이터를 꺼내서 사용하는 모습을 보여줄 것이다.

     

    응답 데이터 형태

    HTTP/1.1 200 OK
    {
        "id":123456789,
        "kakao_account": { 
            "profile_needs_agreement": false,
            "profile": {
                "nickname": "홍길동",
                "thumbnail_image_url": "http://yyy.kakao.com/.../img_110x110.jpg",
                "profile_image_url": "http://yyy.kakao.com/dn/.../img_640x640.jpg",
                "is_default_image":false
            },
            "email_needs_agreement":false, 
            "is_email_valid": true,   
            "is_email_verified": true,   
            "email": "sample@sample.com",
            "age_range_needs_agreement":false,
            "age_range":"20~29",
            "birthday_needs_agreement":false,
            "birthday":"1130",
            "gender_needs_agreement":false,
            "gender":"female"
        },  
        "properties":{
            "nickname":"홍길동카톡",
            "thumbnail_image":"http://xxx.kakao.co.kr/.../aaa.jpg",
            "profile_image":"http://xxx.kakao.co.kr/.../bbb.jpg",
            "custom_field1":"23",
            "custom_field2":"여"
            ...
        }
    }

     

     

     

     

    3. 스프링 프로젝트 카카오 로그인 연동

    [Spring] 스프링으로 OAuth2 로그인 구현하기 - 구글에서 OAuth코드 기반을 잡아줬기 때문에 해당 코드와 이어진다.

    1) OAuthAttributes

    OAuth2UserService를 통해 가져온 카카오 OAuth2User의 attributes를 담을 클래스이다.

    package com.loosie.book.springboot.config.auth.dto;
    
    import com.loosie.book.springboot.domain.user.Role;
    import com.loosie.book.springboot.domain.user.User;
    import lombok.Builder;
    import lombok.Getter;
    
    import java.util.Map;
    
    @Getter
    public class OAuthAttributes {
        private Map<String, Object> attributes; // OAuth2 반환하는 유저 정보 Map
        private String nameAttributeKey;
        private String name;
        private String email;
        private String picture;
    
        @Builder
        public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey, String name, String email, String picture) {
            this.attributes = attributes;
            this.nameAttributeKey = nameAttributeKey;
            this.name = name;
            this.email = email;
            this.picture = picture;
        }
    
        public static OAuthAttributes of(String registrationId, String userNameAttributeName, Map<String, Object> attributes){
            //(new!) kakao         
            if("kakao".equals(registrationId)){
                return ofKakao("id", attributes);
            }
            // naver
            if("naver".equals(registrationId)){
                return ofNaver("id", attributes);
            }
            // google
            return ofGoogle(userNameAttributeName, attributes);
        }
        
        // (new!)
        private static OAuthAttributes ofKakao(String userNameAttributeName, Map<String, Object> attributes) {
            // kakao는 kakao_account에 유저정보가 있다. (email)
            Map<String, Object> kakaoAccount = (Map<String, Object>)attributes.get("kakao_account");
            // kakao_account안에 또 profile이라는 JSON객체가 있다. (nickname, profile_image)
            Map<String, Object> kakaoProfile = (Map<String, Object>)kakaoAccount.get("profile");
    
            return OAuthAttributes.builder()
                    .name((String) kakaoProfile.get("nickname"))
                    .email((String) kakaoAccount.get("email"))
                    .picture((String) kakaoProfile.get("profile_image_url"))
                    .attributes(attributes)
                    .nameAttributeKey(userNameAttributeName)
                    .build();
        }
        
        // ofGoogle, ofNaver 로직 생략...
    
        public User toEntity(){
            return User.builder()
                    .name(name)
                    .email(email)
                    .picture(picture)
                    .role(Role.GUEST) // 기본 권한 GUEST
                    .build();
        }
    
    }
    

     

    2) index.mustache

    홈 화면에 Kakao Login버튼을 추가해준다. url은 authorization_url로 연결시켜줘서 누르면 카카오 로그인 창으로 이동한다.

    {{>layout/header}}
    
    <h1>스프링 부트로 시작하는 웹 서비스</h1>
    <div class="col-md-12">
        <div class="row">
            <div class="col-md-6">
                <a href="/posts/save" role="button" class="btn btn-primary">글 등록</a>
                {{#userName}}
                    Logged in as: <span id="user">{{userName}}</span>
                    <a href="/logout" class="btn btn-info active" role="button">Logout</a>
                {{/userName}}
                {{^userName}}
                    <a href="/oauth2/authorization/google" class="btn btn-success active" role="button">Google Login</a>
                    <a href="/oauth2/authorization/naver" class="btn btn-secondary active" role="button">Naver Login</a>
                    <a href="/oauth2/authorization/kakao" class="btn btn-third active" role="button">Kakao Login</a>
                {{/userName}}
            </div>
        </div>
    </div>
    
    {{>layout/footer}}
    

     

     

     

    웹 로그인 테스트

    1) 카카오 로그인 버튼 클릭

    authorization_url로 이동한다.

     

     

    2) 카카오 로그인 창 > ID, PW입력

    카카오도 마찬가지로 아이디와 비밀번호가 일치하면 로그인 인가 코드가 담긴 콜백 url값을 보낸다. 그 인가 코드로 POST: oauth2.0/token 요청을 통해 인증받는다. F12 → Network를 보면 위 과정을 확인해 볼 수 있다.

     

     

     

    3) 동의 화면을 체크해주면 로그인 성공

    인증이 성공하면 로그인이 완료된다.

     

     

     


    [Spring] 스프링으로 OAuth2 로그인 구현하기1 - 구글

    [Spring] 스프링으로 OAuth2 로그인 구현하기2 - 네이버

    [Spring] 스프링으로 OAuth2 로그인 구현하기3 - 카카오