네이버웍스 api 예제 - neibeowogseu api yeje

API 1.0 개요

NAVER WORKS에서 제공하는 API의 종류와 인증 방식을 설명한다.

동영상 가이드

NAVER WORKS Bot Platform API는 서비스 API와 서버 API 두 가지 종류로 나뉜다. 자세한 내용은 각 API 명세에서 기술한다. 종류가 명시되지 않은 API는 서비스 API이다.

서비스 API

사용자가 로그인한 후 사용할 수 있는 API로, 본인의 데이터에만 접근할 수 있다. 단, 캘린더의 "다른 사람의 일정 목록 조회" API처럼, 도메인 내 다른 사용자의 데이터에 접근 가능한 API도 일부 존재한다.

서버 API

사용자의 로그인 없이 사용할 수 있는 API로, 도메인 내 모든 사용자의 데이터에 접근할 수 있다. 대표적으로 조직 연동과 마이그레이션 API가 서버 API이다.

API 인증

서비스 API와 서버 API는 모두 OAuth 2.0을 기반으로 하지만, 인증 방식은 서로 다르다.

서비스 API 인증 방식

서비스 API의 인증 방식은 다음과 같다.

그림 1 서비스 API 인증 방식

  1. 서비스 API 사용에 필요한 정보를 발급받는다. 사용자는 NAVER WORKS Developer Console에서 서비스 API 사용에 필요한 정보를 발급받는다. 자세한 내용은 API 인증 준비 를 참고한다.
  2. 서비스 API에 필요한 정보를 제공한다.
  3. Authorization code 발급을 요청한다(window open). 새 창으로 Authorization code 발급 요청을 한다. 이때 NAVER WORKS Developer Console에서 발급받은 서비스 API 컨슈머키를 사용한다.
  4. NAVER WORKS 로그인 창을 제공한다. NAVER WORKS에 로그인되어 있지 않으면, 로그인 창을 제공한다. 이미 로그인이 되어 있으면 이 과정은 생략된다.
  5. ID/Password를 입력한다. NAVER WORKS 로그인 창에서 ID/Password를 입력하여 로그인한다.
  6. 인증 확인 후 Authorization code를 발급한다. ID/Password를 검증한 후, Authorization code를 발급하여 3번 요청 시 전달된 redirect_uri 파라미터로 전달한다.
  7. Access Token을 요청한다(API call). Authorization code를 이용하여 Access Token을 요청한다. Authorization code는 일회성이다.
  8. Access Token을 발급한다. Authorization code를 검증한 후, Access Token을 발급한다.
  9. 필요한 정보를 Header에 추가하여 API를 호출한다. 서비스 API 호출 시 컨슈머키와 Access Token을 Header에 추가하여 호출한다.
  10. Header 정보의 유효성을 확인하고, API를 처리한다. NAVER WORKS은 Header 정보의 유효성을 확인하고, API를 실제로 처리한다.
  11. API 처리 결과를 반환한다.

참고

  • API를 호출할 때마다 Access Token을 발급받을 필요는 없다(3~8번 과정).
  • 한 번 발급받은 Access Token은 별도로 저장해 놓고 재사용할 수 있다(예: 쿠키).

서버 API 인증 방식

서버 API 인증 방식은 다음과 같다.

그림 2 서버 API 인증 방식

  1. 서버 API 사용에 필요한 정보를 발급받는다. 사용자는 NAVER WORKS Developer Console에서 서버 API 사용을 위해 필요한 정보를 발급받는다. 자세한 내용은 API 인증 준비 를 참고한다
  2. 서버 API 사용에 필요한 정보를 제공한다.
  3. 필요한 정보를 Header에 추가하여 API를 호출한다. 서버 API 호출 시 컨슈머키와 Server Token을 Header에 추가하여 호출한다.
  4. Header 정보의 유효성을 확인하고 요청 IP를 확인한 후, API를 처리한다. NAVER WORKS는 Header 정보의 유효성을 확인하고, Developer Console에서 Token 발급 시 등록한 서버 IP와 실제 API를 요청한 클라이언트 IP가 동일한지 확인한 후, API를 처리한다.
  5. API 처리 결과를 반환한다.

업데이트 된 답글입니다.

