Spring Boot 3 버전에서 AWS SNS를 통한 SMS 발송하기
📌 서론
AWS에서 SMS 메시지를 보내는 기능을 구현할 때 흔히 "AWS SMS"라고 언급하는 것은 실제로 AWS SNS(Simple Notification Service)의 SMS 발송 기능을 사용하는 것을 의미한다.
AWS에서는 SMS 메시지를 전송하기 위해 별도의 'SMSClient' 같은 클라이언트를 제공하지 않고, 이 기능은 SNS 서비스의 일부로 제공된다.
AWS SNS는 다양한 형태의 알림을 지원하는 서비스로, 이메일, SMS, HTTP/S 알림 등을 보낼 수 있다. 특히 회원가입, 암호 재설정, 2단계 인증과 같은 상황에서 사용자에게 SMS를 보내야 할 때 많이 활용된다.
AWS SMS 도입 이유
우리 프로젝트에서 회원가입할 때 휴대폰 번호를 입력받고 그 번호로 인증 번호를 보내는 기능을 구현하기 위해 AWS SMS를 이용했다. 서론에서도 적었다시피, AWS SMS는 AWS SNS를 통해 문자 메시지를 전송하는 서비스를 이용한 것이다.
Spring Boot 애플리케이션에서 AWS SNS를 이용해 SMS 메시지를 보내는 과정은 굉장히 간단하며, 몇 가지 기본 구성 요소만 필요하다.
여기에는 복잡한 SNS 주제(Topic) 설정이나 SQS와의 연동이 필요하지 않다.
한국 AWS SMS 비용
AWS SNS의 SMS 전송 요금은 대상 국가에 따라 다르며, 종량제 방식으로 청구된다.
스프링 부트에서 AWS SNS 클라이언트를 사용해 SMS 메시지를 발송하는 경우, 이는 '직접 메시지 전송(Direct Publish to Phone Number)' 방식에 해당하며, 특정 전화번호로 직접 SMS를 보내는 것이다. 이는 표준 주제에 메시지를 발행하고, 그 주제를 구독하는 구독자에게 메시지를 전달하는 것과는 구별된다.
Worldwide SMS Pricing 페이지에서 검색해 보면 한국의 비용은 아래와 같다. (아래 캡처는 2023년 12월 기준이다.)
문자 메시지 한 건당 0.02달러 정도가 조금 비싸게 느껴지긴 한다. 하지만 일단 우리 프로젝트에서 SMS 발송 기능은 사용하는 곳은 소셜 네트워크로 회원가입을 하는 게 아니라 이메일로 회원가입할 때 사용자가 입력한 휴대폰 번호로 인증 코드를 전송할 때만 사용된다.
소셜 네트워크를 통한 회원가입일 때는 이 기능을 사용하지 않기 때문에 문자 메시지를 보내는 기능의 사용자가 많을 것 같지 않아서 일단 AWS SMS를 도입했다.
혹시 몰라서 AWS SNS 모바일 메뉴에서 문자 메시지 기본 설정에 '계정 지출 한도'를 1달러로 지정해 놓긴 했다..
국내 SMS 발송 API를 사용해도 되지만 그 부분은 나중에 사용자가 많아지면 고려해 보기로 했다.
AWS SNS를 통한 SMS 발송의 기본 요구사항
IAM 계정 정보
스프링 부트에서 AWS SNS 서비스에 접근하기 위해 필요한 AWS Identity and Access Management(IAM) 계정의 Access Key와 Secret Key가 필요하다. 이는 AWS 리소스에 대한 액세스 권한을 부여한다.
리전 설정
AWS 서비스는 다양한 지리적 위치(리전)에 서비스를 제공한다. SMS 서비스를 사용하기 위해서는 해당 서비스를 사용할 AWS 리전을 지정해야 한다.
🔔 알림: 2023년 12월 기준으로 서울 리전에는 아직 AWS SNS의 SMS 발송 기능이 없다. 그래서 가장 가까운 "도쿄" 리전을 이용해서 SMS 전송을 진행했다.
주제 생성 불필요
SNS에는 topic(주제)를 생성할 수 있다. SMS 메시지 전송을 위해 별도의 SNS 토픽을 생성할 필요는 없다. SNS 토픽은 주로 이메일, HTTP/S 요청 등과 같은 다른 유형의 알림에 사용된다.
SQS 연동 불필요
SMS 전송에 SQS를 연동할 필요도 없다. SQS는 메시지 큐 서비스로, 보통 메시지를 임시 저장하고 처리 순서를 관리하는 데 사용된다.
AWS SDK의 일부인 SNS 클라이언트 - SnsClient
SNS 클라이언트 설정
Spring Boot 애플리케이션에서 SNS 기능을 사용하기 위해서는 SnsClient를 설정하고 초기화한다. 이때, AWS IAM 자격 증명과 리전 정보를 제공한다.
SMS 메시지 전송
PublishRequest 객체를 생성하고 phoneNumber 및 message 속성을 설정한다. 이후, snsClient.publish(publishRequest) 메서드를 호출하여 SMS 메시지를 전송한다.
SNS 클라이언트란?
일반적으로 SnsClient라고 불리는 것은 AWS SDK(Amazon Web Services Software Development Kit)의 일부이다.
이 클라이언트는 AWS SNS 서비스와 상호작용하기 위한 프로그래밍 방식의 인터페이스를 제공한다. SnsClient를 사용하여 AWS의 SNS 서비스에 접근하고, 다양한 종류의 알림을 관리하거나 전송할 수 있다.
SNS 클라이언트의 주요 기능
SNS 클라이언트의 주요 기능은 다음과 같다.
메시지 발행(Publishing Messages)
SnsClient는 SMS, 이메일, HTTP/S 등 다양한 종류의 알림을 발행하는 데 사용된다. 이를 통해 사용자는 SNS 주제(Topic)에 메시지를 발행하거나 직접 전화번호나 이메일 주소로 메시지를 보낼 수 있다.
주제 관리(Managing Topics)
주제(Topic)의 생성, 삭제, 구독 관리 등을 할 수 있다. 각 주제는 알림을 받을 구독자들의 그룹을 대표한다.
구독 관리(Managing Subscriptions)
특정 주제에 대한 구독자를 추가, 삭제하거나 구독자 목록을 조회하는 기능을 제공한다.
스프링 부트에서 SNS 클라이언트 사용
스프링 부트 애플리케이션에서 SnsClient를 사용하기 위해서는 다음 단계를 따른다.
1. AWS SDK 의존성 추가
프로젝트의 build.gradle 또는 pom.xml에 AWS Java SDK 의존성을 추가한다.
2. AWS 자격 증명 설정
application.properties 또는 application.yml 파일에서 AWS 자격 증명(Access Key, Secret Key)과 리전 설정을 한다.
3. SnsClient 빈(Bean) 생성
스프링 부트에서 SnsClient 인스턴스를 빈(Bean)으로 생성하여 필요한 곳에서 주입하여 사용할 수 있다.
4. SMS 발송 로직 구현
SnsClient를 사용하여 PublishRequest 객체를 생성하고, 이를 publish 메서드를 통해 발송한다.
스프링 부트와 AWS SDK를 통해 SnsClient를 사용하는 것은 AWS 서비스를 손쉽게 통합하고 활용할 수 있게 해 준다. 이렇게 함으로써, 애플리케이션 내에서 AWS SNS를 통한 다양한 형태의 알림 전송 기능을 구현할 수 있다.
SNS 클라이언트를 사용하는 다양한 방법
SNSClient에는 문자 메시지를 전송하는 기능 이외에도 다양한 메시지 발행 기능이 있다.
이메일 알림
SNS 주제를 생성하고, 이메일 주소를 구독자로 추가한 뒤, 해당 주제에 메시지를 발행하여 구독자들에게 이메일 알림을 보낼 수 있다.
HTTP/S 알림
HTTP/S 엔드포인트를 주제의 구독자로 설정하고, 메시지를 발행하여 웹 서버나 다른 서비스에 알림을 보낼 수 있다.
AWS Lambda 트리거
SNS 메시지를 통해 AWS Lambda 함수를 트리거할 수 있다. 이를 통해 메시지에 대한 다양한 처리 또는 비즈니스 로직을 실행할 수 있다.
SQS와 연동
복잡한 시스템에서는 SNS 메시지를 SQS로 전달하고, SQS 메시지를 소비하여 처리하는 방식으로 확장성과 안정성을 높일 수 있다.
🔻 SNS와 SQS를 연동해 메시지를 주고받는 로직에 관한 정보는 다음 링크에서 확인할 수 있다. 🔻
[AWS] SNS, SQS 연동하기 (1) - SNS, SQS 생성하기
이처럼, Spring Boot와 AWS SNS를 통해서 SMS 메시지를 간단히 보낼 수 있으며, 필요에 따라 다양한 방식으로 SNS를 활용할 수 있다.
Spring Boot에서 SNS를 통한 SMS 전송 코드 구현
Spring Boot에서 AWS SNS를 통한 SMS를 보내려면, 우선 AWS SDK와 관련 의존성을 프로젝트에 추가해야 한다.
의존성 추가
나는 프로젝트가 스프링 부트 3 버전이기 때문에 이 버전에 맞는 의존성을 주입했다. 본인 프로젝트가 스프링 부트 2 버전이라면 내용이 조금씩 다를 수 있다.
implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.1")
implementation 'io.awspring.cloud:spring-cloud-aws-starter-sns'
implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.1")
이 의존성은 Spring Cloud AWS 프로젝트의 종속성 관리를 위한 BOM(Bill of Materials)을 프로젝트에 추가한다. 이것은 프로젝트에서 사용되는 Spring Cloud AWS 관련 라이브러리들의 호환 버전을 관리해 준다.
implementation 'io.awspring.cloud:spring-cloud-aws-starter-sns'
이 의존성은 실제로 Spring Cloud AWS SNS 스타터를 프로젝트에 추가한다. 이 스타터는 SNS와 통합하는 데 필요한 모든 기본 구성을 제공한다.
IAM 계정 설정 (환경 변수 세팅)
application.yml 파일에 아래처럼 AWS 자격 증명에 사용되는 access-key, secret-key를 설정해 주고 리전 정보인 region을 설정해 준다.
나는 실제 데이터도 톰캣 환경변수에 넣어줬다. 이렇게 값을 감출 필요가 없다면 tokyo-static 데이터처럼 직접 값을 입력해도 전혀 상관없다.
spring:
cloud:
aws:
credentials:
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
region:
static: ${REGION}
tokyo-static: ap-northeast-1
AWS 설정
SnsConfig 클래스는 SNS 서비스에 접근하기 위한 설정을 담고 있다. AWS 자격 증명과 리전 정보를 관리하고, SnsClient bean을 생성해 AWS SNS를 사용할 수 있도록 설정한다.
snsClient 메서드는 SNS 클라이언트를 생성하는 데 사용된다. 이 메서드는 SnsClient.builder()를 사용하여 SNS 클라이언트를 구축한다. 클라이언트는 주어진 리전 정보와 함께 AWS의 자격 증명을 사용하여 생성된다. StaticCredentialsProvider.create() 메서드는 주어진 접근 키와 비밀 키를 사용하여 AWS 자격 증명을 생성한다.
@Configuration
@Getter
public class SnsConfig {
@Value("${spring.cloud.aws.credentials.access-key}")
private String awsAccessKey;
@Value("${spring.cloud.aws.credentials.secret-key}")
private String awsSecretKey;
@Value("${spring.cloud.aws.region.tokyo-static}")
private String awsRegion;
@Bean
public SnsClient snsClient() {
return SnsClient.builder()
.region(Region.of(awsRegion))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(awsAccessKey, awsSecretKey)))
.build();
}
}
SMS 메시지 발송 서비스 구현
TokyoSnsService 클래스는 SnsConfig 클래스에서 생성한 SnsClient bean을 이용해 SMS 메시지를 발송한다.
sendVerificationCode 메서드는 SMS를 통해 인증번호를 보내는 기능을 담당한다. 이 메서드는 전화번호를 매개변수로 받아, 랜덤한 인증번호를 생성하고 이를 메시지 형태로 포맷하여 SMS를 보낸다.
PublishRequest 객체는 SNS에 보낼 SMS 메시지와 전화번호를 담고 있다. 이 요청은 snsClient.publish(request)를 호출하여 SNS 서비스로 전송된다. 메시지 전송이 성공하면, 응답으로 메시지 ID를 받아 로그로 기록한다. 메시지 전송 중 예외가 발생하면, 로그에 에러 메시지를 기록한다.
@Service
@RequiredArgsConstructor
@Slf4j
public class TokyoSnsService {
private final SnsClient snsClient;
private final SnsConfig snsConfig;
// 인증번호를 포함한 SMS 메시지 보내기
public void sendVerificationCode(String phoneNumber) {
String verificationCode = generateRandomCode();
String message = String.format("[Recipia] 인증번호[%s]를 입력해주세요.", verificationCode);
PublishRequest request = PublishRequest.builder()
.message(message)
.phoneNumber(phoneNumber)
.build();
try {
PublishResponse response = snsClient.publish(request);
log.info("Sent message {} to {} with messageId {}", message, phoneNumber, response.messageId());
} catch (Exception e) {
log.error("Error sending SMS: {}", e.getMessage());
}
}
// 랜덤 6자리 숫자 생성
private String generateRandomCode() {
Random random = new Random();
int number = random.nextInt(900000) + 100000; // 100000부터 999999까지
return String.valueOf(number);
}
}
테스트를 위한 컨트롤러
실제로 잘 작동하는지 임시로 만든 컨트롤러 내용이다.
@RequestMapping("/member/auth")
@RequiredArgsConstructor
@RestController
public class AuthController {
private final TokyoSnsService tokyoSnsService;
@PostMapping("/phone")
public void authPhoneNumber() {
String phoneNumbner = "+8201011112222";
tokyoSnsService.sendVerificationCode(phoneNumbner);
}
}
이렇게 만들고 postman으로 요청을 보내면 아래와 같이 로그도 잘 나오고 실제로 문자 메시지가 잘 발송되는 걸 확인할 수 있다.
마무리
스프링 부트로 AWS SMS를 사용할 때 SNS topic이 사용 안된다는 건 몰랐다. 그것도 모르고 SNS에 토픽을 만들고 이 주제를 구독하는 SQS를 생성하긴 했다.. ㅎㅎ (이 부분에 대해선 다른 포스팅으로 작성해야겠다.)
그리고 이전에 SNS, SQS를 이용해 데이터베이스의 일관성을 관리하는 로직을 개발하면서 SNS 클라이언트를 사용했을 때는 당연히 SNS 메시지를 발행하니까SnsClient를 사용되는 게 당연하다고 생각했는데 그 이외 문자 메시지나 이메일 전송, HTTP/S 통신 등 다양한 용도로도 SnsClient가 사용된다는 게 신기했다.
또한, 서울 리전에서는 아직 SNS를 통한 SMS 발송 기능이 지원되지 않아 도쿄 리전을 사용해야 했다. 그렇기 때문에 발송된 문자 메시지에 [국외발신] 표시가 되는 점은 좀 아쉽긴 했다. 그러나 문자 메시지에 링크나 이상한 내용을 포함시키지 않고, 단순히 인증번호만 전송하는 용도로 사용하고 있기 때문에 크게 문제가 되지는 않는다. 나중에 이 부분에 대해 더 알아봐야 할 것 같다.
🔻 JPA에서 발생하는 N+1을 해결하는 방법이 궁금하다면 아래 글에 정리해 놨다. 🔻
JPA N+1 문제 해결하기 (fetch join, entityGraph, batch size)
📣 이 글은 내가 소속된 Team Chillwave에서 진행한 사이드 프로젝트에서 경험한 내용을 정리한 것이다.
다른 팀원인 "개발자의 서랍" 님의 블로그도 방문하면 도움이 될 것 같다 :)
'Spring > Spring Boot' 카테고리의 다른 글
[Spring Boot] 스프링 부트 3에 레디스 적용하기 (1) | 2024.01.09 |
---|---|
스프링 부트 3 버전에서 AWS SNS 클라이언트 여러 개 사용하기 (2) | 2023.12.29 |
헥사고날 아키텍처 실전 적용 (1) - 클래스 의존성 주입 및 도메인, 엔티티의 객체 변환 과정 (0) | 2023.12.16 |
[Spring Boot] Spring Boot 3.X 버전에 p6spy 적용하기 (0) | 2023.12.08 |
[Spring Boot] 스프링 배치와 JPA를 활용해 누락된 SNS 이벤트 재발행 (0) | 2023.11.29 |