@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
Member findByLoginId(String username);
}
Spring Security에서 사용자를 담아둘 클래스입니다
securty/auth/MemberPrincipalDetails
// Spring Security 에 있는 UserDetails 를 구현한 클래스,
// 이 클래스를 통해 Spring Security 에서 사용자의 정보를 담아둠
public class MemberPrincipalDetails implements UserDetails {
// member 패키지에 선언해놓은 member 엔티티를 사용하기 위해 선언
private final Member member;
public MemberPrincipalDetails(Member member) {
this.member = member;
}
// 생성자
public Member getMember() {
return member;
}
// member 계정의 권한을 담아두기위해
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(member.getRole()));
return authorities;
}
// member 계정의 비밀번호를 담아두기 위해
@Override
public String getPassword() {
return member.getPassword();
}
// member 계정의 아이디를 담아두기 위해
@Override
public String getUsername() {
return member.getLoginId();
}
// 계정이 만료되지 않았는지를 담아두기 위해 (true: 만료안됨)
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정이 잠겨있지 않았는지를 담아두기 위해 (true: 잠기지 않음)
@Override
public boolean isAccountNonLocked() {
return true;
}
// 계정의 비밀번호가 만료되지 않았는지를 담아두기 위해 (true: 만료안됨)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정이 활성화되어있는지를 담아두기 위해 (true: 활성화됨)
@Override
public boolean isEnabled() {
return true;
}
}
스프링 시큐리티가 로그인 요청을 가로챌때 동작하는 서비스 입니다
security/auth/MemberPrincipalDetailService
// UserDetailsService 를 구현한 클래스
// 스프링 시큐리티가 로그인 요청을 가로챌 때, username, password 변수 2개를 가로채는데
// password 부분 처리는 알아서 함
@Service
public class MemberPrincipalDetailService implements UserDetailsService {
// JPARepository 를 상속받은 인터페이스를 자동으로 DI (의존성 주입)
@Autowired
private MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 넘겨받은 id 로 DB 에서 회원 정보를 찾음
Member member = memberRepository.findByLoginId(username);
System.out.println("username : " + username);
System.out.println("member : " + member);
// 없을경우 에러 발생
if(member == null)
throw new UsernameNotFoundException(username + "을 찾을 수 없습니다.");
if(!"Y".equals(member.getIsUsed()))
throw new UsernameNotFoundException("사용할 수 없는 계정입니다.");
if(!"N".equals(member.getIsDel()))
throw new UsernameNotFoundException("삭제된 계정입니다.");
// MemberPrincipalDetails 에 Member 객체를 넘겨줌
return new MemberPrincipalDetails(member);
}
}
로그인 정보를 가로챈 후 인증버로를 반환 하기위한 클래스입니다
security/provider/MemberAuthenticationProvider
@Component
// AuthenticationProvider 를 구현한 클래스
public class MemberAuthenticatorProvider implements AuthenticationProvider {
@Autowired
private MemberPrincipalDetailService memberPrincipalDetailService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName(); // 사용자가 입력한 id
String password = (String) authentication.getCredentials(); // 사용자가 입력한 password
// 생성해둔 MemberPrincipalDetailService 에서 loadUserByUsername 메소드를 호출하여 사용자 정보를 가져온다.
MemberPrincipalDetails memberPrincipalDetails = (MemberPrincipalDetails) memberPrincipalDetailService.loadUserByUsername(username);
// ====================================== 비밀번호 비교 ======================================
// 사용자가 입력한 password 와 DB 에 저장된 password 를 비교한다.
// db 에 저장된 password
String dbPassword = memberPrincipalDetails.getPassword();
// 암호화 방식 (BCryptPasswordEncoder) 를 사용하여 비밀번호를 비교한다.
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
if(!passwordEncoder.matches(password, dbPassword)) {
System.out.println("[사용자] 비밀번호가 일치하지 않습니다.");
throw new BadCredentialsException("[사용자] 아이디 또는 비밀번호가 일치하지 않습니다.");
}
// ========================================================================================
// 사용자가 입력한 id 와 password 가 일치하면 인증이 성공한 것이다.
// 인증이 성공하면 MemberPrincipalDetails 객체를 반환한다.
Member member = memberPrincipalDetails.getMember();
if (member == null || "N".equals(member.getIsUsed())) {
System.out.println("[사용자] 사용할 수 없는 계정입니다.");
throw new BadCredentialsException("[사용자] 사용할 수 없는 계정입니다.");
}
// 인증이 성공하면 UsernamePasswordAuthenticationToken 객체를 반환한다.
// 해당 객체는 Authentication 객체로 추후 인증이 끝나고 SecurityContextHolder.getContext() 에 저장된다.
return new UsernamePasswordAuthenticationToken(memberPrincipalDetails, null, memberPrincipalDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
로그인 실패시 에러 메세지를 띄우기 위한 커스텀 핸들러 입니다
security/config/MemberAuthFailureHandler
public class MemberAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws ServletException, IOException {
// 실패 메세지를 담기 위한 세션 선언
HttpSession session = request.getSession();
// 세션에 실패 메세지 담기
session.setAttribute("loginErrorMessage", exception.getMessage());
// 로그인 실패시 이동할 페이지
setDefaultFailureUrl("/member/login/loginForm?error=true&t=h");
// 로그인 실패시 이동할 페이지로 이동
super.onAuthenticationFailure(request, response, exception);
}
}
로그인 성공시 작동하는 커스텀 핸들러 입니다 ( 해당 프로젝트에선 사용하지 않습니다 )
security/config/MemberAuthSuccessHandler
public class MemberAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// 로그인 성공시 이동할 페이지
setDefaultTargetUrl("/member/main");
// 로그인 성공시 이동할 페이지로 이동
super.onAuthenticationSuccess(request, response, authentication);
}
}
위에 작성한 파일들이 사용되는 파일입니다
security/config/MemberSecurityConfig
로그인 기억하기 사용을 위한 rememberMe 도 같이 사용하였습니
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class MemberSecurityConfig {
/*
* 중요
* Spring Security 5.7.0 버전부터
* WebSecurityConfigurerAdapter가 deprecated 되기 때문에
* 이와 같은 방법으로 구현
* */
// 생성해둔 MemberAuthenticatorProvider를 주입받는다.
// 해당 클래스로 MemberPrincipalDetailsService 내부 로직을 수행하며
// 인증 처리도 같이 진행된다
@Autowired
MemberAuthenticatorProvider memberAuthenticatorProvider;
// 로그인 기억하기 사용을 위해 MemberAuthenticatorProvider 내부
// MemberPrincipalDetailsService 선언
@Autowired
MemberPrincipalDetailService memberPrincipalDetailService;
// in memory 방식으로 인증 처리를 진행 하기 위해 기존엔 Override 하여 구현했지만
// Spring Security 5.7.0 버전부터는 AuthenticationManagerBuilder를 직접 생성하여
// AuthenticationManager를 생성해야 한다.
@Autowired
public void configure (AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(memberAuthenticatorProvider);
}
// 5.7.0 부터 Override 하지 않고
// SecurityFilterChain을 직접 생성하여 구현
// 그 외 authorizeRequests 가 deprecated 되었기 때문에
// authorizeHttpRequests 로 변경
@Bean
public SecurityFilterChain memberSecurityFilterChain (HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeHttpRequests(authorize -> {
try {
authorize
.requestMatchers("/css/**", "/images/**", "/js/**", "/member/login/**", "/member/attachment/**", "/member/files/**")
.permitAll() // 해당 경로는 인증 없이 접근 가능
.requestMatchers("/member/**") // 해당 경로는 인증이 필요
.hasRole("MEMBER") // ROLE 이 MEMBER 가 포함된 경우에만 인증 가능
.and()
.formLogin()
.loginPage("/member/login/loginForm") // 로그인 페이지 설정
.loginProcessingUrl("/member/login/login") // 로그인 처리 URL 설정
.defaultSuccessUrl("/member/main") // 로그인 성공 후 이동할 페이지
// .successHandler(new MemberAuthSuccessHandler()) // 로그인 성공 후 처리할 핸들러
.failureHandler(new MemberAuthFailureHandler()) // 로그인 실패 후 처리할 핸들러
.permitAll()
.and()
.logout()
.logoutUrl("/member/login/logout") // 로그아웃 처리 URL 설정
.logoutSuccessUrl("/member/login/loginForm?logout=1") // 로그아웃 성공 후 이동할 페이지
.deleteCookies("JSESSIONID"); // 로그아웃 후 쿠키 삭제
} catch (Exception e) {
throw new RuntimeException(e);
}
});
http.rememberMe()
.key("namhyeok") // 인증 토큰 생성시 사용할 키
.tokenValiditySeconds(60 * 60 * 24 * 7) // 인증 토큰 유효 시간 (초)
.userDetailsService(memberPrincipalDetailService) // 인증 토큰 생성시 사용할 UserDetailsService
.rememberMeParameter("remember-me"); // 로그인 페이지에서 사용할 파라미터 이름
return http.build();
}
}
실행 결과입니다.
테스트 아이디 : member1 , 비밀번호 : 1111
자동로그인 체크후 로그인 시도
로그인 성공화면
자동 로그인 체크 후 remember-me 라는 쿠키가 생성, JSESSIONID를 지워도 remember-me 가 있어 다시 생성됨
로그인 성공 후 담겨있는 데이터를 model에 담은뒤 thymeleaf로 출력
다음 Spring Security 글은 DB에 저장되어있는 권한을 동적으로 부여하는 법에 대해 작성하겠습니다.
이번엔 두번째 글로 자바에서 여러개의 파일을 하나의 zip으로 압축하여 다운로드하는 과정을 진행해 보려고 합니다.
IDE : intelliJ
자바 : Java 17
스프링 부트 : Spring Boot 3.0.2
템플릿 엔진 : Thymeleaf
빌드 도구 : Gradle
그외 Lombok , spring boot web
작성자의 OS는 Mac입니다. ( Window의 경우 파일 디렉토리 확인 바랍니다. )
src 하위 패키지 구조 입니다.
우선 DTO 입니다.
압축 될 파일의 이름과 파일을 담아둘 List 변수를 같이 선언해둡니다.
@Getter
@Setter
public class DownloadDto {
private String zipFileName; // 압축될 파일 이름 (xxxx.zip)
private List<String> sourceFiles; // 압축될 파일 리스트
}
바로 실제 압축 동작이 실행되는 소스 입니다.
@Component
@RequiredArgsConstructor
public class DownloadUtil {
public void downloadZip(DownloadDto downloadDto, HttpServletResponse response) {
// 압축될 파일명이 존재하지 않을 경우
if(downloadDto.getZipFileName() == null || "".equals(downloadDto.getZipFileName()))
throw new IllegalArgumentException("파일명이 존재하지 않습니다.");
// 파일이 존재하지 않을 경우
if(downloadDto.getSourceFiles() == null || downloadDto.getSourceFiles().size() == 0)
throw new IllegalArgumentException("파일이 존재하지 않습니다.");
// ======================== 파일 다운로드 위한 response 세팅 ========================
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=" + new String(downloadDto.getZipFileName().getBytes(StandardCharsets.UTF_8)) + ".zip;");
response.setStatus(HttpServletResponse.SC_OK);
// =============================================================================
// 본격적인 zip 파일을 만들어 내기 위한 로직
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())){
// List<String> 변수에 담아두었던 파일명을 검색한다
for (String sourceFile : downloadDto.getSourceFiles()) {
Path path = Path.of(sourceFile);
try (FileInputStream fis = new FileInputStream(path.toFile())) {
// 압축될 파일명을 ZipEntry에 담아준다
ZipEntry zipEntry = new ZipEntry(path.getFileName().toString());
// 압축될 파일명을 ZipOutputStream 에 담아준다
zos.putNextEntry(zipEntry);
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) >= 0) {
zos.write(buffer, 0, length);
}
} catch (FileNotFoundException e) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
throw new IllegalArgumentException("파일 변환 작업중, [ " + sourceFile + " ] 파일을 찾을 수 없습니다.");
} catch (IOException e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
throw new IllegalArgumentException("파일 변환 작업중, [ " + sourceFile + " ] 파일을 다운로드 할 수 없습니다.");
} finally {
// ZipOutputStream 에 담아둔 압축될 파일명을 flush 시켜준다
zos.flush();
// ZipOutputStream 에 담아둔 압축될 파일명을 close 시켜준다
zos.closeEntry();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// response 에 담아둔 파일을 flush 시켜준다
response.flushBuffer();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
파일의 경로를 찾은 후 ZipEntry에 담아 압축파일을 생성하는 과정입니다
.
햇갈리실 수 있는 List<String> 에 변수를 담은 컨트롤러 부분입니다.
@Controller
@RequiredArgsConstructor
public class DownloadController {
private final DownloadUtil downloadUtil;
@GetMapping("/download/zip")
public void downloadZip(HttpServletResponse response) {
// 파일 작업처리를 위한 DTO 선언
DownloadDto downloadDto = new DownloadDto();
// 압축할 파일 일므 지정
downloadDto.setZipFileName("imageZip");
// 파일 경로를 담아둘 List 선언
List<String> downloadFileList = new ArrayList<>();
// ===================================== 여기에 파일 경로를 넣어주세요 ==============================================================
// 작성자는 Mac 기준으로 작성했습니다.
downloadFileList.add("/Users/jeon/Documents/JavaProject/Blog/zip/src/main/resources/static/images/beach.jpg");
downloadFileList.add("/Users/jeon/Documents/JavaProject/Blog/zip/src/main/resources/static/images/bird.jpg");
downloadFileList.add("/Users/jeon/Documents/JavaProject/Blog/zip/src/main/resources/static/images/corgi.jpg");
// Window 예시
// downloadFileList.add("C:\\Users\\jeon\\Documents\\JavaProject\\Blog\\zip\\src\\main\\resources\\static\\images\\beach.jpg");
// downloadFileList.add("C:\\Users\\jeon\\Documents\\JavaProject\\Blog\\zip\\src\\main\\resources\\static\\images\\bird.jpg");
// downloadFileList.add("C:\\Users\\jeon\\Documents\\JavaProject\\Blog\\zip\\src\\main\\resources\\static\\images\\bird.jpg");
downloadDto.setSourceFiles(downloadFileList);
// ============================================================================================================================
// 데이터를 담은 DTO 를 압축 파일 생성 및 다운로드를 위한 메소드에 전달
try {
downloadUtil.downloadZip(downloadDto, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
첫 글로 Java PDF 생성시 많이 사용하고 있는 itext를 이용하여 HTML페이지를 CSS와 폰트도 함께 적용하여,
PDF를 만들고 또한 라이브 서버 jar 배포시 대응 하기 위한 내용도 같이 포함하여 진행해 보겠습니다.
우선 시작하기에 앞서 환경은 아래와 같습니다.
IDE : intelliJ
자바 : Java 11
스프링 부트 : Spring Boot 2.7.7
템플릿 엔진 : Thymeleaf
빌드 도구 : Gradle
그외 Lombok , spring boot web
작성자의 OS는 Mac입니다. ( Window의 경우 파일 디렉토리 확인 바랍니다. )
사용한 itext 라이브러리 입니다.
// ====================================================================================================
// HTML TO PDF LIBRARY
implementation 'com.itextpdf:itextpdf:5.5.13.3'
implementation 'com.itextpdf.tool:xmlworker:5.5.13.3'
implementation 'com.itextpdf:pdfa:7.2.3'
// ====================================================================================================
@Getter
@Setter
public class ItextPdfDto {
private String pdfCode; // pdf 종류가 여러개일 경우 html 을 구분하기 위한 코드
private String pdfFilePath; // pdf 파일이 저장될 경로
private String pdfFileName; // pdf 파일명
}
이어서
ItextPdfUtil 파일에 하나씩 기술 하겠습니다.
임의로 정의된 html을 담아둘 메소드입니다
* 현재 글에선 hyeok만 사용 할 예정입니다*
// 사용할 html 코드를 가져오는 메소드
public String getHtml(String code) {
String return_html = "";
switch (code) {
case "jeon" :
return_html = "<html>" +
"<body>" +
"<h1>jeon</h1>" +
"</body>" +
"</html>";
break;
case "nam" :
return_html = "<html>" +
"<body>" +
"<h1>nam</h1>" +
"<p>CSS 테스트 입니다.</p>" +
"</body>" +
"</html>";
break;
case "hyeok" :
return_html = "<html>" +
"<body>" +
"<h1>hyeok</h1>" +
"<p>이미지 테스트 합니다.</p>" +
"<img src='http://localhost:8080/images/test.png' />" +
"</body>" +
"</html>";
break;
}
return return_html;
}
실제 itext가 사용되는 메소드 입니다 ( ItextPdfUtil 안에 있습니다 )
/*
* iText 라이브러리를 사용한 PDF 파일 생성
* CSS , Font 설정 기능 포함
* */
public void createPDF(ItextPdfDto itextPdfDto) {
// 최초 문서 사이즈 설정
Document document = new Document(PageSize.B4, 30, 30, 30, 30);
try {
// PDF 파일 생성
PdfWriter pdfWriter = PdfWriter.getInstance(document, new FileOutputStream(itextPdfDto.getPdfFilePath()+itextPdfDto.getPdfFileName()));
// PDF 파일에 사용할 폰트 크기 설정
pdfWriter.setInitialLeading(12.5f);
// PDF 파일 열기
document.open();
// XMLWorkerHelper xmlWorkerHelper = XMLWorkerHelper.getInstance();
// CSS 설정 변수 세팅
CSSResolver cssResolver = new StyleAttrCSSResolver();
CssFile cssFile = null;
try {
/*
* CSS 파일 설정
* 기존 방식은 FileInputStream을 사용했으나, jar 파일로 빌드 시 파일을 찾을 수 없는 문제가 발생
* 따라서, ClassLoader를 사용하여 파일을 읽어오는 방식으로 변경
*/
InputStream cssStream = getClass().getClassLoader().getResourceAsStream("static/css/ItextPdf.css");
// CSS 파일 담기
cssFile = XMLWorkerHelper.getCSS(cssStream);
// cssFile = XMLWorkerHelper.getCSS(new FileInputStream("src/main/resources/static/css/test.css"));
} catch (Exception e) {
throw new IllegalArgumentException("PDF CSS 파일을 찾을 수 없습니다.");
}
if(cssFile == null) {
throw new IllegalArgumentException("PDF CSS 파일을 찾을 수 없습니다.");
}
// CSS 파일 적용
cssResolver.addCss(cssFile);
// PDF 파일에 HTML 내용 삽입
XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS);
/*
* 폰트 설정
* CSS 와 다르게, fontProvider.register() 메소드를 사용하여 폰트를 등록해야 함
* 해당 메소드 내부에서 경로처리를 하여 개발, 배포 시 폰트 파일을 찾을 수 있도록 함
* */
try {
fontProvider.register("static/font/AppleSDGothicNeoR.ttf", "AppleSDGothicNeo");
} catch (Exception e) {
throw new IllegalArgumentException("PDF 폰트 파일을 찾을 수 없습니다.");
}
if(fontProvider.getRegisteredFonts() == null) {
throw new IllegalArgumentException("PDF 폰트 파일을 찾을 수 없습니다.");
}
// 사용할 폰트를 담아두었던 내용을
// CSSAppliersImpl에 담아 적용
CssAppliers cssAppliers = new CssAppliersImpl(fontProvider);
// HTML Pipeline 생성
HtmlPipelineContext htmlPipelineContext = new HtmlPipelineContext(cssAppliers);
htmlPipelineContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
// ========================================================================================
// Pipelines
PdfWriterPipeline pdfWriterPipeline = new PdfWriterPipeline(document, pdfWriter);
HtmlPipeline htmlPipeline = new HtmlPipeline(htmlPipelineContext, pdfWriterPipeline);
CssResolverPipeline cssResolverPipeline = new CssResolverPipeline(cssResolver, htmlPipeline);
// ========================================================================================
// ========================================================================================
// XMLWorker
XMLWorker xmlWorker = new XMLWorker(cssResolverPipeline, true);
XMLParser xmlParser = new XMLParser(true, xmlWorker, StandardCharsets.UTF_8);
// ========================================================================================
/* HTML 내용을 담은 String 변수
주의점
1. HTML 태그는 반드시 닫아야 함
2. xml 기준 html 태그 확인( ex : <p> </p> , <img/> , <col/> )
위 조건을 지키지 않을 경우 DocumentException 발생
*/
String htmlStr = getHtml(itextPdfDto.getPdfCode());
// HTML 내용을 PDF 파일에 삽입
StringReader stringReader = new StringReader(htmlStr);
// XML 파싱
xmlParser.parse(stringReader);
// PDF 문서 닫기
document.close();
// PDF Writer 닫기
pdfWriter.close();
} catch (DocumentException e1) {
throw new IllegalArgumentException("PDF 라이브러리 설정 에러");
} catch (FileNotFoundException e2) {
e2.printStackTrace();
throw new IllegalArgumentException("PDF 파일 생성중 에러");
} catch (IOException e3) {
e3.printStackTrace();
throw new IllegalArgumentException("PDF 파일 생성중 에러2");
} catch (Exception e4) {
e4.printStackTrace();
throw new IllegalArgumentException("PDF 파일 생성중 에러3");
}
finally {
try {
document.close();
} catch (Exception e) {
System.out.println("PDF 파일 닫기 에러");
e.printStackTrace();
}
}
}
추가적인 메소드로 PDF 파일이 매번 생성되지 않게 하기 위한 메소드 입니다.
/*
* PDF 유무를 체크한 후
* PDF 파일이 없을 경우 PDF 파일 생성 메소드 실행
*/
public File checkPDF (ItextPdfDto pdfDto) {
File file = new File(pdfDto.getPdfFilePath(),pdfDto.getPdfFileName());
int fileSize = (int) file.length();
if (fileSize == 0) {
createPDF(pdfDto);
file = new File(pdfDto.getPdfFilePath(),pdfDto.getPdfFileName());
}
return file;
}
생성된 파일을 다운로드 하기위한 컨트롤러 입니다.
@Controller
@RequiredArgsConstructor
public class ItextPdfController {
@Autowired
private final ItextPdfUtil itextPdfUtil;
@RequestMapping("/attachment/pdf")
public void pdfDownload(HttpServletResponse response) {
// 미리 준비한 DTO 선언
ItextPdfDto itextPdfDto = new ItextPdfDto();
// pdf 파일이 저장될 경로 ( Mac 기준 )
itextPdfDto.setPdfFilePath("/Users/jeon/Documents/JavaProject/Blog/pdf/");
// pdf 파일이 저장될 경로 ( Windows 기준 )
// itextPdfDto.setPdfFilePath("C:\\Users\\hyeok\\Desktop\\pdf");
// pdf 파일명 ( 테스트를 위해 랜덤으로 생성 )
itextPdfDto.setPdfFileName(new Random().nextInt() + ".pdf");
// itextPdfDto.setPdfFileName("test.pdf");
// getHtml 에서 호출될 코드명
itextPdfDto.setPdfCode("hyeok");
// ======================= PDF 존재 유무 체크 =======================
// 없다면 PDF 파일 만들기
File file = itextPdfUtil.checkPDF(itextPdfDto);
int fileSize = (int) file.length();
// ===============================================================
// ===============================================================
// 파일 다운로드를 위한 header 설정
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename="+itextPdfDto.getPdfFileName()+";");
response.setContentLengthLong(fileSize);
response.setStatus(HttpServletResponse.SC_OK);
// ===============================================================
// 파일 다운로드
BufferedInputStream in = null;
BufferedOutputStream out = null;
// PDF 파일을 버퍼에 담은 후 다운로드
try{
in = new BufferedInputStream(new FileInputStream(file));
out = new BufferedOutputStream(response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
try {
byte[] buffer = new byte[4096];
int read = 0;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
Objects.requireNonNull(out).flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
이상으로 Java itext를 사용한 PDF만들기 ( HTML을 PDF로 만들기 ) 입니다.