2편에서 http client로 NCP 알림톡을 발송했다면 이번엔 feign를 활용해 알림톡을 발송하는 방법과 코드 예시를 소개할 예정이다.

 

기존 http client 코드를 활용하려고 했더니 feign과 안맞는 부분이 있어 해당 부분들을 수정해 코드를 작성했다.

원래는 jsonobject를 만들어 바디 값을 담았다면 feign에서는 dto를 만들어 바디 값을 담았고 이때 ncp에서 요구하는 형식에 맞게 바디 값 구조를 그대로 직관적으로 맞춰 설정하는게 중요했다.

또한, serviceId를 환경 변수로 받아와 해당 주소로 api 요청을 보낼때 ':'이 다른 값으로 자동 인코딩되며 이를 ncp에서 그대로 인식하고 오류가 발생했다. 구글링을 통해 ColonInterceptor을 만들어 해결했다.


 

먼저 흐름을 크게 보면 open feign 관련 의존성 추가와 공통 설정을 프로젝트에 해주고 ncp 알림톡 관련해 필요한 설정들을 따로 더 추가한다.

설정들을 끝냈으면 필요한 정보들을 담아 @FeignClient 인터페이스를 활용해 api 요청을 보내면 된다.

필자는 필요한 정보들을 담는 용도로 helper를 사용해 정보들을 세팅했다.

 

공식 문서 링크는 아래와 같다.

https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/

 

Spring Cloud OpenFeign

Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable

docs.spring.io

 

코드 예시

 

1. 의존성 추가

feign을 사용하기 위한 최소한의 의존성은 아래와 같다.

* Maven
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>3.1.4</version>
</dependency>

* Gradle
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '3.1.4'

필자의 프로젝트에 들어간 의존성은 아래와 같다.

dependencies {
    api 'io.github.openfeign:feign-httpclient:12.1'
    api 'org.springframework.cloud:spring-cloud-starter-openfeign:3.1.4'
    api 'io.github.openfeign:feign-jackson:12.1'
}

 

2. 공통 설정

public interface BaseFeignClientPackage {}
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import feign.Logger.Level;
import feign.codec.Decoder;
import feign.jackson.JacksonDecoder;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableFeignClients(basePackageClasses = BaseFeignClientPackage.class)
public class FeignCommonConfig {
    @Bean
    public Decoder feignDecoder() {
        return new JacksonDecoder(customObjectMapper());
    }

    @Bean
    Level feignLoggerLevel() {
        return Level.FULL;
    }
}

 

3. ncp 관련 설정

에러 디코더 커스터마이징

(각자의 프로젝트에 맞게 에러 처리 넘겨주시면 됩니다!)

import feign.FeignException;
import feign.Response;
import feign.codec.ErrorDecoder;

public class NcpErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        if (response.status() >= 400) {
            switch (response.status()) {
                case 401 -> throw OtherServerUnauthorizedException.EXCEPTION;
                case 403 -> throw OtherServerForbiddenException.EXCEPTION;
                case 404 -> throw OtherServerNotFoundException.EXCEPTION;
                case 500 -> throw OtherServerInternalSeverErrorException.EXCEPTION;
                default -> throw OtherServerBadRequestException.EXCEPTION;
            }
        }

        return FeignException.errorStatus(methodKey, response);
    }
}

 

import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.codec.ErrorDecoder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.*;

@Import(NcpErrorDecoder.class)
public class NcpConfig {
    @Bean
    @ConditionalOnMissingBean(value = ErrorDecoder.class)
    public NcpErrorDecoder commonFeignErrorDecoder() {
        return new NcpErrorDecoder();
    }

    @Bean
    public RequestInterceptor basicAuthRequestInterceptor() {
        return new ColonInterceptor();
    }

    public static class ColonInterceptor implements RequestInterceptor {
        @Override
        public void apply(RequestTemplate template) {
            template.uri(template.path().replaceAll("%3A", ":"));
        }
    }
}

 

