Naver Works API를 사용하여 메세지 보내기(Bot)

hyejin·2024년 10월 17일
0

study-2024

목록 보기
17/17
post-thumbnail

이번 포스팅에서는 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에 클라이언트 앱을 등록하고 아래와 같은 정보를 발급한다.

  • Client ID
  • Client Secret
  • Service Account
  • Private Key

Naver Works Developer Console 에 접근하여 ClientApp에서 새로운 앱을 추가한다.
1. 생성한 앱에 접속한다.
2. OAuth Scopesbot 권한을 추가한다.

  • bot 권한은 메시지 봇의 Read 및 Write 권한을 포함하며, 해당 scope를 추가해야 bot API 사용 가능
  • 자세한 내용은 Scope 참고

3. Private Key를 발급받아 안전하게 보관한다.

유의사항
1. 발급한 Service Account는 해당 클라이언트에만 사용할 수 있다.
2. 비밀키는 Service Account 인증에 사용되며, 파일 형태로 발급받을 수 있다.
3. 1개의 Private Key만 유효하며, 재발행할 경우 기존 Private Key 는 사용할 수 없다.
4. Access Token 발행시 유효기간은 1일이며, Refresh Token의 유료기간은 90일이다.


토큰 발급 과정

JWT(Json Web Token)

JSON 객체에 인증에 필요한 정보들을 담은 후, 비밀키로 서명한 토큰이다.

JWT 구조

  • header: alg(Signature에서 사용하는 알고리즘), typ(토큰 타입)으로 구성
    -> signature에서 사용하는 알고리즘은 대표적으로 RS256(공개키/개인키)HS256(비밀키(대칭키))가 있다.
  • payload: 사용자 정보의 한 조각인 클레임(claim)으로 구성
  • signature: header와 payload의 문자열을 합친 후에, header에서 선언한 알고리즘와 key를 이용해 암호한 값

headerpayload는 단순히 Base64Url로 인코딩되어 있어 누구나 쉽게 복호화할 수 있지만, signature는 key가 없으면 복호화할 수 없다.
또한, header에 선언한 알고리즘에 따라 key개인키가 될 수도 있고, 비밀키가 될 수도 있다.
개인키로 서명했다면 공개키로 유효성 검사를 할 수 있고, 비밀키로 서명했다면 비밀키를 가지고 있는 사람만이 암호화와 복호화, 유효성 검사를 할 수 있다.

JWT 생성 및 전자 서명

// 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

profile
노는게 제일 좋아
post-custom-banner

0개의 댓글