들어가기 전

이번 포스팅에서는 Spring Boot + Java를 이용하여 크롤링을 하고 크롤링을 한 내용을 Pdf 파일로 만들어 메일에 담아서 보내는 방법에 대해 알아보겠습니다. 그리고 메일로 보낸 Pdf 파일을 암호화하는 방법에 대해서도 알아보겠습니다.

 

크롤링, 메일전송, Pdf 파일 생성 및 암호화하는 방법에 대해 알아보기 전에 해당 기능들을 구현하기 위해 사용되는 라이브러리에 대해 먼저 알아보겠습니다.

 

메일 전송에 사용되는 라이브러리 및 설정

 

JavaMailSender

JavaMailSender는 스프링에서 메일을 손쉽게 보낼 수 있게 제공해 주는 라이브러리입니다.

JavaMailSender를 사용하기 위해서는 의존성을 추가해 줘야 되고 application.yml 또는 properties에 설정을 해줘야 됩니다.

그리고 application.yml 또는 properties에서 사용할 password는 따로 앱 비밀번호를 발급받아야 됩니다.

 

build.gradle

 

//메일 전송에 필요한 의존성
implementation 'org.springframework.boot:spring-boot-starter-mail'

 

application.yml

 

spring :
  mail:
    host : smtp.gmail.com
    port : 587
    username: '구글 이메일'
    password: '발급받은 앱 비밀번호'

 

앱 비밀번호 발급받는 방법

1. 구글 로그인 -> 계정관리 -> 보안

 

 

 

2. 보안을 선택한 뒤 2단계 인증을 선택합니다.

 

 

3.  앱  비밀번호를 클릭 한 뒤 "앱 선택", "기기 선택"을 한 뒤 생성을 하게 앱 비밀번호를 발급받을 수 있습니다. 발급받은 비밀번호를 application.yml 또는 properties의 password에 작성하시면 됩니다.

 

 

 

이제 메일을 보내기 위한 설정을 마쳤습니다. 그럼 이제 메일을 보내는 예제에 대해 알아보겠습니다.

 

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
@Service
@RequiredArgsConstructor
public class MailSendService {

    private final JavaMailSender mailSender;
    @Value("${spring.mail.username}")
    private String fromEmail;

    public void exampleMail() throws MessagingException {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
        mimeMessageHelper.setTo("메일을 수신받을 이메일 주소");
        mimeMessageHelper.setFrom(fromEmail);
        mimeMessageHelper.setText("안녕하세요~!");
        mailSender.send(mimeMessage);
    }
}

 

  • MimeMessage :  MimeMessage는 MIME 유형과 헤더를 이해하는 전자 메일 메시지입니다
    • MIME 유형 :  클라이언트에게 전송된 문서의 다양성을 알려주기 위한 메커니즘, ex) text/plain, text/html 등등
  • MimeMessageHelper : HTML, 이미지, 첨부파일 등 멀티파일 메일을 보낼 수 있도록 스프링에서 제공하는 헬퍼 객체
    • 첫 번째 인자 : MIME 메시지
    • 두 번째 인자 : 이미지, 첨부 파일 등 멀티파일 전송 유무, true이면 멀티파일 사용가능, false일 경우 사용 불가능
    • 세 번째 인자 : 인코딩

  • setTo : 메일을 수신받을 이메일 주소
  • setFrom : 메일을 발신하는 이메일 주소
  • setText : 메일 내용

 

그런데 위와 같이 application.yml을 설정을 하면 아래와 같은 오류가 발생하게 됩니다.

 

org.springframework.mail.MailSendException:
Failed messages: com.sun.mail.smtp.SMTPSendFailedException:
530 5.7.0 Must issue a STARTTLS command first.
u13-20020a170902e5cd00b001bb33ee4057sm8979587plf.43 - gsmtp

 

위와 같은 오류를 해결하기 위해 아래와 같이 설정을 추가해 줘야 됩니다.

 

spring :
  mail:
    host : smtp.gmail.com
    port : 587
    username: '구글 이메일'
    password: '발급받은 앱 비밀번호'
    properties :
      mail :
        smtp :
          starttls :
            enable : true

 

그럼 정상적으로 메일이 발송된 것을 확인할 수 있습니다.

 

 

크롤링, PDF 생성 및 암호화

 

JSOUP