4. 알림톡 발송 코드 작성

body 값 담는 dto 생성

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

public class MessageDto {

    @Getter
    @Builder
    public static class AlimTalkBody {
        private String plusFriendId;
        private String templateCode;
        private List<AlimTalkMessage> messages;
    }

    @Getter
    @Builder
    @AllArgsConstructor
    public static class AlimTalkMessage {
        private String to;
        private String content;
    }
}

api 요청 정보들 세팅하는 helper 작성

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;

@Helper
public class NcpHelper {
    private final NcpClient ncpClient;
    private final String serviceID;
    private final String ncpAccessKey;
    private final String ncpSecretKey;
    private final String plusFriendId;

    public NcpHelper(
            NcpClient ncpClient,
            @Value("${ncp.service-id}") String serviceID,
            @Value("${ncp.access-key}") String ncpAccessKey,
            @Value("${ncp.secret-key}") String ncpSecretKey,
            @Value("${ncp.plus-friend-id}") String plusFriendId) {
        this.ncpClient = ncpClient;
        this.serviceID = serviceID;
        this.ncpAccessKey = ncpAccessKey;
        this.ncpSecretKey = ncpSecretKey;
        this.plusFriendId = plusFriendId;
    }

    public void sendNcpAlimTalk(String to, String templateCode, String content) {
        // signature 생성
        String alimTalkSignatureRequestUrl = "/alimtalk/v2/services/" + serviceID + "/messages";
        String[] signatureArray =
                makePostSignature(ncpAccessKey, ncpSecretKey, alimTalkSignatureRequestUrl);
        // 바디 생성
        MessageDto.AlimTalkBody body = makeBody(templateCode, to, content);
        ncpClient.sendAlimTalk(
                serviceID,
                "application/json; charset=UTF-8",
                ncpAccessKey,
                signatureArray[0],
                signatureArray[1],
                body);
    }

    public MessageDto.AlimTalkBody makeBody(String templateCode, String to, String content) {
        MessageDto.AlimTalkMessage alimTalkMessage =
                MessageDto.AlimTalkMessage.builder().to(to).content(content).build();
        List<MessageDto.AlimTalkMessage> alimTalkMessages = new ArrayList<>();
        alimTalkMessages.add(alimTalkMessage);

        return MessageDto.AlimTalkBody.builder()
                .plusFriendId(plusFriendId)
                .templateCode(templateCode)
                .messages(alimTalkMessages)
                .build();
    }

    public String[] makePostSignature(String accessKey, String secretKey, String url) {
        String[] result = new String[2];
        try {

            String timeStamp =
                    String.valueOf(Instant.now().toEpochMilli()); // current timestamp (epoch)
            String space = " "; // space
            String newLine = "\n"; // new line
            String method = "POST"; // method

            String message =
                    new StringBuilder()
                            .append(method)
                            .append(space)
                            .append(url)
                            .append(newLine)
                            .append(timeStamp)
                            .append(newLine)
                            .append(accessKey)
                            .toString();

            SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(signingKey);

            byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
            String encodeBase64String = Base64.encodeBase64String(rawHmac);

            result[0] = timeStamp;
            result[1] = encodeBase64String;

        } catch (Exception ex) {
            throw new DuDoongDynamicException(0, "400", ex.getMessage());
        }
        return result;
    }
}

feign client 생성

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;

@FeignClient(
        name = "NcpClient",
        url = "https://sens.apigw.ntruss.com",
        configuration = NcpConfig.class)
public interface NcpClient {
    @PostMapping(
            path = "/alimtalk/v2/services/{serviceId}/messages",
            consumes = "application/json; charset=UTF-8")
    void sendAlimTalk(
            @PathVariable("serviceId") String serviceId,
            @RequestHeader("Content-Type") String contentType,
            @RequestHeader("x-ncp-iam-access-key") String ncpAccessKey,
            @RequestHeader("x-ncp-apigw-timestamp") String timeStamp,
            @RequestHeader("x-ncp-apigw-signature-v2") String signature,
            @RequestBody MessageDto.AlimTalkBody alimTalkBody);
}

 

