Google API에서 연락처 가져오기

cleopatra·2021년 1월 20일
0
post-thumbnail

오늘의 목표📋

☑️ (지난번에 이어) 로그인 후 발급받은 토큰을 로컬 스토리지에 저장하고 관리하기
☑️ Vue에 api 모듈 추가하기
☑️ Google이 제공하는 People API 로 사용자 연락처 받아오기📌

문서가 잘 되어있는듯 안 되어있어서 너무 힘들었다 ㅜㅜ
people API 가 아니라도 Google이 제공하는 모든 open API 는 같은 방식으로 사용할 수 있다.

저번에 로그인 한 거 있잖아요

그 때 받아온 AuthToken을 AuthManager를 만들어서 관리하기로 했었는데 포스트로 정리 안하고 그냥 했었다.....

벨로그를 오픈한김에 정리 하자면

frontend/src/plugins/AuthManager.js 파일을 만들어서 아래처럼 구현한다.

대충 내용은 로그인 후 발급받은 access token 을 브라우저의 로컬 스토리지에 저장해두고 AuthManager를 통해 관리하겠다는 것이다.

import moment from 'moment';
const AUTH_SECRET_KEY='secret';
const AUTH_EXPIRED_KEY='expired';

const AuthManager = () => {
  let state = {
    [AUTH_SECRET_KEY]: null,
    [AUTH_EXPIRED_KEY]: null,
  };

  const initialize = () => {
    state[AUTH_SECRET_KEY] = null;
    state[AUTH_EXPIRED_KEY] = null;

    localStorage.removeItem(AUTH_SECRET_KEY, null);
    localStorage.removeItem(AUTH_EXPIRED_KEY, null);
  };

  const save = (params) => {
    console.log(params);
    state[AUTH_SECRET_KEY] = params.token;
    state[AUTH_EXPIRED_KEY] = params.expired;

    localStorage.setItem(AUTH_SECRET_KEY, params.token);
    localStorage.setItem(AUTH_EXPIRED_KEY, params.expired);
  };

  const load = () => {
    state[AUTH_SECRET_KEY]  = localStorage.getItem(AUTH_SECRET_KEY);
    state[AUTH_EXPIRED_KEY] = localStorage.getItem(AUTH_EXPIRED_KEY);
  };

  const getToken = () => {
    return state[AUTH_SECRET_KEY];
  };

  const getExpired = () => {
    return state[AUTH_EXPIRED_KEY];
  };

  return {
    initialize,
    save,
    load,
    getToken,
    getExpired,
    //TODO isValid, 
  };
};

export default AuthManager();

그리고 로그인 화면에서 로그인 성공 시 토큰과 만료시간을 저장해준다.
frontend/src/views/Login.vue

    async handleLogin() {
      try {
        const GoogleUser = await this.$gAuth.signIn(() => {}, (e) => {console.log(e);});
        if (!GoogleUser.Bc.access_token) throw new Error('로그인에 실패했습니다.');

        // 저장중
        this.googleUser = GoogleUser;
        
        // AuthManager를 통해 로컬 스토리지에 저장
        AuthManager.save({
          token: GoogleUser.Bc.access_token,
          expired: GoogleUser.Bc.expires_at,
        });

        this.$router.push('/main');
      } catch (e) {
        if (e.error === 'popup_closed_by_user') {
          // 로그인 취소, 에러 아님
          return;
        }
        console.error(e);
      } finally {
        this.getLoginInfo();
      }
    },

내 프로젝트의 경우 직접 구현하는 서버는 폐쇄망에 설치 될 예정이라 별도로 인증 기능은 구현하지 않기로 했다.

단, 저장된 access token의 expired를 체크하여 만료되는 순간 자동으로 AuthManager.initialize()를 호출하여 정보를 클리어해주고 로그인 화면으로 이동 시킬 예정이다.

Vue에 api 모듈 세팅

frontend/src/api 경로를 만들고 다음과 같이 구성한다.

frontend/src/api/index.js

import axios from 'axios';
const client = axios.create({
  headers: {
    ['Content-Type']: 'application/json;charset=UTF-8',
  },
});

client.interceptors.request.use(
  (request) => {
    if (!request.data) {
      request.data = {};
    }

    return request;
  },

  (error) => {
    Promise.reject(error);
  },
);

client.interceptors.response.use(
  (response) => {
  	// 200 이 아니면 에러로 처리
    if (response && response.status !== 200) {
      return Promise.reject(response);
    }
    return response;
  },
  (error) => {
    throw error;
  },
);