Bot API는 Sandbox 환경에서는 제공되지 않으며, 운영 환경에서 테스트를 진행해주셔야 합니다.

Bot API는 서버 API이며 Developers에서 Bot을 생성하시고
Admin 서비스에서 Bot 추가를 해주신 이후에 아래와 같은 URL 형식으로 요청할 수 있습니다.

[메시지 전송(Text) Request URL 형식]
https://apis.worksmobile.com/r/{API ID}/message/v1/bot/{botNo}/message/push

그리고 공유주신 캡쳐화면을 보면 브라우저에서 URL을 입력해주셨는데 그 방식으로는 API를 연동하실 수 없습니다.
개발환경을 구축해주신 후 Server to Server 간 Https를 이용하여 API 호출해주셔야 합니다.

직접적인 샘플코드를 제공하고 있지 않지만 서비스 API 토큰을 획득하는 자바 예제를 아래 링크에서 확인하실 수 있으니 참고하시기 바랍니다.

https://developers.worksmobile.com/kr/document/2002003?lang=ko

환경

- Mac OS

- Eclipse 202009 버전

- 당연히 Spring Plugin 도 설치 했다.

신규 프로젝트 생성

Wizards 에서 Spring 으로 검색하여 나오는 Spring Starter Project 로 진행

네이버웍스 api 예제 - neibeowogseu api yeje

다른건 딱히 뭐 고칠께 없어서 걍 밑에 각 항목 별 텍스트 정도면 잘 넣어준다.

(기존에 message 로 만들어 놓은게 있다보니 샘플로 message1로 했다.)

네이버웍스 api 예제 - neibeowogseu api yeje

당장 필요한 Dependencies 만 몇개 걸어준다.

- Spring Boot DevTools : 서버 실행해놓고 소스 고치면 알아서 자동 적용해주는 기능이 있어서 사용한다.

- Lombok : 모델 Bean 클래스 파일에 get, set 넣기 귀찮아서 넣었다. ( Log 어노테이션도 이거 참조 한다. )

- Spring Web : 웹서비스 형태로 만들거라서 넣었다.

네이버웍스 api 예제 - neibeowogseu api yeje

생성된 프로젝트 구조

- 아래와 같이 네이버 웍스 Developers Console 에서 다운로드 받은 서버ID 방식 비밀키 파일을 resources 에 복사한다.

- 각각 controller, model, service, util 패키지를 만들어 준다.

- Java 파일 구조 설명

    > MessageApplication.java => 실행 파일

    > controller.MessageRestController.java => 메시지 내용 입력받는 API

    > model.Message.java => 메시지 전송용 모델(Controller 에서 입력 받은 데이터를 이 객체에다가 매핑 한다.)

    > model.NaverWorksInfo.java => 네이버웍스 API 호출 하기 위한 키값, URL 정보를 저장 한다.

    > service.MessageService.java => 네이버웍스 메시지 전송 API 를 호출한다.

    > service.NaverWorksTokenService.java => 네이버웍스 API를 호출하기 위한 Token을 발급 받는다.

    > util.RSAUtils.java => 비밀키 파일을 읽어서 암호화 키로 만드는 역할을 한다.

- Resources 파일 구조 설명

    > application.properties => 네이버웍스 연동 키 정보들을 넣어놨다.

    > private.key => 네이버웍스 Developers Console 에서 다운로드 받은 파일이다 (서버 Token 발급 받는 용도) 

네이버웍스 api 예제 - neibeowogseu api yeje

파일별 소스 내용

Dependency

- pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.5</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.naverworks</groupId>
	<artifactId>message</artifactId>
	<version>0.0.1</version>
	<name>message</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>16</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		
		
		<dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>
		
		<dependency> 
			<groupId>org.apache.httpcomponents</groupId> 
			<artifactId>httpclient</artifactId> 
		</dependency>
		
		
		<dependency>
		    <groupId>com.auth0</groupId>
		    <artifactId>java-jwt</artifactId>
		    <version>3.8.0</version>
		</dependency>
		
		
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

API 호출 하기 위한 정보 저장

- src/main/resources/application.properties

