☑️ (지난번에 이어) 로그인 후 발급받은 토큰을 로컬 스토리지에 저장하고 관리하기
☑️ 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()
를 호출하여 정보를 클리어해주고 로그인 화면으로 이동 시킬 예정이다.
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',
},
};
이제 나머지에서 가져다 쓰기만 하면 끄읏 😊
지난번에 console.developers.google.com 에서 내 어플리케이션이 사용할 프로젝트 생성하는 방법을 링크로 대체했었다.
그 때 생성했던 프로젝트로 돌아가서 API 추가 버튼을 누르자.
그럼 멋진 구글 API 고르기 화면이 나온다.
여기서 내 앱에 붙여줄 마음에 드는 API 를 하나하나 둘러볼 수도 있지만
우리는 👥 연락처 데이터를 받아올 것이기 때문에 'people' 을 검색한다.
주의!
기존에는 연락처 정보를 관리하기 위해 Contract API 를 사용했었지만 이제 그건 사라졌고 People API 를 사용해야 한다.
찾아 들어가서
사용 버튼을 누르면 내 프로젝트(neti)에 로그인한 사람의 연락처 정보를 얻어올 권한이 생긴다.
지난번 포스팅에서 발견한 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는 이거!
People API 페이지 가기
해당 화면 우측에 보면 Try it 버튼이 있는데 일단 이걸로 제대로 사용법을 간단히 익혀보자.
문서가 잘 정리된 듯 잘 정리되어있지 않다... 눈에 안들어와.
Query parameters 를 보면 필수인 애들은 Required 라고 명시 되어있다.
Try this API 에서 필수인 파라미터를 눈치껏 넣어준 다음
우린 OAuth만 쓸거니까 아래건 체크 해제를 해주고 (사실 뭔 차이인지 모르겠다) EXECUTE 로 실행한다.
권한 설정이 잘못 되어있으면 401이나 403 에러가 난다.
그런데 Try it 에서 권한 에러가 난다면 당신의 계정 자체가 이 API를 사용할 권한이 없다는 뜻이다.
처음에 Group Manager API 를 사용하려고 정말 하루 종일 삽질을 엄청 했다.
내 경우, 구글의 Group 메뉴에서 그룹 추가/삭제/수정 작업을 잘만 했는데 API를 호출하면 권한이 없다고 해서(401) 돌아버릴뻔....
다른 API 를 써본 결과, 구글 웹페이지를 쓰는 권한과 OAuth로 API를 사용할 권한은 다른것으로 결론이 났다.
아무튼 200 응답을 받았다면 Try it 상단의 확장 버튼으로 상세한 호출 방법을 확인하자.
눌러서
cURL 부분을 보자.
첫 줄은 우리가 사용할 URL🍒 을,
두 번째 줄은 HTTPS Request를 날릴 때 Header🧀 를 보여준다.
위에서 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 구조가 너무 복잡하다..
내 프로젝트 내에서 이 구조를 고대로 들고다니자니 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>
이럼 대충 간단해져서 관리가 편해진다 ☺️
이거 한다고 삽질한게 꼬박 하루라.. 정리 안하고는 못배기겠다.
오늘도 끄읏✨