이번 포스팅에서는 Naver Works API를 활용해 봇으로 메시지를 수신하는 기능을 구현하는 방법을 소개한다. 특정 함수의 실행 결과를 Naver Works 봇 API를 통해 메시지로 받아, 손쉽게 모니터링할 수 있는 효율적인 방법을 제시한다.
1. 메시지 정보 전달: 특정 함수가 실행된 후, 그 결과 값(메시지 정보)을 메시지 전송용 Lambda 함수로 전달.
2. JWT 토큰 생성: 메시지 전송용 Lambda 함수에서 JWT 토큰을 생성하여 인증에 사용.
3. 인증 과정 수행: 생성된 JWT 토큰을 이용해 인증을 수행.
4. bot API 호출: 인증이 완료되면 accessToken으로 bot API를 호출하여 특정 채널로 메시지를 전송.
Naver Works API는 Naver Works와의 연동 기능을 제공한다.
개발자는 Naver Works API를 사용하여 Bot 사용, 조직 및 그룹 관리, 파일 업로드/다운로드 등 Naver Works에서 제공하는 다양한 기능과 리소스를 활용하는 앱을 개발할 수 있다.
Naver Works API를 사용하기 위해서는 반드시 API 인증 절차를 거쳐야 한다.
인증 방법에는 구성원 계정 인증(OAuth)과 서비스 계정 인증(JWT) 두 가지가 있다.
Naver Works 문서에 따르면,
Naver Works API 2.0를 이용하려면 OAuth를 기반으로 하는 사용자의 승인이 필요하다.하지만 이 방법으로는 조직 연동이나 안부 확인처럼 구성원이 작성한 데이터에 접근하는 클라이언트 앱을 작성할 때 현실적으로 어려움이 따른다. 이런 경우 서비스 계정(service account)라는 가상 계정을 이용할 수 있다.
서비스 계정을 이용하면 클라이언트 앱에서 구성원이 작성한 데이터에 해당 구성원의 승인 없이 접근할 수 있다. 관리자는 클라이언트 앱에서 서비스 계정을 생성한 후, 구성원 또는 관리자의 사용 권한을 위임받아 대신 접근하는 형태로 사용할 수 있다.
위와 같이 서비스 계정 인증에 대한 설명이 작성되어있지만, 나는 단순하게 액세스 토큰 받기 단계가 더 간편하다는 이유로 서비스 계정 인증 방식(JWT)을 선택했다.
이 방법은 절차가 단순하여 인증 과정을 빠르게 진행할 수 있고, API 연동을 보다 효율적으로 구현할 수 있다.
자세한 내용은 구성원 계정으로 인증(OAuth), 서비스 계정으로 인증(JWT) 참고
먼저 Developer Console에 클라이언트 앱을 등록하고 아래와 같은 정보를 발급한다.
Naver Works Developer Console 에 접근하여 ClientApp에서 새로운 앱을 추가한다.
1. 생성한 앱에 접속한다.
2. OAuth Scopes에 bot 권한을 추가한다.
유의사항
1. 발급한 Service Account는 해당 클라이언트에만 사용할 수 있다.
2. 비밀키는 Service Account 인증에 사용되며, 파일 형태로 발급받을 수 있다.
3. 1개의 Private Key만 유효하며, 재발행할 경우 기존 Private Key 는 사용할 수 없다.
4. Access Token 발행시 유효기간은 1일이며, Refresh Token의 유료기간은 90일이다.
JSON 객체에 인증에 필요한 정보들을 담은 후, 비밀키로 서명한 토큰이다.
JWT 구조
header와 payload는 단순히 Base64Url로 인코딩되어 있어 누구나 쉽게 복호화할 수 있지만, signature는 key가 없으면 복호화할 수 없다.
또한, header에 선언한 알고리즘에 따라 key는 개인키가 될 수도 있고, 비밀키가 될 수도 있다.
개인키로 서명했다면 공개키로 유효성 검사를 할 수 있고, 비밀키로 서명했다면 비밀키를 가지고 있는 사람만이 암호화와 복호화, 유효성 검사를 할 수 있다.
// jwt 토큰 발급
async function createJwt(clientId, serviceAccount, primaryKey) {
try {
const currentTime = Math.floor(Date.now() / 1000);
const payload = {
iss: clientId,
sub: serviceAccount,
iat: currentTime, // 발급 시간
exp: currentTime + 3600, // 만료 시간 (발급 시간으로부터 1시간)
};
const headers = {
// alg: 'RS256',
typ: 'JWT',
};
const jwtToken = jwt.sign(payload, primaryKey, {
algorithm: 'RS256', // algorithm은 옵션에서 지정하면 자동으로 헤더에 반영됨
header: headers,
});
return jwtToken;
} catch (error) {
console.log('Error creating jwt: ', error.message);
throw error;
}
}
// 서버로 토큰 요청
async function requestToken(jwtToken, clientId, clientSecret) {
const authUrl = 'https://auth.worksmobile.com/oauth2/v2.0/token';
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
const data = new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
client_id: clientId,
client_secret: clientSecret,
assertion: jwtToken,
scope: 'bot',
});
try {
const response = await axios.post(authUrl, data, { headers });
return response.data;
} catch (error) {
console.error('Error requesting token:', error.message);
throw error;
}
}
botId: Developer Console의 Bot 탭에서 메세지를 보낼 Bot을 선택하여 Bot ID 확인
channelId: 선택한 봇을 포함한 채팅방을 생성한 후, 채팅방의 채널 ID 확인
export async function handler(event) {
const body = JSON.parse(event.body);
const clientId = 'my-clientId';
const clientSecret = 'my-clientSecret';
const serviceAccount = 'my-serviceAccount';
const primaryKey = 'my-primaryKey';
const botId = 'my-botId';
const channelId = 'my-channelId';
const url = `https://www.worksapis.com/v1.0/bots/${botId}/channels/${channelId}/messages`;
try {
const jwtToken = await createJwt(clientId, serviceAccount, primaryKey);
const tokenResponse = await requestToken(jwtToken, clientId, clientSecret);
const accessToken = tokenResponse.access_token;
const headers = {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};
const data = {
content: {
type: 'text',
text: body.message,
},
};
await axios.post(url, data, { headers });
return true;
} catch (error) {
return false;
}
}
[참고 자료]
https://developers.worksmobile.com/kr/docs/api
https://velog.io/@chuu1019/%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90-JWTJson-Web-Token