사이드프로젝트를 진행하면서 '메일 발송' 기능을 담당하게 됐다.
회사 소스에 이미 세팅된 걸 파악하기 보단, 내가 처음부터 세팅하면서 알아가고 싶어서 자원했다.
하지만 뜻대로 되지 않아 의외로 시간을 많이 쏟았고 원인은 다음 세 가지였다.
실질적으로 세팅하는 방법은 다른 포스팅에 적어두려고 한다.
이번엔 시간을 많이 쓴 원인과 알게된 것들을 정리하고자 한다.
가장 근본적인 원인이었다.
application.properties에 smtp 관련 값을 추가해두고 바인딩이 안 되는 것을 모른 채 테스트를 진행했다.
우선 yaml과 properties는 다음과 같은 차이점이 있다.
어떤 형식을 쓸 것인지는 취향 차이인 것 같다.
부끄럽게도 아래 이미지처럼 세팅해두면 자동으로(?) MailProperties에 바인딩되는 줄 알았다...!
모든 과정을 디버깅 찍어가면서 확인하다가 알게된 것이 저렇게만 둔다고 알아서 주입되는 것이 아니라는 점이었다.
방법은 아래와 같다.
@Getter
@Setter
@Component
public final class MailProperties {
// SMTP 서버
@Value("${spring.mail.host}")
private String host;
// 계정
@Value("${spring.mail.username}")
private String username;
}
이렇게 필드값마다 어노테이션을 달아주고, Placeholder나 SpEL을 명시해주는 방식이다.
다만, 객체가 생성된 후에 주입이 되기 때문에 객체 생성자가 실행되는 시점에선 @Value 값이 null이 되므로 주의할 필요가 있다.
@Getter
@Setter
@Configuration
@ConfigurationProperties("mail")
public final class MailProperties {
// SMTP 서버
private String host;
// 계정
private String username;
//smtp
private Properties properties;
@Getter
@Setter
public static class Properties {
private Smtp smtp;
@Getter
@Setter
public static class Smtp {
private boolean auth;
}
}
}
이런 방식으로 정의한다.
prefix를 적어줘야하며, inner class를 사용하게 되는 경우 이름을 똑같이 일치 시켜야 한다.
또, setter를 반드시 정의해줘야 한다.
@Value보다 간결하고, inner class 사용 시 좀 더 간편하다는 장점이 있다.
spring boot 2.2부터는 @ConfigurationProperties가 달린 클래스를 변경 불가능하도록 @ConstructorBinding을 쓸 수 있게 됐다.
그래서 final 필드에 대해 값을 주입해준다.
@Getter
@RequiredArgsConstructor
@ConfigurationProperties("mail")
public final class MailProperties {
// SMTP 서버
private final String host;
// 계정
private final String username;
//smtp
private final Properties properties;
@Getter
@RequiredArgsConstructor
public static final class Properties {
private final Smtp smtp;
@Getter
@RequiredArgsConstructor
public static final class Smtp {
private final boolean auth;
}
}
}
이 방식은 MailProperties 클래스에 직접적으로 bean을 만들어주지 않는다.
따라서 @EnableConfigurartionProperties를 이용해서 클래스 타입을 명시해줘야한다.
프로젝트에는 @Value 방식을 썼다.
1) 대규모 프로젝트가 아니기에 yml 파일이 여러 개 생성되지 않는다는 점
2) 메일 관련 설정을 MailProperties 외에 다른 클래스에서 산발적으로 호출하지 않는다는 점
3) 세팅에 좀 더 많은 시간을 쓸 수 없었다는 점
을 이유로 그렇게 사용했다.
프로젝트 마무리 시점에 시간적 여유가 된다면 @Value 외에 다른 방식으로 바인딩하게 변경해보고 싶다.
+) 3번 시간 이슈에 대해 좀 더 설명하자면 @ConfigurationProperties를 쓰려고 설정했지만 주입이 안 됐다. @EnableConfigurartionProperties 도 달아줬으나 안 돼 해결하고 싶었다.
하지만 혼자하는 프로젝트가 아니어서 다른 팀원들이 merge를 기다리고 있는 상황이라 우선 @Value로 마무리 지었다.
참고한 블로그 글들, 회사 소스들 모두 방식은 각자에 맞게 했지만 원리는 동일했다.
하지만 각 단계별로 왜 이렇게 세팅 하는지를 이해하지 못하고 베끼다보니 원인 파악도 느렸다.
결국 각 단계별로 필요한 설정 순서는 아래와 같다.
1) .yml 혹은 .properties에 필요한 설정 값을 넣어준다
2) MailProperties에 값을 매핑해준다
3) MailConfig에 javaMailService 메소드를 @Bean 등록 해준다
3-1) javaMailService 메소드 내 JavaMailSender 객체를 생성하고, 설정값을 set해준다
3-2) Properties 객체에 smtp.auth, starttls 등도 put 해준다
3-3) javaMailSender에 session도 세팅해준다
4) password 보안 때문에 Authenticator를 상속받아 MailAuthenticator를 만들어준다
5) PasswordAuthentication 객체를 재정의 해준다
각 순서에 대해 전체적인 그림을 그리고, 현재 어떤 단계에 있는지를 생각하면서 코드를 작성했으면 문제가 발생하는 위치를 빠르게 잡을 수 있지 않았을까 싶다.
대다수의 블로그를 보면, 메일 발송이 안되면 '보안수준이 낮은 앱의 액세스 허용'을 해주라고 나온다.
하지만 구글 정책상 이 허용값 설정 자체가 불가능하게 바뀌었다.
그러다가 발견한 stackOverflow 글.
친절하게 캡쳐를 첨부해 설명해줘서 이해하고 해결했다.
회고 아닌 회고글이 됐지만 이번 작업으로 느낀 점이 많았다.
첫째는 이미 선배 개발자들이 세팅해둔 회사 소스를 눈으로 보는 것보다 내가 직접 설정하면서 겪어야 부족한 부분을 뼈저리게 느낀다는 것이다.
두 번째는 기본기를 확실하게 다져야 하고, 구멍이 숭숭 나 있다는 것이었다.
이렇게 느끼기 위해서 사이드 프로젝트를 시작했는데 역시나 하길 잘 했다.
앞으로 배우고 느낄 게 많아서 행복하다.