서론 및 변명
회원가입하기를 누르면 비동기 방식으로 이메일 인증 메일이 발송되는 것을 구현해보았다. 처음엔 쓰레드를 사용하지 않고 구현했더니 메일 보내는 시간이 5초나 걸리는 것이 아닌가.. 그래서 @Async 어노테이션을 이용해 비동기로 구현하고자 했다. 아직 이해가 부족한 부분이라 설명이 빈약한 부분 양해바랍니다... 이상한 부분이 있다면 지적 환영,,
테이블
회원 테이블은 아래와 같이 구성되어 있다. 이메일 인증과 관련된 건 USERKEY 속성뿐
Dependency
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.5.0-b01</version>
</dependency>
궁금해서 spring-context-support에 대해 찾아봤는데 spring document에서는 외부 라이브러리를 스프링 어플리케이션 컨텍스트에 통합하는데 도움을 주는 repository라고 나와 있다. (이렇게 해석하는게 맞나..)
spring-context-support provides support for integrating common third-party libraries into a Spring application context for caching (EhCache, Guava, JCache), mailing (JavaMail), scheduling (CommonJ, Quartz) and template engines (FreeMarker, JasperReports, Velocity).
코드설명
servlet-context.xml : JavaMailSenderImpl 빈 등록
<!-- 회원가입 메일 인증 gmail -->
<beans:bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<beans:property name="host" value="smtp.gmail.com"/>
<beans:property name="port" value="587"/>
<beans:property name="username" value="이메일주소"/>
<beans:property name="password" value="비밀번호"/>
<beans:property name="defaultEncoding" value="utf-8"/>
<beans:property name="javaMailProperties">
<beans:props>
<beans:prop key="mail.transport.protocol">smtp</beans:prop>
<beans:prop key="mail.smtp.auth">true</beans:prop>
<beans:prop key="mail.smtp.starttls.enable">true</beans:prop>
<beans:prop key="mail.debug">true</beans:prop>
</beans:props>
</beans:property>
</beans:bean>
- username과 password에 본인 gmail 주소와 비밀번호를 적어주면 된다. 이제 그 이메일이 발신자가 되어 회원들에게 메일을 홀홀 뿌리게 된다.
환경설정이 다 끝났다면 총 4개의 클래스를 작성한다.
- 이메일 난수 만드는 클래스
- AysncConfigurer를 상속받은 클래스
- 비동기 task 관리하는 클래스
- 컨트롤러 클래스
UserMailSendService.java
public class UserMailSendService {
private static int size = 20;
//이메일 난수 만드는 메서드
public static String random() {
Random ran = new Random();
StringBuffer buf = new StringBuffer();
int num = 0;
do {
num = ran.nextInt(75)+48;
if((num>=48 && num<=57) || (num>=65 && num <=90) || (num>=97 && num<=122)) {
buf.append((char)num);//숫자, 영문자만
}
else continue;
} while(buf.length() < size); //20글자
return buf.toString();
}
}
- 여기서 생성된 난수를 멤버 테이블에 저장했다가, 이메일에서 인증 버튼을 눌렀을 때 userkey를 특정 문자열로 업데이트 하여 이메일 인증 여부를 판단한다.
AsyncConfig.java : AsyncConfigurer를 상속받은 클래스
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer{
private static int TASK_CORE_POOL_SIZE = 2;//기본쓰레드사이즈
private static int TASK_MAX_POOL_SIZE = 4;//최대쓰레드사이즈
private static int TASK_QUEUE_CAPACITY = 0;//Max쓰레드가 동작하는 경우 대기하는 큐 사이즈
private static String EXECUTOR_BEAN_NAME = "executor";
@Resource(name="executor")
private ThreadPoolTaskExecutor executor;
@Bean(name="executor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
setExecutor(executor);
return executor;
}
public void setExecutor(ThreadPoolTaskExecutor executor) {
executor.setCorePoolSize(TASK_CORE_POOL_SIZE);
executor.setMaxPoolSize(TASK_MAX_POOL_SIZE);
executor.setQueueCapacity(TASK_QUEUE_CAPACITY);
executor.setBeanName(EXECUTOR_BEAN_NAME);
executor.initialize();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
- 비동기 구현에 대해 참조한 글은 아래에(아주아주 쉽고 잘 정리해놓으셔서.. 감사합니다..)
AsyncTaskService.java
@Service("asyncTask")
public class AsyncTaskService { // 비동기 task를 관리하는 클래스
@Resource(name = "memberService")
private MemberServiceImpl memberService;
@Resource(name="mailSender")
private JavaMailSender mailsender;
@Async("executor")
public void executor(String email, String contextPath) throws MessagingException {
System.out.println("========THREAD START========");
String key = UserMailSendService.random(); //난수얻기
//유저 인증키 db에 저장(난수)
Map map = new HashMap();
map.put("email",email);
map.put("userKey", key);
memberService.updateUserKey(map);
//실제 메일 보내는 부분
MimeMessage mail = mailsender.createMimeMessage();
String htmlStr = "<!DOCTYPE html><html><head><meta charset=\"UTF-8\">"
+ "<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css\">"
+ "<style>@font-face {font-family: 'Chosunilbo_myungjo';src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/Chosunilbo_myungjo.woff') format('woff');font-weight: normal;font-style: normal;}</style>"
+ "</head><body><div class='row justify-content-center'>"
+ "<div class='col-12 col-md-6 col-lg-4' style='position: relative'>"
+ "<a href='http://localhost:8080"+ contextPath +"/Auth/EmailAuth.do?email="
+ email +"&userKey=" + key + "' style='font-size:1.4em;'>"
+ "<img src='https://ifh.cc/g/tvBEaI.jpg' alt='이미지'></a>"
+ "</div></div></body></html>";
mail.setSubject("[본인인증]집밥구리의 인증메일입니다","utf-8");
mail.setText(htmlStr,"utf-8","html");
mail.addRecipient(RecipientType.TO, new InternetAddress(email));
mailsender.send(mail);
System.out.println("========THREAD END========");
}
}
- 비동기로 실행할 메소드 구현
- 사용자가 입력한 email과 난수생성기에서 생성한 난수를 db에 저장한다.
무료 로고? 배너? 생성하기 사이트에서 이미지 만든 다음에 무료로 이미지를 호스팅 해주는 사이트에(일정기간) 올려서 인증 버튼을 만들었다. (무료에 미쳐버린 나..)
이미지를 클릭하면 아래 코드에 쓴 것과 같이 사용자 email과 난수를 파라미터로 가지고 사이트로 이동한다.
"<a href='http://localhost:8080"+ contextPath +"EmailAuth.do?email=" + email +"&userKey=" + key + "' style='font-size:1.4em;'>" + "<img src="https://ifh.cc/g/tvBEaI.jpg' alt='이미지'></a>"
AuthController.java : 회원가입, 인증 메소드 구현
@Controller
@RequestMapping("/Auth/")
public class AuthController {
@Resource(name="asyncTask")
private AsyncTaskService asyncService;
@RequestMapping(value="Register.do", method=RequestMethod.POST)
public String registerpost(@RequestParam Map map,
MultipartHttpServletRequest mreq) throws IllegalStateException, IOException, MessagingException {
//회원가입 로직 작성
/*비동기로 인증 메일 보내기 메서드 호출*/
try {
asyncService.executor(email, mreq.getContextPath());
} catch(Exception e) {
e.printStackTrace();
}
return "/Auth/SuccessRegi.tiles"; // welcome 페이지로 이동
}
@RequestMapping("EmailAuth.do") // 메일 사용자가 인증하기 누르면 실행되는 메소드
public String emailauth(@RequestParam Map map,HttpSession session) {
int isLogin = memberService.updateUserKeyYes(map);
if(isLogin==1) {
int affectedAuth = memberService.insertAuth(map); //권한부여
}
session.setAttribute("isLogin", isLogin);
System.out.println("세션:" + session.getAttribute("isLogin"));
return "/Auth/Login.tiles";
}
}
- registerpost 메소드는 회원가입 로직을 수행하고(db저장 등) 인증메일을 보내는 역할을 한다.
asyncService.executor(email, mreq.getContextPath());
- 사용자가 본인의 이메일에서 인증 메일의 이미지를 클릭하면 emailauth 메소드가 실행된다. 그리고 userkey를 'Y'라는 문자열로 업데이트 한다.
int isLogin = memberService.updateUserKeyYes(map);
- 스프링 시큐리티를 사용하였기 때문에 인증까지 성공한 사용자일 때만 권한 부여를 한다.
if(isLogin==1) int affectedAuth = memberService.insertAuth(map);
auth.xml : 마이바티스
<update id="updateUserKey" parameterType="Map">
UPDATE member SET userkey = #{userKey} WHERE email = #{email}
</update>
<update id="updateUserKeyYes" parameterType="Map">
update member set userkey ='Y' where email= #{email} and userkey= #{userKey}
</update>
이렇게 해서 이메일 인증으로 회원가입 완료하기를 완성해보았다.
'공부기록 > 스프링 프레임워크' 카테고리의 다른 글
Spring Boot Security session timeout not working (0) | 2022.12.28 |
---|---|
[스프링] 파일 용량별 전송 시간 (0) | 2021.06.12 |
이클립스 build path 없어짐..? (프로젝트 생성시) (0) | 2021.05.18 |
[스프링] 화면에 이미지 출력하기(url로 접근) (0) | 2021.04.19 |
[스프링] 공통 java class 라이브러리화 하기 (0) | 2021.04.14 |