참고 사이트

https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/

https://techblog.woowahan.com/2630/

https://vmpo.tistory.com/109

https://velog.io/@haron/Feign-client-%EC%A0%81%EC%9A%A9%EA%B8%B0

https://wildeveloperetrain.tistory.com/172

https://forkyy.tistory.com/10

https://bepoz-study-diary.tistory.com/414

https://stackoverflow.com/questions/40262132/how-to-add-a-request-interceptor-to-a-feign-client

https://stackoverflow.com/questions/61830167/disable-feign-encoding-of-pathvariables

https://github.com/Youngiyong/gofield-backend/blob/d05a2850fe15d22e987e4e7b26d1d9a3c551403c/gofield-infrastructure/gofield-infrastructure-external/src/main/java/com/gofield/infrastructure/external/api/naver/NaverSnsApiClient.java

https://github.com/kakakoo/tsid-core/blob/ffff29fe6e61a25d1f624d50e8ac91ea585c239d/tsid-external/src/main/java/com/tsid/external/api/naver/dto/req/NaverSmsRequest.java

 

이번 편에서는 1편에서 세팅한 내용들을 바탕으로 스프링 부트 프로젝트에서 알림톡을 발송 코드 작성을 다룹니다.


NCP 알림톡 API 가이드

https://api.ncloud-docs.com/docs/ai-application-service-sens-alimtalkv2

 

알림톡 API

 

api.ncloud-docs.com

공식 docs 링크는 위와 같으며 위 문서를 참고하여 작성했습니다!

 

1. 메시지 발송 API 기본 정보

얼핏보면 내용이 많아 복잡해보일 수 있지만 해당 요청 주소로 필수로 요구하는 정보들을 채워 보내면 됩니다!

아래는 필요한 정보들을 정리한 것으로 마지막에 코드 예시로도 보여드리겠습니다.

 

요청 URL

POST https://sens.apigw.ntruss.com/alimtalk/v2/services/{serviceId}/messages

필수 path variables

serviceId String

 

필수 API header 

Content-Type 요청 Body Content Type을 application/json으로 지정 (POST)
x-ncp-apigw-timestamp - 1970년 1월 1일 00:00:00 협정 세계시(UTC)부터의 경과 시간을 밀리초(Millisecond)로 나타냄
- API Gateway 서버와 시간 차가 5분 이상 나는 경우 유효하지 않은 요청으로 간주
x-ncp-iam-access-key 포탈 또는 Sub Account에서 발급받은 Access Key ID
x-ncp-apigw-signature-v2 - 위 예제의 Body를 Access Key Id와 맵핑되는 SecretKey로 암호화한 서명
- HMAC 암호화 알고리즘은 HmacSHA256 사용

요청 body (필수+옵션)

{
    "plusFriendId":"string",
    "templateCode":"string",
    "messages":[
        {
            "countryCode":"string",
            "to":"string",
            "title":"string",
            "content":"string",
            "headerContent":"string",
            "itemHighlight":{
                "title":"string",
                "description":"string"
            },
            "item":{
                "list":[
                    {
                        "title":"string",
                        "description":"string"
                    }
                ],
                "summary":{
                    "title":"string",
                    "description":"string"
                }
            },
            "buttons":[
                {
                    "type":"string",
                    "name":"string",
                    "linkMobile":"string",
                    "linkPc":"string",
                    "schemeIos":"string",
                    "schemeAndroid":"string"
                }
            ],
            "useSmsFailover": "boolean",
            "failoverConfig": {
                "type": "string",
                "from": "string",
                "subject": "string",
                "content": "string"
            }
        }
    ],
    "reserveTime": "yyyy-MM-dd HH:mm",
    "reserveTimeZone": "string",
    "scheduleCode": "string"
}

 

plusFriendId Mandatory  String 카카오톡 채널명
((구)플러스친구 아이디)
 