export default client;

다음은 google 로 api를 던질때와 내 서버로 api 를 던질때 사용할 url을 정의한다.

frontend/src/api/url.js

export default {
  server: `${process.env.VUE_APP_SERVER_URL}`,
  google: {
    base: 'https://www.googleapis.com',
  },
};

이제 나머지에서 가져다 쓰기만 하면 끄읏 😊

✨ 이제 진짜로 구글API를 써보자!

Google API 에서 내가 쓸 라이브러리 골라 권한 얻기

지난번에 console.developers.google.com 에서 내 어플리케이션이 사용할 프로젝트 생성하는 방법을 링크로 대체했었다.

그 때 생성했던 프로젝트로 돌아가서 API 추가 버튼을 누르자.

그럼 멋진 구글 API 고르기 화면이 나온다.

여기서 내 앱에 붙여줄 마음에 드는 API 를 하나하나 둘러볼 수도 있지만
우리는 👥 연락처 데이터를 받아올 것이기 때문에 'people' 을 검색한다.

주의!
기존에는 연락처 정보를 관리하기 위해 Contract API 를 사용했었지만 이제 그건 사라졌고 People API 를 사용해야 한다.

찾아 들어가서

사용 버튼을 누르면 내 프로젝트(neti)에 로그인한 사람의 연락처 정보를 얻어올 권한이 생긴다.

OAuth 에 People API scope 추가하기

지난번 포스팅에서 발견한 vue3 전용 oauth2를 아직 안붙였음...
아무튼 각자의 방식으로 oauth 모듈에 people 스콥을 추가해주자.

frontend/src/main.js

// set auth config
const prompt = 'select_account'
const GoogleAuthConfig = Object.assign({ scope: 'profile email' }, {
  clientId: process.env.VUE_APP_OAUTH_CLIENT,
  // 여기가 스콥 추가 부분 
  scope: 'profile email https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/directory.readonly',
});

(잘보면 내가 캘린더 API 도 몰래 추가해놓은 것을 알 수 있다)

API 로 요청을 날려보자

내가 사용할 API는 이거!
People API 페이지 가기

해당 화면 우측에 보면 Try it 버튼이 있는데 일단 이걸로 제대로 사용법을 간단히 익혀보자.

문서가 잘 정리된 듯 잘 정리되어있지 않다... 눈에 안들어와.

Query parameters 를 보면 필수인 애들은 Required 라고 명시 되어있다.
Try this API 에서 필수인 파라미터를 눈치껏 넣어준 다음

우린 OAuth만 쓸거니까 아래건 체크 해제를 해주고 (사실 뭔 차이인지 모르겠다) EXECUTE 로 실행한다.

권한 설정이 잘못 되어있으면 401이나 403 에러가 난다.
그런데 Try it 에서 권한 에러가 난다면 당신의 계정 자체가 이 API를 사용할 권한이 없다는 뜻이다.

⚠️ 구글 웹으로 서비스를 이용하는 것과 API 권한이 있는건 다르다

처음에 Group Manager API 를 사용하려고 정말 하루 종일 삽질을 엄청 했다.
내 경우, 구글의 Group 메뉴에서 그룹 추가/삭제/수정 작업을 잘만 했는데 API를 호출하면 권한이 없다고 해서(401) 돌아버릴뻔....

다른 API 를 써본 결과, 구글 웹페이지를 쓰는 권한과 OAuth로 API를 사용할 권한은 다른것으로 결론이 났다.

아무튼 200 응답을 받았다면 Try it 상단의 확장 버튼으로 상세한 호출 방법을 확인하자.


눌러서

cURL 부분을 보자.

첫 줄은 우리가 사용할 URL🍒 을,
두 번째 줄은 HTTPS Request를 날릴 때 Header🧀 를 보여준다.

access token을 헤더에 싣기

위에서 API 사용설정을 해주었으면 변경된 scope을 적용하기 위해 로그인을 다시 해주어야 한다

이미 로그인이 되어 로컬 스토리지에 access token이 있다고 가정하겠다.

위에서 확인한 우리가 사용할 URL🍒을 추가해주자.
frontend/src/api/url.js

export default {
  server: `${process.env.VUE_APP_SERVER_URL}`,
  google: {
    base: 'https://www.googleapis.com',
    // people URL 추가
    people: 'https://people.googleapis.com/v1',
  },
};

이제 Header 🧀 를 설정해주면 된다. 거의 다 왔다!

frontend/src/api/google/index.js 파일을 만들어 구글로의 요청을 따로 분리했다.