naverworks.api_id=kr1wDYVCnaPCE
naverworks.server_consumer_key=Ir9LabOOkLB_NO1pDLwa
naverworks.server_id=55b16ce3d7744c19b1e62dc5e3ff8349
naverworks.server_private_key=private.key
naverworks.url_auth=https://auth.worksmobile.com
naverworks.url_api=https://apis.worksmobile.com

네이버웍스 개발자 콘솔에서 다운로드 받은 비밀키파일

- src/main/resources/private.key

-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC40XF3cyA/Wr7E
ecqW8mEGcpdDb8cdlrKIJkQ0dPXQb1rqmSC+ZxsO20FBbR9aD5EIqYqfDICcWyiH
urIbtjN7w3juy0+CF2aWol0aStxnSL4kUAf0cY33nQPej8vB1bfHqeTnhrTP1LCo
ofLQAr4AyWDzedO24Y5EroxEdW4AtiW+C9RybXcxcH/IbA6BSyTs7IpA7xFiK0NV
hshoa/64FfHUSXwEGdpTm8WhnrbLcIT/+zCE66SLmjEK6j+rr/Pg9UMsQEIqp0jc
1Vi+/TEDZIT9qQbL1h54/mSBfOGzPZO9gSfcXySWF8k5gOX06Df+CrUvyQTc9/+J
xS83YQbXAgMBAAECggEBAIJNuJAdLTk9w45GyB0QmnOvSxKStnIYE5uGT0QkykLA
n/sTJ1DW5O7eiu2UACzBGOJglol4iRyz8Klxaa28EiRKNvM1iMMRuJSvuLCW4zo2
wUhc3J7tiMhxfY7nFyN8iOxOzkKmaMi3Di52r/kUVd28HYzL8gbYlDO8VMzXdk0s
6m9C5ICfNoLVUwrYAJ8zWHK506EuoMzL59aRjgXEOyZaTUoX7UfF46Xub1FycO4h
gL8Vvx81PsPVtZqIR29Um0EYQRyAuM9ybMF8JBKAzlGeob1nnJeIxYrzHkutAH3w
7HRgBceewcEGsekolFPomCt5VvwC3JZf8lDA9i3J55ECgYEA8tIrq5CIU+B47B87
mcidOuYnZiDuvpV5d32sdvdRfDF5iGvm1oyR1kEN4dvaI/7PofUMnv4jYjlvivIZ
lLSCWiVw5ssXOxhzlfR+x216nnwcj/oTQmOTzgTfH4GP3ymq6HfNXZZ5IKpbSBux
nJ++ls1WBrXcyjjjoS36p6CUkh0CgYEAwtldAi4IbI9oi97CXCYJO6tilh5qT76V
sRpAnOfpHhu3qVStSkjaOkfiwDFUpOFKsgpDqv75SrQB7hDCj+iKRvXh5JHRKRxE
fwnuIinHzP0K7XITTT+6nNikw6YkUwucB7ZY4d22tvzrkgmj0bwZJuyYQbjAd7Ym
P8s2c5svqoMCgYEAmK3+N4pRrive3NlmqHO0KWy+KUFb3QovriqfXs5zthuFx3nR
U+ZfbNDK4dFTgH6gH73tiatSpFhpAnzoCNDXhcc1Dml02fut8gQiE1OLXku7yev8
YCosrcXyZeUZicCxbiqmPsp2r0tuyhBfxZPMSxYhmWaQxbavqpE2omAeCyECgYEA
pHX6/yADlD3nLEe1HTotiOO7rqG1ceGXT4itCMLmDLewaYhDS/P53j9WsInyu8g8
19G6E9ZqbBdLmbkjv3uzWvmm1NoPBPgLCH9FNCi3r9nVI12p1QX5aPGjAFB3UHD4
HbpXgy00T3djDjx8vUZapHQGf1KFgYWI+0d2ilVuAikCgYBZ2srXHvxGzUm165qb
hemZsq0+fKGJ1oJNEFfnSvGcpFP/WA6J6EeyaVb/yJkj/u8wseSr51yH9Z5tPNJI
EDgHi/iHK5lruyxr1H+zAPrDU7qL1cuJzKLN1McO3WBAbG0kJ0e+ebrqbGPI87D2
Y2zA5KoK3R/hlj0CGPIepLSg/Q==
-----END PRIVATE KEY-----

키 생성

- com.naverworks.message.util.RSAUtils.java

