diff --git a/build.gradle b/build.gradle index 560a1374..fa01eb4e 100644 --- a/build.gradle +++ b/build.gradle @@ -46,12 +46,6 @@ dependencies { // // https://mvnrepository.com/artifact/org.flywaydb/flyway-mysql // implementation 'org.flywaydb:flyway-mysql:10.15.2' - // Gmail - implementation 'com.google.api-client:google-api-client:2.0.0' - implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1' - implementation 'com.google.apis:google-api-services-gmail:v1-rev20220404-2.0.0' - implementation 'javax.mail:mail:1.4.7' - // QueryDSL def querydslVersion = "5.0.0" implementation "com.querydsl:querydsl-jpa:${querydslVersion}:jakarta" @@ -67,6 +61,9 @@ dependencies { // FCM implementation 'com.google.firebase:firebase-admin:9.4.1' + + // SES + implementation 'com.amazonaws:aws-java-sdk-ses:1.12.778' } spotless { diff --git a/src/main/java/org/ioteatime/meonghanyangserver/auth/service/AuthService.java b/src/main/java/org/ioteatime/meonghanyangserver/auth/service/AuthService.java index b5ba02ca..9d5e2d85 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/auth/service/AuthService.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/auth/service/AuthService.java @@ -9,7 +9,7 @@ import org.ioteatime.meonghanyangserver.auth.dto.request.LoginRequest; import org.ioteatime.meonghanyangserver.auth.mapper.AuthEntityMapper; import org.ioteatime.meonghanyangserver.auth.mapper.AuthResponseMapper; -import org.ioteatime.meonghanyangserver.clients.google.GoogleMailClient; +import org.ioteatime.meonghanyangserver.clients.ses.SesClient; import org.ioteatime.meonghanyangserver.common.exception.BadRequestException; import org.ioteatime.meonghanyangserver.common.exception.NotFoundException; import org.ioteatime.meonghanyangserver.common.exception.UnauthorizedException; @@ -30,13 +30,13 @@ @Service @RequiredArgsConstructor public class AuthService { - private final MemberRepository memberRepository; - private final GoogleMailClient googleMailClient; - private final BCryptPasswordEncoder bCryptPasswordEncoder; private final JwtUtils jwtUtils; + private final SesClient sesClient; + private final MemberRepository memberRepository; + private final GroupMemberRepository deviceRepository; private final EmailCodeRepository emailCodeRepository; + private final BCryptPasswordEncoder bCryptPasswordEncoder; private final RefreshTokenRepository refreshTokenRepository; - private final GroupMemberRepository deviceRepository; public LoginResponse login(LoginRequest loginRequest) { MemberEntity memberEntity = @@ -84,7 +84,7 @@ public void send(String email) {
%s
""" .formatted(code); - googleMailClient.sendMail(email, mailSubject, mailContent); + sesClient.sendEmail(email, mailSubject, mailContent); } private static String getCode() { diff --git a/src/main/java/org/ioteatime/meonghanyangserver/clients/google/GoogleMailClient.java b/src/main/java/org/ioteatime/meonghanyangserver/clients/google/GoogleMailClient.java deleted file mode 100644 index 4ea05a45..00000000 --- a/src/main/java/org/ioteatime/meonghanyangserver/clients/google/GoogleMailClient.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.ioteatime.meonghanyangserver.clients.google; - -import com.google.api.client.auth.oauth2.Credential; -import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; -import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; -import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; -import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; -import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; -import com.google.api.client.googleapis.json.GoogleJsonError; -import com.google.api.client.googleapis.json.GoogleJsonResponseException; -import com.google.api.client.http.javanet.NetHttpTransport; -import com.google.api.client.json.gson.GsonFactory; -import com.google.api.client.util.store.FileDataStoreFactory; -import com.google.api.services.gmail.Gmail; -import com.google.api.services.gmail.GmailScopes; -import com.google.api.services.gmail.model.Message; -import java.io.*; -import java.nio.file.Paths; -import java.security.GeneralSecurityException; -import java.util.Properties; -import java.util.Set; -import javax.mail.MessagingException; -import javax.mail.Session; -import javax.mail.internet.AddressException; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; -import org.apache.commons.codec.binary.Base64; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -@Service -public class GoogleMailClient { - @Value("${google.service-account}") - private String gcpServiceAccount; - - @Value("${google.official-gmail}") - private String fromEmailAddress; - - private Credential getCredentials( - final NetHttpTransport HTTP_TRANSPORT, GsonFactory jsonFactory) { - try { - // Load client secrets. - InputStream in = new FileInputStream(gcpServiceAccount); - GoogleClientSecrets clientSecrets = - GoogleClientSecrets.load(jsonFactory, new InputStreamReader(in)); - - // Build flow and trigger user authorization request. - GoogleAuthorizationCodeFlow flow = - new GoogleAuthorizationCodeFlow.Builder( - HTTP_TRANSPORT, - jsonFactory, - clientSecrets, - Set.of(GmailScopes.GMAIL_SEND)) - .setDataStoreFactory( - new FileDataStoreFactory(Paths.get("tokens").toFile())) - .setAccessType("offline") - .build(); - LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build(); - return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user"); - } catch (IOException e) { - throw new RuntimeException("Google Credential 획득 중 I/O 오류가 발생하였습니다."); - } - } - - public void sendMail(String toEmailAddress, String subject, String message) { - try { - NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); - GsonFactory jsonFactory = GsonFactory.getDefaultInstance(); - Gmail service = - new Gmail.Builder( - httpTransport, - jsonFactory, - getCredentials(httpTransport, jsonFactory)) - .setApplicationName("Test Mailer") - .build(); - - // Encode as MIME message - Properties props = new Properties(); - Session session = Session.getDefaultInstance(props, null); - MimeMessage email = new MimeMessage(session); - email.setFrom(new InternetAddress(fromEmailAddress)); - email.addRecipient( - javax.mail.Message.RecipientType.TO, new InternetAddress(toEmailAddress)); - email.setSubject(subject); - email.setContent(message, "text/html;charset=utf-8"); - - // Encode and wrap the MIME message into a gmail message - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - email.writeTo(buffer); - byte[] rawMessageBytes = buffer.toByteArray(); - String encodedEmail = Base64.encodeBase64URLSafeString(rawMessageBytes); - Message msg = new Message(); - msg.setRaw(encodedEmail); - - // Create send message - msg = service.users().messages().send("me", msg).execute(); - System.out.println("Message id: " + msg.getId()); - System.out.println(msg.toPrettyString()); - - } catch (GoogleJsonResponseException e) { - GoogleJsonError error = e.getDetails(); - if (error.getCode() == 403) { - System.err.println("Unable to send message: " + e.getDetails()); - } else { - throw new RuntimeException( - "Gmail Send Mail에서 GoogleJsonResponseException이 발생하였습니다."); - } - } catch (AddressException e) { - throw new RuntimeException("Gmail Send Mail에서 AddressException이 발생하였습니다."); - } catch (MessagingException e) { - throw new RuntimeException("Gmail Send Mail에서 MessagingException이 발생하였습니다."); - } catch (IOException e) { - throw new RuntimeException("Gmail Send Mail에서 IOException이 발생하였습니다."); - } catch (GeneralSecurityException e) { - throw new RuntimeException("Gmail Client GeneralSecurityException이 발생하였습니다."); - } - } -} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/clients/ses/SesClient.java b/src/main/java/org/ioteatime/meonghanyangserver/clients/ses/SesClient.java new file mode 100644 index 00000000..abd585d1 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/clients/ses/SesClient.java @@ -0,0 +1,42 @@ +package org.ioteatime.meonghanyangserver.clients.ses; + +import com.amazonaws.services.simpleemail.AmazonSimpleEmailService; +import com.amazonaws.services.simpleemail.model.*; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class SesClient { + @Value("${google.official-gmail}") + private String officialGmail; + + private final AmazonSimpleEmailService emailService; + + public void sendEmail(String email, String subject, String body) { + final String FROM = officialGmail; + final String TO = email; + final String SUBJECT = subject; + final String HTMLBODY = body; + + SendEmailRequest request = + new SendEmailRequest() + .withDestination(new Destination().withToAddresses(TO)) + .withMessage( + new Message() + .withBody( + new Body() + .withHtml( + new Content() + .withCharset("UTF-8") + .withData(HTMLBODY))) + .withSubject( + new Content() + .withCharset("UTF-8") + .withData(SUBJECT))) + .withSource(FROM); + emailService.verifyEmailAddress(new VerifyEmailAddressRequest().withEmailAddress(email)); + emailService.sendEmail(request); + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/config/AwsConfig.java b/src/main/java/org/ioteatime/meonghanyangserver/config/AwsConfig.java index 9bb7b259..512e2d3f 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/config/AwsConfig.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/config/AwsConfig.java @@ -5,6 +5,8 @@ import com.amazonaws.regions.Regions; import com.amazonaws.services.kinesisvideo.AmazonKinesisVideo; import com.amazonaws.services.kinesisvideo.AmazonKinesisVideoClient; +import com.amazonaws.services.simpleemail.AmazonSimpleEmailService; +import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -17,6 +19,12 @@ public class AwsConfig { @Value("${aws.kvs.secret-key}") private String kvsSecretKey; + @Value("${aws.ses.access-key}") + private String sesAccessKey; + + @Value("${aws.ses.secret-key}") + private String sesSecretKey; + @Bean public AmazonKinesisVideo amazonKinesisVideo() { AWSCredentials awsCredentials = @@ -37,4 +45,25 @@ public String getAWSSecretKey() { .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) .build(); } + + @Bean + public AmazonSimpleEmailService amazonSimpleEmailService() { + AWSCredentials awsCredentials = + new AWSCredentials() { + @Override + public String getAWSAccessKeyId() { + return sesAccessKey; + } + + @Override + public String getAWSSecretKey() { + return sesSecretKey; + } + }; + + return AmazonSimpleEmailServiceClient.builder() + .withRegion(Regions.AP_NORTHEAST_2) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } } diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 04f4debb..d5dfba29 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -15,4 +15,8 @@ spring: multipart: location: C:\\multipart\\uploads\\ max-file-size: 10MB - max-request-size: 10MB \ No newline at end of file + max-request-size: 10MB + +logging: + level: + root: debug \ No newline at end of file diff --git a/src/main/resources/application-key.yaml b/src/main/resources/application-key.yaml index 7e28fdeb..9e657507 100644 --- a/src/main/resources/application-key.yaml +++ b/src/main/resources/application-key.yaml @@ -6,10 +6,14 @@ token: google: service-account: ${GCP_SERVICE_ACCOUNT} + gmail-service-account: ${GMAIL_SERVICE_ACCOUNT} official-gmail: ${OFFICIAL_GMAIL} application-credentials: ${GOOGLE_APPLICATION_CREDENTIALS} aws: kvs: access-key: ${AWS_KVS_ACCESS_KEY} - secret-key: ${AWS_KVS_SECRET_KEY} \ No newline at end of file + secret-key: ${AWS_KVS_SECRET_KEY} + ses: + access-key: ${AWS_SES_ACCESS_KEY} + secret-key: ${AWS_SES_SECRET_KEY} \ No newline at end of file