import client from '@/api';
import BASE from '@/api/url';
import AuthManager from '@/plugins/AuthManager';

const accessToken = () => {
  AuthManager.load();
  return AuthManager.getToken();
}

export default {
  getMembers: () => client.get(
    // 위에서 확인한 query param도 붙여넣어주고 
    `${BASE.google.people}/people:listDirectoryPeople?readMask=emailAddresses,names,photos&sources=DIRECTORY_SOURCE_TYPE_DOMAIN_PROFILE`,
    { // header 🧀  세팅
      headers : { 'Authorization' : `Bearer ${accessToken()}` }
    }
  ),
};

내 query param은 이메일 주소와 이름, 프로필 이미지를 받아오도록 설정되어있다.

이제 날려볼까?

테스트용 페이지를 새로 만들어서 버튼을 추가한 다음, 클릭 콜백에서 요 API 를 호출하자.

아무 vue 파일

<template>
  <div>
    <button
      @click="getMembers"
    >
      직원 목록 가져오기
    </button>
    {{ members }}
  </div>
</template>
<script>
import api from '@/api/google';

export default {
  name: 'GoogleTest',
  data: () => ({
    members: null,
  }),

  methods: {
    async getMembers() {
      const response = await api.getMembers();
      console.log(response);
      this.members = response.data.people; 
    },
  },
};
</script>

로그인 한 다음 실행해야 한다. 안그럼 권한 없음으로 실패 뜸~!

정상적으로 성공한 response.data를 찍어보면 쬐금 많이 복잡하다.

{
  "people": [
    {
      "resourceName": "people/아이디",
      "etag": "뭔지 모르지만 숨김",
      "names": [
        {
          "metadata": {
            "primary": true,
            "source": {
              "type": "PROFILE",
              "id": "아이디"
            }
          },
          "displayName": "이름1",
          "familyName": "김",
          "givenName": "김이름1",
          "displayNameLastFirst": "김이름1",
          "unstructuredName": "김이름1"
        }
      ],
      "photos": [
        {
          "metadata": {
            "primary": true,
            "source": {
              "type": "PROFILE",
              "id": "아이디"
            }
          },
          "url": "https://lh5.googleusercontent.com/어쩌고저쩌고/photo.jpg",
          "default": true
        }
      ],
      "emailAddresses": [
        {
          "metadata": {
            "primary": true,
            "verified": true,
            "source": {
              "type": "DOMAIN_PROFILE",
              "id": "아이디"
            }
          },
          "value": "sample@test.com"
        }
      ]
    },
    ...   
  ]
}

여기까지 된다면 People API 사용 성공!⚡️

너무 복잡한 People을 순화하기

마지막에 찍어본 people 구조가 너무 복잡하다..
내 프로젝트 내에서 이 구조를 고대로 들고다니자니 getter 하기가 너무 피곤할 것 같아서 나만의 클래스를 쓰기로 한다.

frontend/src/entity/people.js생성

class People {
  constructor(_people) {
    if (_people.emailAddresses && _people.emailAddresses.length) {
      this.email = _people.emailAddresses[0].value;
    }

    if (_people.names && _people.names.length) {
      this.name = _people.names[0].value;
    } else if (this.email) {
      this.name = this.email.split('@')[0];
    }

    if (_people.photos && _people.photos.length) {
      this.avatar = _people.photos[0].url || '/static/images/default_avatar.png';
    }
  }

  getEmail() {
    return this.email;
  }

  getName() {
    return this.name;
  }

  getAvatar() {
    return this.avatar;
  }
}

export { People as default };

만들었으니 써먹자~!

아까 그 아무 파일.vue 를 다시 열어서 People로 컨버팅 한 다음 화면에 찍어보자.

<template>
  <div>
    <button
      @click="getMembers"
    >
      직원 목록 가져오기
    </button>
    {{ members }}
  </div>
</template>
<script>
import api from '@/api/google';
import People from '@/entity/people';

export default {
  name: 'GoogleTest',
  data: () => ({
    members: null,
  }),

  methods: {
    async getMembers() {
      const response = await api.getMembers();
      console.log(response);
      this.members = response.data.people.map(people => new People(people));
    },
  },
};
</script>

이럼 대충 간단해져서 관리가 편해진다 ☺️

이거 한다고 삽질한게 꼬박 하루라.. 정리 안하고는 못배기겠다.
오늘도 끄읏✨

profile
안녕나는클레오파트라세상에서제일가는포테이토칩

0개의 댓글