package com.naverworks.message.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;


public class RSAUtils {

	private static String getKey(String filename) throws IOException {
		
		ClassPathResource resource = new ClassPathResource(filename);
		InputStream inputStream = resource.getInputStream();
		
		byte[] bdata = FileCopyUtils.copyToByteArray(inputStream);
        String data = new String(bdata, StandardCharsets.UTF_8);
		
	    return data;
	}
	public static RSAPrivateKey getPrivateKey(String filename) throws IOException, GeneralSecurityException {
	    String privateKeyPEM = getKey(filename);
	    return getPrivateKeyFromString(privateKeyPEM);
	}

	public static RSAPrivateKey getPrivateKeyFromString(String key) throws IOException, GeneralSecurityException {
	    String privateKeyPEM = key;
	    privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----", "");
	    privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", "");
	    byte[] encoded = Base64.decodeBase64(privateKeyPEM);
	    KeyFactory kf = KeyFactory.getInstance("RSA");
	    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
	    RSAPrivateKey privKey = (RSAPrivateKey) kf.generatePrivate(keySpec);
	    return privKey;
	}


	public static RSAPublicKey getPublicKey(String filename) throws IOException, GeneralSecurityException {
	    String publicKeyPEM = getKey(filename);
	    return getPublicKeyFromString(publicKeyPEM);
	}

	public static RSAPublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException {
	    String publicKeyPEM = key;
	    publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----", "");
	    publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
	    byte[] encoded = Base64.decodeBase64(publicKeyPEM);
	    KeyFactory kf = KeyFactory.getInstance("RSA");
	    RSAPublicKey pubKey = (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
	    return pubKey;
	}

	public static String sign(PrivateKey privateKey, String message) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
	    Signature sign = Signature.getInstance("SHA1withRSA");
	    sign.initSign(privateKey);
	    sign.update(message.getBytes("UTF-8"));
	    return new String(Base64.encodeBase64(sign.sign()), "UTF-8");
	}


	public static boolean verify(PublicKey publicKey, String message, String signature) throws SignatureException, NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
	    Signature sign = Signature.getInstance("SHA1withRSA");
	    sign.initVerify(publicKey);
	    sign.update(message.getBytes("UTF-8"));
	    return sign.verify(Base64.decodeBase64(signature.getBytes("UTF-8")));
	}

	public static String encrypt(String rawText, PublicKey publicKey) throws IOException, GeneralSecurityException {
	    Cipher cipher = Cipher.getInstance("RSA");
	    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
	    return Base64.encodeBase64String(cipher.doFinal(rawText.getBytes("UTF-8")));
	}

	public static String decrypt(String cipherText, PrivateKey privateKey) throws IOException, GeneralSecurityException {
	    Cipher cipher = Cipher.getInstance("RSA");
	    cipher.init(Cipher.DECRYPT_MODE, privateKey);
	    return new String(cipher.doFinal(Base64.decodeBase64(cipherText)), "UTF-8");
	}
	
}

연동 키정보 저장

- com.naverworks.message.model.NaverWorksInfo.java

package com.naverworks.message.model;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import lombok.Data;

@Data
@Component
public class NaverWorksInfo {

	@Value("${naverworks.api_id}")
	private String apiId;
	
	@Value("${naverworks.server_consumer_key}")
	private String serverConsumerKey;
	
	@Value("${naverworks.server_id}")
	private String serverId;

	@Value("${naverworks.server_private_key}")
	private String serverPrivateKey;
	
	@Value("${naverworks.url_auth}")
	private String urlAuth;
	
	@Value("${naverworks.url_api}")
	private String urlApi;
	
}

메시지 전송 데이타 저장

- com.naverworks.message.model.Message.java

package com.naverworks.message.model;

import java.util.List;

import lombok.Data;

@Data
public class Message {

	private String botNo;
	private String text;
	private List<String> targets;
	
}

서버 Token 발급 서비스

- com.naverworks.message.service.NaverWorksTokenService.java

- 참고 : https://developers.worksmobile.com/kr/document/4002002?lang=ko

package com.naverworks.message.service;

import java.net.URI;
import java.net.URLEncoder;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.naverworks.message.model.NaverWorksInfo;
import com.naverworks.message.util.RSAUtils;