templateCode Mandatory String 템플릿 코드  
messages Mandatory Object 메시지 정보 - 아래 항목 참조 (messages.XXX)
- 최대 100개
messages.countryCode Optional String 수신자 국가번호  
messages.to Mandatory String 수신자번호  
messages.title Optional String 알림톡 강조표시 내용 강조 표기 유형의 템플릿에서만 사용 가능
messages.content Mandatory String 알림톡 메시지 내용  
messages.headerContent Optional String 알림톡 헤더 내용 - 아이템 리스트 유형의 템플릿에서만 사용 가능
- 최대 16자까지 입력 가능
messages.itemHighlight Optional Object 아이템 하이라이트 아이템 리스트 유형의 템플릿에서만 사용 가능
messages.itemHighlight.title Mandatory String 아이템 하이라이트 제목 - 아이템 리스트 유형의 템플릿에서만 사용 가능
이미지가 없는 경우
- 최대 30자까지 입력 가능 (2줄)
- 1줄은 15자까지 입력 가능
이미지가 있는 경우
- 최대 21자까지 입력 가능 (2줄)
- 1줄은 10자까지 입력 가능
- 2줄 초과 시 말줄임 처리
messages.itemHighlight.description Mandatory String 아이템 하이라이트 설명 - 아이템 리스트 유형의 템플릿에서만 사용 가능
이미지가 없는 경우
- 최대 19자까지 입력 가능 (1줄)
이미지가 있는 경우
- 최대 13자까지 입력 가능 (1줄)
- 1줄 초과 시 말줄임 처리
messages.item Optional Object 아이템 리스트 아이템리스트 유형의 템플릿에서만 사용 가능
messages.messages.item.list Mandatory Array of Object 아이템 리스트 - 아이템리스트 유형의 템플릿에서만 사용 가능
- 최소 2개 이상, 최대 10개
messages.messages.item.list.title Mandatory String 아이템 리스트 제목 - 아이템리스트 유형의 템플릿에서만 사용 가능
- 최대 6자까지 입력 가능
messages.messages.item.list.description Mandatory String 아이템 리스트 설명 - 아이템리스트 유형의 템플릿에서만 사용 가능
- 최대 23자까지 입력 가능
messages.messages.summary Optional Object 아이템 요약 정보 아이템리스트 유형의 템플릿에서만 사용 가능
messages.messages.summary.title Mandatory String 아이템 요약 제목 - 아이템리스트 유형의 템플릿에서만 사용 가능
- 최대 6자까지 입력 가능
messages.messages.summary.description Mandatory String 아이템 요약 설명 - 아이템리스트 유형의 템플릿에서만 사용 가능
- 허용되는 문자: 통화기호(유니코드 통화기호, 元, 円, 원), 통화코드 (ISO 4217), 숫자, 콤마, 소수점, 공백
- 소수점 2자리까지 허용
- 최대 23자까지 입력 가능
messages.buttons Optional Array of Object 알림톡 메시지 버튼 아래 템플릿 버튼 정보 참조
messages.buttons.type Mandatory String 버튼 Type 아래 템플릿 버튼 정보 참조
messages.buttons.name Mandatory String 버튼명 아래 템플릿 버튼 정보 참조
messages.useSmsFailover Optional Boolean SMS Failover 사용 여부 - Failover가 설정된 카카오톡 채널에서만 사용 가능
- 기본: 카카오톡 채널의 Failover 설정 여부를 따름
messages.failoverConfig Optional Object Failover 설정 아래 항목 참조
messages.failoverConfig.type Optional String Failover SMS 메시지 Type - SMS 또는 LMS
- 기본: content 길이에 따라 자동 적용 (90 bytes 이하 SMS, 초과 LMS)
messages.failoverConfig.from Optional String Failover SMS 발신번호 - 기본: Failover 설정 시 선택한 발신번호
- 승인되지 않은 발신번호 사용시 Failover 동작 안함
messages.failoverConfig.subject Optional String Failover SMS 제목 - LMS type으로 동작할 때 사용
- 기본: 카카오톡 채널명
messages.failoverConfig.content Optional String Failover SMS 내용 기본: 알림톡 메시지 내용 (버튼 제외)
reserveTime Optional String 예약 일시 메시지 발송 예약 일시 (yyyy-MM-dd HH:mm)
reserveTimeZone Optional String 예약 일시 타임존 - 예약 일시 타임존 (기본: Asia/Seoul)
- 지원 타임존 목록
* TZ database name 값 사용
scheduleCode Optional String 스케줄 코드 등록하려는 스케줄 코드

