1. NCP 세팅
1.1. 액세스 키 발급
1. 마이페이지 > 계정 관리 > 인증키 관리 > 신규 API 인증키 생성
2. 생성된 액세스 키와 시크릿 키를 저장해 놓는다.
1.2. Cloud Outbound Mailer 이용 신청
- 콘솔 > Services > ‘Cloud Outbound Mailer’ 선택
- 이용 신청 > 전체 동의 > 확인
2. Spring Boot Mail 전송 구현
2.0. Cloud Outbound Mailer
요청 예시의 Body 부분과 createMailRequest의 요청 파라미터를 보고, MailRequest 필드(title, body, senderAddress, senderName, recipients) 및 MailRequestRecipients 필드(address, name, type) 지정
{
"senderAddress":"no_reply@company.com",
"title":"${customer_name}님 반갑습니다. ",
"body":"귀하의 등급이 ${BEFORE_GRADE}에서 ${AFTER_GRADE}로 변경되었습니다.",
"recipients":[
{
"address":"hongildong@naver_.com",
"name":"홍길동",
"type":"R",
"parameters":{
"customer_name":"홍길동",
"BEFORE_GRADE":"SILVER",
"AFTER_GRADE":"GOLD"
}
},
{
"address":"chulsoo@daum_.net",
"name":null,
"type":"R",
"parameters":{
"customer_name":"철수",
"BEFORE_GRADE":"BRONZE",
"AFTER_GRADE":"SILVER"
}
}
],
"individual":true,
"advertising":false
}
2.1. 구현
| application.properties
ncp.accessKey=${NCP_ACCESS_KEY}
ncp.secretKey=${NCP_SECRET_KEY}
ncp.mail.senderAddress=${NCP_MAIL_SENDER_ADDRESS}
| MemberController.java
@RequiredArgsConstructor
@RestController
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
...
@PostMapping("/company/code")
public ResponseEntity<Void> createVerificationCodeForCompany(@AuthorizedVariable Long memberProfileId, @RequestBody CompanyVerificationCodeRequest companyVerificationCodeRequest)
throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, JsonProcessingException {
memberService.createVerificationCodeForCompany(memberProfileId, companyVerificationCodeRequest);
return ResponseEntity.ok().build();
}
...
}
| MemberService.java
@RequiredArgsConstructor
@Transactional
@Service
public class MemberService {
private static final String EMAIL_DELIMITER = "@";
private static final int DOMAIN = 1;
private final MailService mailService;
private final CompanyRepository companyRepository;
...
public void createVerificationCodeForCompany(Long memberProfileId, CompanyVerificationCodeRequest companyVerificationCodeRequest)
throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, JsonProcessingException {
String email = companyVerificationCodeRequest.getEmail();
validateCompanyDomain(email);
mailService.sendMail(getMemberProfileById(memberProfileId).getMemberName(), email);
}
...
private void validateCompanyDomain(String email) { (1)
if (!companyRepository.existsByEmail(extractDomain(email))) {
throw new NotFoundException(ApplicationError.INVALID_COMPANY);
}
}
private String extractDomain(String email) {
return email.split(EMAIL_DELIMITER)[DOMAIN];
}
}
(1) @ 뒤 회사 도메인에 대해 검증
| NCPMailService.java
@RequiredArgsConstructor
@Service
public class NCPMailService implements MailService {
private static final int VERIFICATION_CODE_LENGTH = 6;
private static final int EXPIRE_TIME = 3;
private static final String TYPE = "R";
private static final String CHARSET_NAME = "UTF-8";
private static final String SECRET_KEY_ALGORITHM = "HmacSHA256";
private static final String BLANK = " ";
private static final String NEW_LINE = "\\n";
private final RedisConnector redisConnector;
private final ObjectMapper objectMapper;
private final RestTemplate restTemplate;
@Value("${ncp.accessKey}")
private String accessKey;
@Value("${ncp.secretKey}")
private String secretKey;
@Value("${ncp.mail.senderAddress}")
private String senderAddress;
@Override
public void sendMail(String name, String email) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, JsonProcessingException {
MailResponse mailResponse = restTemplate.postForObject(
URI.create("<https://mail.apigw.ntruss.com/api/v1/mails>"),
new HttpEntity<>(objectMapper.writeValueAsString(getMailRequest(name, email, generateVerificationCode(email))), getHttpHeaders()),
MailResponse.class);
if (mailResponse.getCount() != 1) { (1)
throw new MailSendFailException(ApplicationError.MAIL_SEND_FAIL);
}
private String generateVerificationCode(String email) {
String verificationCode = VerificationCodeGenerator.generate(VERIFICATION_CODE_LENGTH);
redisConnector.set(email, verificationCode, Duration.ofMinutes(EXPIRE_TIME)); (2)
return verificationCode;
}
private MailRequest getMailRequest(String name, String email, String verificationCode) {
return MailRequest.builder()
.title("[직팅] 회사 인증번호 안내 메일")
.body(getVerificationCodeMessage(verificationCode))
.senderAddress(senderAddress)
.senderName("직팅")
.recipients(List.of(MailRequestRecipient.of(name, email, TYPE)))
.build();
}
private String getVerificationCodeMessage(String verificationCode) {
return "[직팅]\\n인증번호: " + verificationCode;
}
private HttpHeaders getHttpHeaders() throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
String time = String.valueOf(System.currentTimeMillis());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("x-ncp-apigw-timestamp", time);
headers.set("x-ncp-iam-access-key", accessKey);
headers.set("x-ncp-apigw-signature-v2", getSignature(time));
return headers;
}
private String getSignature(String time) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
Mac mac = Mac.getInstance(SECRET_KEY_ALGORITHM);
mac.init(new SecretKeySpec(secretKey.getBytes(CHARSET_NAME), SECRET_KEY_ALGORITHM));
return Base64.encodeBase64String(mac.doFinal(getMessage(time).getBytes(CHARSET_NAME)));
}
private String getMessage(String time) {
return HttpMethod.POST.name() + BLANK + "/api/v1/mails"
+ NEW_LINE + time
+ NEW_LINE + accessKey;
}
}
(1) 메일 전송 결과에 이메일 발송 요청 ID인 requestId와 메일 요청 건수인 count가 반환되므로, count가 1이 아니면 메일 전송 실패 예외가 발생한다.
(2) redis에 email을 key 값으로 생성된 인증번호를 저장. 이때, 만료기간은 3분으로 설정했으므로 3분 뒤 해당 값이 삭제된다.
redis 설정에 대한 부분은 [Spring Boot] 전화번호 인증 with NCP SMS API & Redis를 참고해주세요.
| MailRequest.java
package com.cupid.jikting.member.dto;
import lombok.*;
import java.util.List;
@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MailRequest {
private String title;
private String senderAddress;
private String senderName;
private String body;
private List<MailRequestRecipient> recipients;
}
| MailRequestRecipient.java
package com.cupid.jikting.member.dto;
import lombok.*;
@Getter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MailRequestRecipient {
private String address;
private String name;
private String type;
public static MailRequestRecipient of(String name, String email, String type) {
return new MailRequestRecipient(email, name, type);
}
}
2.2. 테스트 with 포스트맨
References
'study > Spring' 카테고리의 다른 글
[Spring] Redis Sorted Set을 이용한 검색 랭킹 조회 기능 개선 (1) | 2023.12.10 |
---|---|
[Spring WebSocket] Redis를 이용한 채팅 고도화 (0) | 2023.09.01 |
[Spring Boot] 전화번호 인증 with NCP SMS API & Redis (0) | 2023.08.03 |
[Spring Boot] AWS S3 이미지 업로드 (0) | 2023.08.03 |
[Spring WebSocket] Spring WebSocket STOMP 적용 (3) | 2023.06.19 |