import lombok.extern.log4j.Log4j2;

@Log4j2
@Service
public class NaverWorksTokenService {

	@Autowired
	private NaverWorksInfo naverWorksInfo;
	
	public String getServerToken() throws Exception {
		
		Map<String, Object> headers = new HashMap<String, Object>();
		headers.put("alg", "RS256");
		headers.put("typ", "JWT");
		Date iat = new Date();
		Date exp = DateUtils.addMinutes(new Date(), 30);
		String grantType = URLEncoder.encode("urn:ietf:params:oauth:grant-type:jwt-bearer");
		
		//RSA
		RSAPublicKey publicKey = null;//Get the key instance
		RSAPrivateKey privateKey = RSAUtils.getPrivateKey(naverWorksInfo.getServerPrivateKey());
		Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);
		
		String assertion = JWT.create()
								.withHeader(headers)
								.withIssuer(naverWorksInfo.getServerId())
								.withIssuedAt(iat)
								.withExpiresAt(exp)
								.sign(algorithmRS);
		
		// Parameter 셋팅 
		List<NameValuePair> params = new ArrayList<NameValuePair>(); 
		params.add(new BasicNameValuePair("assertion", assertion));
		params.add(new BasicNameValuePair("grant_type", grantType));
		
		// 호출 URL 셋팅
		URI uri = new URI(naverWorksInfo.getUrlAuth());
		URIBuilder ub = new URIBuilder(uri);
		ub.setPath("/b/"+naverWorksInfo.getApiId()+"/server/token");
		
		String contentType = "application/x-www-form-urlencoded";
		
		//전송 준비 
		HttpClient httpClient = HttpClientBuilder.create().build();
		
		//post로 호출 하기 위해 준
		HttpPost http = new HttpPost(ub.toString());
		http.addHeader("Content-Type", contentType);
		http.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
		log.info("### request => " + http.toString() + " , header[Content-Type=" + contentType + "] , content" + params.toString());
		
		//호출 
		HttpResponse response = httpClient.execute(http);
		log.info("### response => " + response.toString());
		
		//리턴 데이타 받아옴 
		HttpEntity entity = response.getEntity();
		String responseStr = EntityUtils.toString(entity);
		log.info("### response responseStr => " + responseStr);
		
		Map<String, Object> responseMap = new ObjectMapper().readValue(responseStr, Map.class);
		log.info("### response responseMap => " + responseMap.toString());
		
		if(responseMap.containsKey("access_token")) return responseMap.get("access_token")+"";
		else return null;
		
	}
}

메시지 전송 서비스

- com.naverworks.message.service.MessageService.java

- 참고 : https://developers.worksmobile.com/kr/document/1005008?lang=ko

package com.naverworks.message.service;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.naverworks.message.model.Message;
import com.naverworks.message.model.NaverWorksInfo;

import lombok.extern.log4j.Log4j2;

@Log4j2
@Service
public class MessageService {

	@Autowired
	private NaverWorksInfo naverWorksInfo;
	
	@Autowired
	private NaverWorksTokenService naverWorksTokenService;
	
	public void sendMessage(Message message) throws Exception {
		 
		String token = naverWorksTokenService.getServerToken();
		Map<String, Object> params = new HashMap<String, Object>();
		Map<String, String> content = new HashMap<String, String>();
		
		for( String target : message.getTargets() ) {
			params.put("content", content);
			params.put("accountId", target);
			content.put("type", "text");
			content.put("text", message.getText());
			
			
			// 호출 URL 셋팅
			URI uri = new URI(naverWorksInfo.getUrlApi());
			URIBuilder ub = new URIBuilder(uri);
			ub.setPath("/r/"+naverWorksInfo.getApiId()+"/message/v1/bot/"+message.getBotNo()+"/message/push");
			
			String contentType = "application/json";
			
			//전송 준비 
			HttpClient httpClient = HttpClientBuilder.create().build();
			
			//post로 호출 하기 위해 준
			HttpPost http = new HttpPost(ub.toString());
			http.addHeader("consumerKey", naverWorksInfo.getServerConsumerKey());
			http.addHeader("Authorization", "Bearer " + token);
			http.addHeader("Accept", contentType);
			http.addHeader("Content-Type", contentType);
			http.setEntity(new StringEntity(new ObjectMapper().writeValueAsString(params)));
			log.info("### request => " + http.toString() + " , header[Content-Type=" + contentType + "] , content" + params.toString());
			
			//호출 
			HttpResponse response = httpClient.execute(http);
			log.info("### response => " + response.toString());
			
			//200이면 성공
			log.info("### response StatusCode => " + response.getStatusLine().getStatusCode());
			
			//200이 아닐 경우 리턴 데이타 받아옴 
			HttpEntity entity = response.getEntity();
			String responseStr = EntityUtils.toString(entity);
			log.info("### response responseStr => " + responseStr);
			
		}
		
		
	}
	
}