Jsoup은 자바 언어로 HTML을 쉽게 다룰 수 있고 강력한 기능을 제공하는 HTML Parser입니다.

주로 정적인 웹 페이지의 HTML을 간단하게 가져와 파싱을 할 수 있는 라이브러리입니다.

Jsoup을 이용하여 특정 URL을 파싱해올 때 해당 URL의 프로토콜이 http 또는 https로 이루어져 있어야 합니다.

 

Jsoup요소는  크게 다섯 가지로 볼 수 있습니다.

객체 설명
Document Jsoup이 파싱해서 얻어온 전체 HTML 문서
Element Document의 HTML 요소
Elements Element가 모인 자료형으로 반복문을 사용이 가능
Connection Jsoup의 connect 혹은 설정 메소드들을 이용해 만들어지는 객체, 연결을 하기 위한 정보
Response Jsoup을 이용해 URL에 접속해 얻어온 결과. Document와 다르게 status 코드, status 메시지나 charset같은 헤더 메시지와 쿠키등을 가지고 있음

 

Jsoup을 사용하려면 의존성을 추가해줘야 합니다.

 

build.gradle

 

implementation 'org.jsoup:jsoup:1.13.1'

 

FileOutputStream

자바에서 파일에 데이터를 쓰기 위해 사용하는 클래스입니다. 바이트 단위로 데이터를 파일에 쓸 수 있는 기능을 제공하여 데이터를 파일에 기록할 수 있습니다. 텍스트 파일에 텍스트 데이터를 쓰거나 이미지, 첨부파일 등의 바이너리 데이터를 파일에 쓸 때 사용합니다.

 

PdfRenderBuilder, PdfBoxRenderer, PDDocument

PdfRenderBuilder는 데이터를 PDF에 반영하기 위한 라이브러리입니다.

PdfBoxRender는 글자 크기, 페이지 사이즈 등 Css를 적용하는 라이브러리입니다.

PDDocument는 데이터가 반영된 PDF 문서를 제공하는 라이브러리입니다.

위의 세 개의 라이브러리를 적용하려면 의존성이 필요합니다.

 

build.gradle

 

implementation 'com.openhtmltopdf:openhtmltopdf-pdfbox:1.0.10'
implementation 'com.openhtmltopdf:openhtmltopdf-core:1.0.10'

 

AccessPermission

AccessPermission은 문서에 대한 액세스 권한을 나타낼 수 있도록 해주는 라이브러리입니다. 권한은 PDF 사양에 지정되는데 문서 인쇄, 문서 내용 수정, 복사 또는 추출 등 수신자에게 액세스 권한을 할당하여 문서를 보호하는 데 사용할 수 있습니다.

특정 경우에는 ProtectionPolicy와 함께 사용해야 됩니다.  문서를 해독한 사용자에게 부여된 액세스 권한인 currentAccessPermission 속성이 포함됩니다.

 

StandardProtectionPolicy

ProtectionPolicy 자식 객체입니다. StandardProtectionPolicy는 문서에 비밀번호를 설정하여 Pdf 문서를 보호합니다.

 

 

AccessPerMission이랑 StandardProtectionPolicy를 사용하려면 의존성을 추가해 줘야 됩니다.

 

build.gradle

 

implementation 'com.openhtmltopdf:openhtmltopdf-pdfbox:1.0.10'

 

 

특정 정적 페이지를 크롤링하여 크롤링한 결과를 PDF에 담고 암호를 설정한 PDF를 메일로 보내기 위한 라이브러리들을 모두 알아보았습니다. 이제 위에 설명한 라이브러리를 활용하는 예제에 대해 알아보겠습니다.

 

 

