인증메일을 하는 이유는 무분별한 사용자들의 접속을 막을 수 있고 원치 않는 액세스를 방지 할 수 있다. 또한 온라인 서비스에서 보안 및 사용자에 대한 신원 확인이 가능하다.
인증메일을 보내는 시점은 ?
회원가입 양식 입력 후 member테이블 저장 할때 member_auth 테이블에도 저장 해주고, 인증메일 유효시간도 지정해주자
회원가입 인증메일 보내는 건 어떻게 해야 할까?
DB 테이블 생성(member_auth)
| 컬럼명 | Comment |
|---|---|
| auth_seq | 자동시퀀스(AI) |
| member_seq | 회원번호 |
| auth_num | 인증문자 |
| auth_uri | 인증용난수(링크) |
| reg_dtm | 등록일자 |
| expire_dtm | 인증유효시간 |
| auth_yn | 인증여부(Y/N) |
기존 프로젝트에서 사용하던 JoinDao → MemberDao(Alt+Shift+r) 수정
package com.portfolio.www.dao;
import java.util.HashMap;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class MemberDao extends JdbcTemplate {
public MemberDao() {}
public MemberDao(DataSource ds) {
super(ds);
}
public int join(HashMap<String,String> params) {
String sql = "INSERT INTO forum.`member` "
+ "( member_id, passwd, member_nm, email, auth_yn, pwd_chng_dtm, join_dtm) "
+ "VALUES( '" + params.get("memberId")
+ "', '" + params.get("passwd")
+ "', '', '" + params.get("email")
+ "', '', '', DATE_FORMAT(now(),'%Y%m%d%H%i%s'))";
return update(sql);
}
public int getmemberSeq(String memberId) {
String sql = " SELECT member_seq FROM member WHERE member_id = '" + memberId + "'";
return queryForObject(sql, Integer.class);
/* queryForObject
This method is useful for running static SQL with a known outcome.
The query is expected to be a single row/single column query;
the returnedresult will be directly mapped to the corresponding
object type.
*/
}
} <bean id="joinService" class="com.portfolio.www.service.JoinService" >
<property name="memberDao" ref="memberDao" />
</bean>
<bean id="memberDao" class="com.portfolio.www.dao.MemberDao" >
<constructor-arg ref="dataSource" />
</bean> private MemberDao memberDao;
public void setJoinDao(MemberDao memberDao) {
System.out.println("\n\njoinService : " + memberDao + "in joinService \n\n");
this.memberDao = memberDao;
} package com.portfolio.www.dto;
public class MemberAuthDto {
private int authSeq;
private int memberSeq;
private String authNum;
private String authUri;
private String regDtm;
/* db타입은 bigint인데 자바에서 타입이 long인 이유는?
bigint 타입의 시간데이터는 자바에서 일반적으로 사용하며,
* 데이터 유지 보수성과 코드 일관성을 유지하기 위한것 */
private long expireDtm; // 시간
private String authYn;
public int getAuthSeq() {
return authSeq;
}
public void setAuthSeq(int authSeq) {
this.authSeq = authSeq;
}
public int getMemberSeq() {
return memberSeq;
}
public void setMemberSeq(int memberSeq) {
this.memberSeq = memberSeq;
}
public String getAuthNum() {
return authNum;
}
public void setAuthNum(String authNum) {
this.authNum = authNum;
}
public String getAuthUri() {
return authUri;
}
public void setAuthUri(String authUri) {
this.authUri = authUri;
}
public String getRegDtm() {
return regDtm;
}
public void setRegDtm(String regDtm) {
this.regDtm = regDtm;
}
public long getExpireDtm() {
return expireDtm;
}
public void setExpireDtm(long expireDtm) {
this.expireDtm = expireDtm;
}
public String getAuthYn() {
return authYn;
}
public void setAuthYn(String authYn) {
this.authYn = authYn;
}
}
MemberAuthDao 생성
package com.portfolio.www.dao;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import com.portfolio.www.dto.MemberAuthDto;
import com.portfolio.www.dto.MemberAuthRowMapper;
public class MemberAuthDao extends JdbcTemplate {
public MemberAuthDao() {}
public MemberAuthDao(DataSource ds) {
super(ds);
}
public int addAuthInfo(MemberAuthDto dto) {
String sql = " INSERT INTO forum.member_auth "
+ " (member_seq, auth_num, auth_uri, reg_dtm, expire_dtm, auth_yn) "
+ " VALUES(" + dto.getMemberSeq()
+ " , '', '" + dto.getAuthUri()
+ "', DATE_FORMAT(now(),'%Y%m%d%H%i%s'), "
+ "" + dto.getExpireDtm()
+ ", 'N'); ";
return update(sql);
}
public MemberAuthDto getMemberAuthDto(String uri) {
String sql = " SELECT auth_seq, member_seq, auth_num, auth_uri, reg_dtm, expire_dtm, auth_yn "
+ " FROM forum.member_auth "
+ " WHERE auth_uri = '"+uri+"'"
+ " AND auth_yn = 'N';";
return query(sql, new MemberAuthRowMapper());
/*
return queryForObject(sql,MemberAuthDto.class);
사용이 안되는것이다 !! 가능한것은 기본형 레퍼 클래스만 가능하다
// 기본형을 위한 single row/column
*/
}
}
<bean id="memberAuthDao" class="com.portfolio.www.dao.MemberAuthDao" >
<constructor-arg ref="dataSource" />
</bean> private MemberAuthDao memberAuthDao;
public void setMemberAuthDao(MemberAuthDao memberAuthDao) {
this.memberAuthDao = memberAuthDao;
}package com.portfolio.www.dto;
public class EmailDto {
private String from;
private String receiver;
private String text;
private String subject;
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
} <!--이메일 전송을 위한 라이브러리 추가-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.34</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>```xml
<properties>
<spring.version>5.3.34</spring.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.5.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/at.favre.lib/bcrypt -->
<dependency>
<groupId>at.favre.lib</groupId>
<artifactId>bcrypt</artifactId>
<version>0.10.2</version>
</dependency>
<!--이메일 전송을 위한 라이브러리 추가-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
</dependencies>
``` <!-- 이메일 전송하기 위한 bean설정 -->
<!-- bean id 가 mailSender 를 이용하기 위해 설정 -->
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="smtp.naver.com" />
<property name="port" value="587" />
<property name="username" value="네이버ID" />
<property name="password" value="네이버PWD" />
<property name="javaMailProperties">
<props>
<prop key="mail.smtp.starttls.enable">true</prop>
</props>
</property>
</bean>package com.portfolio.www.util;
import javax.mail.internet.MimeMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import com.portfolio.www.dto.EmailDto;
public class EmailUtil {
// property 설정으로 받아보기
private JavaMailSender mailSender;
public void setMailSender(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
public String sendMail(EmailDto email) {
return sendMail(email,false);
}
public String sendMail(EmailDto email, boolean isHtml) {
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(message, true, "UTF-8");
messageHelper.setTo(email.getReceiver());
messageHelper.setFrom(email.getFrom());
messageHelper.setSubject(email.getSubject()); // 메일제목은 생략이 가능하다
messageHelper.setText(email.getText(),isHtml);
mailSender.send(message);
} catch(Exception e){
System.out.println(e);
return "Error";
}
return "Sucess";
}
public boolean checkHtml(String link) {
return link.contains("<") ? true : false;
}
} <bean id="emailUtil" class="com.portfolio.www.util.EmailUtil" >
<property name="mailSender" ref="mailSender" />
</bean>JoinService 작성
package com.portfolio.www.service;
import java.util.Calendar;
import java.util.HashMap;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.portfolio.www.dao.MemberAuthDao;
import com.portfolio.www.dao.MemberDao;
import com.portfolio.www.dto.EmailDto;
import com.portfolio.www.dto.MemberAuthDto;
import com.portfolio.www.util.EmailUtil;
import at.favre.lib.crypto.bcrypt.BCrypt;
public class JoinService {
private static final Logger log = LoggerFactory.getLogger(JoinService.class);
private MemberDao memberDao;
private MemberAuthDao memberAuthDao;
private EmailUtil emailUtil;
public void setMemberDao(MemberDao memberDao) {
this.memberDao = memberDao;
}
public void setMemberAuthDao(MemberAuthDao memberAuthDao) {
this.memberAuthDao = memberAuthDao;
}
public void setEmailUtil(EmailUtil emailUtil) {
this.emailUtil = emailUtil;
}
public int join(HashMap<String,String> params) {
String passwd = params.get("passwd");
String encPasswd = BCrypt.withDefaults().hashToString(12,passwd.toCharArray());
log.info("encPasswd >>>>>>>>> " + encPasswd);
BCrypt.Result result = BCrypt.verifyer().verify(passwd.toCharArray(), encPasswd);
log.info("result.verified >>>>>>> " + result.verified);
params.put("passwd", encPasswd);
int cnt = memberDao.join(params);
/* member 테이블 추가 되었으면 auth 테이블에 추가 하기 위해 구현 */
int memberSeq = memberDao.getmemberSeq(params.get("memberId"));
if(cnt ==1) {
/* 인증 메일구조 만들기 */
MemberAuthDto authDto = new MemberAuthDto();
// memberSeq
authDto.setMemberSeq(memberSeq);
// UUID 사용해서 유니크함을 부여
authDto.setAuthUri(UUID.randomUUID().toString().replaceAll("-", ""));
// 인증유효시간
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MINUTE, 30); // cal 현재시간 기준 (분) + 30
authDto.setExpireDtm(cal.getTimeInMillis()); // long 타입으로 반환,(주어진 날짜or시간을 밀리초 단위로)
// set된 값을 보기 위한 log
log.info("getExpireDtm() : " + authDto.getExpireDtm());
memberAuthDao.addAuthInfo(authDto);
/* 인증 메일 발송 */
EmailDto email = new EmailDto();
email.setFrom("네이버ID");
email.setReceiver(params.get("email"));
email.setSubject("인증하세요");
String html = "<a href='http://localhost:8080/04/emailAuth.do?uri="+authDto.getAuthUri()+"'>인증하기</a>";
log.info("html : " + html);
email.setText(html);
emailUtil.sendMail(email,true);
}
return cnt;
}
}
회원가입 아이디 중복 방지를 위해 DB 변경
member 테이블의 member_id 를 unique key 로 변경