응답 status

202 Accepted (발송 요청 완료)
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error

응답 body

{
    "requestId":"string",
    "requestTime":"string",
    "statusCode":"string",
    "statusName":"string",
    "messages":[
        {
            "messageId":"string",
            "countryCode":"string",
            "to":"string",
            "content":"string",
            "requestStatusCode":"string",
            "requestStatusName":"string",
            "requestStatusDesc":"string",
            "useSmsFailover":"boolean"
        }
    ]
}
requestId Mandatory String 발송 요청 아이디  
requestTime Mandatory DateTime 발송 요청 시간  
statusCode Mandatory String 요청 상태 코드 - 성공: 202
- 실패: 그 외
- HTTP Status 규격을 따름
statusName Mandatory String 요청 상태명 - 성공: success
- 처리 중: processing
- 예약 중: reserved
- 스케줄 중: scheduled
- 실패: fail
messages.messageId Mandatory String 메시지 아이디  
messages.countryCode Optional String 수신자 국가번호  
messages.to Mandatory String 수신자 번호  
messages.content Mandatory String 알림톡 메시지 내용  
messages.requestStatusCode Mandatory String 발송요청 상태 코드 - 성공: A000
- 실패: 그 외 코드(Desc 항목에 실패 사유가 명시)
messages.requestStatusName Mandatory String 발송 요청 상태명 - 성공: success
- 실패: fail
messages.requestStatusDesc Mandatory String 발송 요청 상태
내용
 
messages.useSmsFailover Mandatory Boolean SMS Failover
사용 여부

2. 메시지 발송 코드 작성 예시

크게 4가지 단계로 나눠볼 수 있습니다.

(1) 서명 생성

(2) 헤더 생성 

(3) 바디 생성

(4) api 요청

 

전체 코드 예시

변수들 설명 및 유의 사항

plusFriendId: 카톡플러스친구채널 id. @를 붙여서 넣어주세요.

                        ex) @chaech2929

to: 알림톡을 보낼 전화번호 String 타입

templateCode: 알림톡 템플릿 등록 시 설정했던 식별 코드

content: 알림톡 템플릿 등록 시 설정했던 글 내용과 동일하게 넣어주세요. 변수 값은 코드에서 사용하던 변수에 맞춰 알맞은 걸로 채워서                  넣어주시면 됩니다.

                ex) String content = userName + "님, 두둥에 가입하신 것을 환영합니다!\n"

 

signature 생성 시 사용하는 timestamp 값과 api 요청시 헤더에 넣는 timestamp 값이 동일해야합니다.

 

가장 기본 옵션으로만 이루어진 메시지를 기준으로 작성했습니다. 

package band.gosrock.infrastructure.config.AlilmTalk;


import java.io.IOException;
import java.sql.Timestamp;
import java.time.Instant;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.configurationprocessor.json.JSONArray;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Component
@Configuration
public class AlimTalk {
    private final String serviceID;
    private final String ncpAccessKey;
    private final String ncpSecretKey;
    private final String plusFriendId;

    public AlimTalk(
            @Value("${ncp.service-id}") String serviceID,
            @Value("${ncp.access-key}") String ncpAccessKey,
            @Value("${ncp.secret-key}") String ncpSecretKey,
            @Value("${ncp.plus-friend-id}") String plusFriendId) {
        this.serviceID = serviceID;
        this.ncpAccessKey = ncpAccessKey;
        this.ncpSecretKey = ncpSecretKey;
        this.plusFriendId = plusFriendId;
    }