import com.openhtmltopdf.pdfboxout.PdfBoxRenderer;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.jsoup.Jsoup;
import org.jsoup.helper.W3CDom;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MailSendService {
    private final JavaMailSender mailSender;
    @Value("${spring.mail.username}")
    private String fromEmail;

    public void send() throws MessagingException, IOException {

        MimeMessage mimeMessage = mailSender.createMimeMessage();

        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "utf-8");

        mimeMessageHelper.setFrom(fromEmail);
        mimeMessageHelper.setTo("수신받을 이메일");
        mimeMessageHelper.setText("첨부파일을 참고해주세요.", true);
        File pdf = getPdf();

        mimeMessageHelper.addAttachment(pdf.getName(), pdf);
        mailSender.send(mimeMessage);
    }

    private File getPdf() throws IOException {
        Document jsoup = Jsoup.connect("https://javadoc.io").get();
        org.w3c.dom.Document document = new W3CDom().fromJsoup(jsoup);
        File pdfFile = new File("hoe.pdf");
        FileOutputStream fileOutputStream = new FileOutputStream(pdfFile);
        PdfRendererBuilder renderRender = new PdfRendererBuilder();
        renderRender.toStream(fileOutputStream);
        renderRender.withW3cDocument(document,"https://javadoc.io/");
        PdfBoxRenderer pdfBoxRenderer = renderRender.buildPdfRenderer();
        PDDocument pdfDocument = pdfBoxRenderer.getPdfDocument();
        AccessPermission accessPermission = new AccessPermission();
        accessPermission.setCanPrint(false);
        StandardProtectionPolicy standardProtectionPolicy = new StandardProtectionPolicy("1234",
            "1234", accessPermission);
        standardProtectionPolicy.setEncryptionKeyLength(128);
        standardProtectionPolicy.setPermissions(accessPermission);
        pdfDocument.protect(standardProtectionPolicy);
        renderRender.run()
        fileOutputStream.close();
        return pdfFile;
    }
}

 

 

Document jsoup = Jsoup.connect("https://javadoc.io").get();
org.w3c.dom.Document document = new W3CDom().fromJsoup(jsoup);

 

  • Jsoup.connect("크롤링할 URL 주소"). get() : 크롤링할 URL 주소에 연결 후 HTML문서를 반환합니다.
  • new W3CDom().fromJsoup(jsoup) : Jsoup으로 크롤링한 HTML 문서를 W3C HTML 문서로 변환합니다.

 

File pdfFile = new File("hoe.pdf");
FileOutputStream fileOutputStream = new FileOutputStream(pdfFile);
  • new File("파일이름") : 파일이름으로 파일 생성합니다.
  • new FileOutputStream(File) : 출력 스트림을 생성하여 file에 데이터를 쓸 수 있습니다.

 

PdfRendererBuilder renderRender = new PdfRendererBuilder();
renderRender.toStream(fileOutputStream);
renderRender.withW3cDocument(document,"https://javadoc.io/");
PdfBoxRenderer pdfBoxRenderer = renderRender.buildPdfRenderer();
PDDocument pdfDocument = pdfBoxRenderer.getPdfDocument();
AccessPermission accessPermission = new AccessPermission();
StandardProtectionPolicy standardProtectionPolicy = new StandardProtectionPolicy("1234",
"1234", accessPermission);
standardProtectionPolicy.setEncryptionKeyLength(128);
standardProtectionPolicy.setPermissions(accessPermission);
pdfDocument.protect(standardProtectionPolicy);
renderRender.run();

 

  • renderRender.toStream(fileOutputStream) : HTML문서를 담을 파일을 설정을 합니다. 
  • renderRender.withW3cDocument(document, baseUrl) : 위에서 크롤링 한 HTML 문서와 크롤링한 baseURL을 설정합니다.
  • PdfBoxRenderer pdfBoxRenderer = renderRender.buildPdfRenderer() : 설정된 내용을 기반으로 글자 크기, 페이지 크기 등 Css를 적용합니다.
  • PDDocument pdfDocument = pdfBoxRenderer.getPdfDocument() : 크롤링한 HTML 데이터와 CSS를 모두 적용하여 최종 결과인 Pdf 문서를 반환합니다.
  • StandardProtectionPolicy standardProtectionPolicy = new StandardProtectionPolicy(ownerPassword,
    userPassword, accessPermission) : PDF에 암호를 설정합니다. 해당 PDF 파일을 받은 사용자는 설정된 비밀번호를 입력해야 PDF를 확인할 수 있습니다.
  • standardProtectionPolicy.setEncryptionKeyLength(128) : 암호화할 키의 크기를 설정합니다. 인자로는 40, 128, 256만 설정이 가능합니다.

 

  • pdfDocument.protect(standardProtectionPolicy) :  PDF에 권한을 설정합니다.
  • renderRender.run() : PDF를 생성하고 생성된 PDF에 크롤링한 결과를 담습니다. 그 후 PdfBoxRenderer 객체는 자동으로 자원이 해제됩니다.

 

 

실행 결과