메시지 데이타 입력 받을 컨트롤러

- com.naverworks.message.controller.MessageRestController.java

- 입력 값

    > Request-Url : http://localhost:8080/message/sendMessage

    > Method : post

    > Content-type : application/json

    > Body-Entity : {"botNo":"2153041","text":"Hi~!!","targets":["loveleejs1@jongsuk"]}

package com.naverworks.message.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.naverworks.message.model.Message;
import com.naverworks.message.service.MessageService;

import lombok.extern.log4j.Log4j2;

@Log4j2
@RequestMapping("/message")
@RestController
public class MessageRestController {

	@Autowired
	private MessageService messageService;
	
	@PostMapping("/sendMessage")
	public void sendMessage(@RequestBody Message message) {
		
		try {
			messageService.sendMessage(message);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		
	}
	
}

호출 테스트

postman 을 이용해서 테스트해 봤다.

네이버웍스 api 예제 - neibeowogseu api yeje

호출 결과

로그 결과

- 실패

2021-05-19 22:01:31.835  INFO 5704 --- [nio-8080-exec-1] c.n.m.service.NaverWorksTokenService     : ### request => POST https://auth.worksmobile.com/b/kr1wDYVCnaPCE/server/token HTTP/1.1 , header[Content-Type=application/x-www-form-urlencoded] , content[assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI1NWIxNmNlM2Q3NzQ0YzE5YjFlNjJkYzVlM2ZmODM0OSIsImV4cCI6MTYyMTQzMTA5MSwiaWF0IjoxNjIxNDI5MjkxfQ.LXk2J7lq9nDRlU_Uyw-xeLfTHHa87op4ghJh6thAAwVktiJdb7ucr334TCMDf360Tj31p2oVI6e06XHCXf1_4OThkuntgo1nnWLSykVwyoZidlij72v9Tk2BQsXXkAwTYkfm7G7wJzPsckOqYywMKhUatjz0Unh2aMxW3NMaSqLmFNM3g1XI5MEwqAhzmcY1nrACG5l_Z2L8WL1aomWHHhNskTGUdHU_c_En9byl-oMSeY017CLnNXNMmlX0d7WUgp3Z3n9l97WZRjI6IDl-aBO5D9LbVlZ6mD_K55KOikwCnAjVLbuxoCKvGJ49QVmNdootiGzj6jnczRonxP3WkQ, grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer]
2021-05-19 22:01:31.971  INFO 5704 --- [nio-8080-exec-1] c.n.m.service.NaverWorksTokenService     : ### response => HttpResponseProxy{HTTP/1.1 200 OK [Date: Wed, 19 May 2021 13:01:32 GMT, Content-Type: application/json;charset=UTF-8, Transfer-Encoding: chunked, Connection: keep-alive, Cache-Control: no-store, Content-Language: en-US] ResponseEntityProxy{[Content-Type: application/json;charset=UTF-8,Chunked: true]}}
2021-05-19 22:01:31.972  INFO 5704 --- [nio-8080-exec-1] c.n.m.service.NaverWorksTokenService     : ### response responseStr => {"access_token":"AAABAZNVLDOjHVvZxDExSkR9P3fps++c2VydmVySWQ+JPnpBZ8QrYCBJMijN5tCLQg+4chiMfTvOxf3GP3lBnPbUsafmYqYQtJmTwCAKfZyBkXC/joINbt+im1VCdkCY/ryC90YnM6ZEBInvrwR/ohFjbRQt0hEEIqBSbN8WgtvsXR62qXqZeOkr9dnU5wjhpfZWYiRaPYITR30f7ivAH+TlM7J5y7VnoBMCdVj79H4RM3U7FgvZ8A//eK2LGlnkSWnBBe3o0Q3cyXT0D9o9XOA+m5Dr3eE8TnBlYhYvyxbj1xzt1m/bex78ezv+Y0QDULyam+vovrfzbZ3ZWSQboQcl3tq1q020cuKBiYnmO9xQJCapVTyBMNZumvqXW","token_type":"Bearer","expires_in":86400}
2021-05-19 22:01:31.972  INFO 5704 --- [nio-8080-exec-1] c.n.m.service.NaverWorksTokenService     : ### response responseMap => {access_token=AAABAZNVLDOjHVvZxDExSkR9P3fps++c2VydmVySWQ+JPnpBZ8QrYCBJMijN5tCLQg+4chiMfTvOxf3GP3lBnPbUsafmYqYQtJmTwCAKfZyBkXC/joINbt+im1VCdkCY/ryC90YnM6ZEBInvrwR/ohFjbRQt0hEEIqBSbN8WgtvsXR62qXqZeOkr9dnU5wjhpfZWYiRaPYITR30f7ivAH+TlM7J5y7VnoBMCdVj79H4RM3U7FgvZ8A//eK2LGlnkSWnBBe3o0Q3cyXT0D9o9XOA+m5Dr3eE8TnBlYhYvyxbj1xzt1m/bex78ezv+Y0QDULyam+vovrfzbZ3ZWSQboQcl3tq1q020cuKBiYnmO9xQJCapVTyBMNZumvqXW, token_type=Bearer, expires_in=86400}
2021-05-19 22:01:31.974  INFO 5704 --- [nio-8080-exec-1] c.n.message.service.MessageService       : ### request => POST https://apis.worksmobile.com/r/kr1wDYVCnaPCE/message/v1/bot/2153041/message/push HTTP/1.1 , header[Content-Type=application/json] , content{accountId=loveleejs@jongsuk, content={text=Hi~!!, type=text}}
2021-05-19 22:01:32.091  INFO 5704 --- [nio-8080-exec-1] c.n.message.service.MessageService       : ### response => HttpResponseProxy{HTTP/1.1 400  [Date: Wed, 19 May 2021 13:01:32 GMT, Content-Type: application/json, Transfer-Encoding: chunked, Connection: keep-alive] ResponseEntityProxy{[Content-Type: application/json,Chunked: true]}}
2021-05-19 22:01:32.091  INFO 5704 --- [nio-8080-exec-1] c.n.message.service.MessageService       : ### response StatusCode => 400
2021-05-19 22:01:32.091  INFO 5704 --- [nio-8080-exec-1] c.n.message.service.MessageService       : ### response responseStr => {"message":"Bad Request Parameters: Can not find \"loveleejs@jongsuk\"","code":"BAD_REQUEST","domain":"message"}

- 성공

2021-05-19 22:03:48.496  INFO 5704 --- [nio-8080-exec-3] c.n.m.service.NaverWorksTokenService     : ### request => POST https://auth.worksmobile.com/b/kr1wDYVCnaPCE/server/token HTTP/1.1 , header[Content-Type=application/x-www-form-urlencoded] , content[assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI1NWIxNmNlM2Q3NzQ0YzE5YjFlNjJkYzVlM2ZmODM0OSIsImV4cCI6MTYyMTQzMTIyOCwiaWF0IjoxNjIxNDI5NDI4fQ.TLBzZkZoGTMqEoGHvwjWqH4evPHoQUQQ72JKivqpLEQXe9UR8NDyaHecsqosu92QADWAOcZn0VWoO-YaUx2VTNZSiWGo8MHdsfudRaE3H-5kacr0NtefFVeJEEZESRcBT_KZQMwV7FhjTZ52TxsM6zniyxksr0hBq8CU5ILT3F0_5tjE5w3Fbtm6LHOFHSfM3j9HEiBUmjqecjNhaI5ii1JCMwfgfqpEJQUvdxS-zUeGGxY7qoRnnst7-9giSkyEuGzXegOvxDDSWjxPPwRVadRLFrbIySXnIF4xOxstZ2l0fEZq3hhgYWm7Ky3_Dx2vHNzPa325RK-WP568Q1wPrQ, grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer]
2021-05-19 22:03:48.633  INFO 5704 --- [nio-8080-exec-3] c.n.m.service.NaverWorksTokenService     : ### response => HttpResponseProxy{HTTP/1.1 200 OK [Date: Wed, 19 May 2021 13:03:48 GMT, Content-Type: application/json;charset=UTF-8, Transfer-Encoding: chunked, Connection: keep-alive, Cache-Control: no-store, Content-Language: en-US] ResponseEntityProxy{[Content-Type: application/json;charset=UTF-8,Chunked: true]}}
2021-05-19 22:03:48.633  INFO 5704 --- [nio-8080-exec-3] c.n.m.service.NaverWorksTokenService     : ### response responseStr => {"access_token":"AAABBbJcUFt7i7/1M0vF3S4EMj7GTF+c2VydmVySWQ+9T1u5eDlzVlerBkMRPXrQstQ46qmsII1EgrnoDPExBfCbDpndIVDJP2r1jAv6koPIJcsA+ctICDwvHvMjmAX7L9Fv5XW+LZ+40bVqEyRA3wgzIulGQcP1s+Qi34yCgC8SX6uzVq9xUmYfoT+c5SnHEnAS2ppODcTSortHzyiXhIJzD2HFOXdaOrj3rwSewqjIO6UP7ywJlX12QtPJkeOzkadYffNW5rVfFVyk8s65tfTVfZ7gskPUtNnySRt42yz62GvmZ9GATe4bQDlBpDUeTolOiWJQKrGOGuIt5Tu7W56Rl5lG9vUjwbQBJm4t/XX8LofJ5ST8orXhM60q0","token_type":"Bearer","expires_in":86400}
2021-05-19 22:03:48.633  INFO 5704 --- [nio-8080-exec-3] c.n.m.service.NaverWorksTokenService     : ### response responseMap => {access_token=AAABBbJcUFt7i7/1M0vF3S4EMj7GTF+c2VydmVySWQ+9T1u5eDlzVlerBkMRPXrQstQ46qmsII1EgrnoDPExBfCbDpndIVDJP2r1jAv6koPIJcsA+ctICDwvHvMjmAX7L9Fv5XW+LZ+40bVqEyRA3wgzIulGQcP1s+Qi34yCgC8SX6uzVq9xUmYfoT+c5SnHEnAS2ppODcTSortHzyiXhIJzD2HFOXdaOrj3rwSewqjIO6UP7ywJlX12QtPJkeOzkadYffNW5rVfFVyk8s65tfTVfZ7gskPUtNnySRt42yz62GvmZ9GATe4bQDlBpDUeTolOiWJQKrGOGuIt5Tu7W56Rl5lG9vUjwbQBJm4t/XX8LofJ5ST8orXhM60q0, token_type=Bearer, expires_in=86400}
2021-05-19 22:03:48.634  INFO 5704 --- [nio-8080-exec-3] c.n.message.service.MessageService       : ### request => POST https://apis.worksmobile.com/r/kr1wDYVCnaPCE/message/v1/bot/2153041/message/push HTTP/1.1 , header[Content-Type=application/json] , content{accountId=loveleejs1@jongsuk, content={text=Hi~!!, type=text}}
2021-05-19 22:03:48.784  INFO 5704 --- [nio-8080-exec-3] c.n.message.service.MessageService       : ### response => HttpResponseProxy{HTTP/1.1 200  [Date: Wed, 19 May 2021 13:03:48 GMT, Content-Type: application/json, Transfer-Encoding: chunked, Connection: keep-alive, RateLimit-Limit: 1, RateLimit-Remaining: 59, RateLimit-Reset: 12] ResponseEntityProxy{[Content-Type: application/json,Chunked: true]}}
2021-05-19 22:03:48.785  INFO 5704 --- [nio-8080-exec-3] c.n.message.service.MessageService       : ### response StatusCode => 200
2021-05-19 22:03:48.785  INFO 5704 --- [nio-8080-exec-3] c.n.message.service.MessageService       : ### response responseStr => ""

네이버웍스 PC 앱에서 결과 확인

네이버웍스 api 예제 - neibeowogseu api yeje