    public void sendAlimTalk(String to, String templateCode, String content) {
        String alimTalkSendRequestUrl =
                "https://sens.apigw.ntruss.com/alimtalk/v2/services/" + serviceID + "/messages";
        String alimTalkSignatureRequestUrl = "/alimtalk/v2/services/" + serviceID + "/messages";
        CloseableHttpClient httpClient = null;
        try {
            // signature 생성
            String[] signatureArray =
                    makePostSignature(ncpAccessKey, ncpSecretKey, alimTalkSignatureRequestUrl);

            // http 통신 객체 생성
            httpClient = HttpClients.createDefault(); // http client 생성
            HttpPost httpPost = new HttpPost(alimTalkSendRequestUrl); // post 메서드와 URL 설정

            // 헤더 설정
            httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");
            httpPost.setHeader("x-ncp-iam-access-key", ncpAccessKey);
            httpPost.setHeader("x-ncp-apigw-timestamp", signatureArray[0]);
            httpPost.setHeader("x-ncp-apigw-signature-v2", signatureArray[1]);

            // body 설정
            JSONObject msgObj = new JSONObject();
            msgObj.put("plusFriendId", plusFriendId);
            msgObj.put("templateCode", templateCode);

            JSONObject messages = new JSONObject();
            messages.put("to", to);
            messages.put("content", content);

            JSONArray messageArray = new JSONArray();
            messageArray.put(messages);
            msgObj.put("messages", messageArray);

            // api 전송 값 http 객체에 담기
            httpPost.setEntity(new StringEntity(msgObj.toString(), "UTF-8"));
            // api 호출
            CloseableHttpResponse httpResponse = httpClient.execute(httpPost);

            // 응답 결과
            String result = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
            System.out.println(result);

        } catch (Exception ex) {
            // TODO: 에러 처리
            ex.printStackTrace();
        } finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public String[] makePostSignature(String accessKey, String secretKey, String url) {
        String[] result = new String[2];
        try {
            String timeStamp = String.valueOf(Instant.now().toEpochMilli()); // current timestamp (epoch)
            String space = " "; // space
            String newLine = "\n"; // new line
            String method = "POST"; // method

            String message =
                    new StringBuilder()
                            .append(method)
                            .append(space)
                            .append(url)
                            .append(newLine)
                            .append(timeStamp)
                            .append(newLine)
                            .append(accessKey)
                            .toString();

            SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(signingKey);

            byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
            String encodeBase64String = Base64.encodeBase64String(rawHmac);

            result[0] = timeStamp;
            result[1] = encodeBase64String;

        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }

    public String[] makeGetSignature(String accessKey, String secretKey, String url) {
        String[] result = new String[2];
        try {
            String timeStamp = String.valueOf(Instant.now().toEpochMilli()); // current timestamp (epoch)
            String space = " "; // space
            String newLine = "\n"; // new line
            String method = "GET"; // method

            String message =
                    new StringBuilder()
                            .append(method)
                            .append(space)
                            .append(url)
                            .append(newLine)
                            .append(timeStamp)
                            .append(newLine)
                            .append(accessKey)
                            .toString();

            SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(signingKey);

            byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
            String encodeBase64String = Base64.encodeBase64String(rawHmac);

            result[0] = timeStamp;
            result[1] = encodeBase64String;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }
}

 

 

참고 사이트

https://api.ncloud-docs.com/docs/ai-application-service-sens-alimtalkv2

https://honeystorage.tistory.com/191

https://wildeveloperetrain.tistory.com/77

https://blog.naver.com/PostView.naver?blogId=n_cloudplatform&logNo=222475388473&parentCategoryNo=&categoryNo=12&viewDate=&isShowPopularPosts=false&from=postView 

+ Recent posts