이번엔 회원가입 시 ,이메일 인증을 구현했다.
메일로 인증번호를 받아서 인증번호를 입력 후 인증하는 방법과
메일에서 바로 버튼을 눌러서 인증을 하는 방법을 생각했는데,
사용자가 느끼기엔 버튼을 눌러서 인증하는 방법이 편할 것 같아서 후자의 방법으로 구현했다.
다음 프로젝트땐 전자의 방법으로 구현 할 생각이다. 크게 다르지 않다.
pom.xml에 메일 라이브러리를 추가
<!-- mail library -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
<!-- mail 서포트 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework-version}</version>
</dependency>
MailUtils.java 생성
public class MailUtils {
private JavaMailSender mailSender;
private MimeMessage message;
private MimeMessageHelper messageHelper;
public MailUtils(JavaMailSender mailSender) throws MessagingException {
this.mailSender = mailSender;
message = this.mailSender.createMimeMessage();
messageHelper = new MimeMessageHelper(message, true, "UTF-8");
}
public void setSubject(String subject) throws MessagingException {
messageHelper.setSubject(subject);
}
public void setText(String htmlContent) throws MessagingException {
messageHelper.setText(htmlContent, true);
}
public void setFrom(String email, String name) throws UnsupportedEncodingException, MessagingException {
messageHelper.setFrom(email, name);
}
public void setTo(String email) throws MessagingException {
messageHelper.setTo(email);
}
public void addInline(String contentId, DataSource dataSource) throws MessagingException {
messageHelper.addInline(contentId, dataSource);
}
public void send() {
mailSender.send(message);
}
}
메일전송 라이브러리의 JavaMailSender인터페이스이다.
위부터,
제목 , 내용, 발송자 , 수신자 , send(보내기) 의 setter이다.
tempKey.java 생성
public class TempKey{
private boolean lowerCheck;
private int size;
public String getKey(int size, boolean lowerCheck) {
this.size = size;
this.lowerCheck = lowerCheck;
return init();
}
private String init() {
Random ran = new Random();
StringBuffer sb = new StringBuffer();
int num = 0;
do {
num = ran.nextInt(75) + 48;
if ((num >= 48 && num <= 57) || (num >= 65 && num <= 90) || (num >= 97 && num <= 122)) {
sb.append((char) num);
} else {
continue;
}
} while (sb.length() < size);
if (lowerCheck) {
return sb.toString().toLowerCase();
}
return sb.toString();
}
}
이메일 인증코드를 난수화하는 클래스이다.
이메일인증 구현 후, 이메일 기능으로 아이디/비밀번호 찾기도 구현할 계획이기 때문에 따로 클래스를 만들어서
import해서 사용하려고 한다.
servelt-context.xml
<!-- 회원가입 메일 인증 -->
<beans:bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<beans:property name="host" value="smtp.gmail.com" />
<beans:property name="port" value="587" /><!-- 465 or 25 -->
<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>
이메일 기능라이브러리의 bean 등록과 설정을 해주자.
그리고,
구글 계정설정에 들어가서 보안수준이 낮은 앱의 액세스를 허용해준다.
혹시,
Request processing failed; nested exception is org.springframework.mail.MailSendException: Mail server connection failed; nested exception is javax.mail.MessagingException: Could not convert socket to TLS;
에러가 발생하면
<beans:prop key="mail.smtp.ssl.trust">smtp.gmail.com</beans:prop>
<beans:prop key="mail.smtp.ssl.protocols">TLSv1.2</beans:prop>
prop key를 추가해주자.
클라이언트(브라우저)와 서버(톰캣)의 버전차이 때문에 발생할 수 있는 문제인데,
버전을 1.2로 올려주면 해결된다.
그래도 안되면 방화벽을 끄고 시도해보자.
메일주소와 인증키를 저장할 테이블 생성, 회원테이블에 인증키 생성
create table MP_MEMBER_AUTH(MEMBEREMAIL varchar2(100),AUTHKEY varchar2(50));
alter table mp_member add member_auth number default 0;
commit;
Mapper.xml
<insert id="createAuthKey">
insert into MP_MEMBER_AUTH values(#{memberEmail}, #{authKey})
</insert>
<update id="memberAuth">
<![CDATA[update MP_MEMBER set MEMBER_AUTH = 1 where (select count(*) from MP_MEMBER_AUTH where MEMBER_EMAIL = #{memberEmail}) > 0]]>
</update>
메일인증을 하면 MEMBER_AUTH 칼럼을 기본값0에서 1로 바꿔, 로그인을 허용하는 로직으로 진행하려고 한다.
지난 글에서 아이디 중복검사를 했듯, 이메일도 중복검사를 할 수 있겠으나,
서비스용이 아닌 제작용인 커뮤니티에서 이메일 제한을 해버리면;
개발자인 내가 너무 힘들어지니 이 부분은 생략하겠다..
DAO
public void createAuthKey(String memberEmail,String authKey) throws Exception;
public void memberAuth(String memberEmail) throws Exception;
DAOImlp
@Override
public void createAuthKey(String memberEmail,String authKey) throws Exception{
Map<String, Object> map = new HashMap<String, Object>();
map.put("memberEmail", memberEmail);
map.put("authKey", authKey);
sqlsession.selectOne("memberMapper.createAuthKey", map);
}
@Override
public void memberAuth(String memberEmail) throws Exception{
sqlsession.update("memberMapper.memberAuth", memberEmail);
}
Service
public MemberVO checkLoginBefore(String value) throws Exception;
public void memberAuth(String memberEmail) throws Exception;
ServiceImpl
@Inject
private JavaMailSender mailSender;
@Transactional
@Override
public void register(MemberVO memberVO) throws Exception{
memberDAO.register(memberVO);
String key = new TempKey().getKey(50,false);
memberDAO.createAuthKey(memberVO.getMemberEmail(), key);
MailUtils sendMail = new MailUtils(mailSender);
sendMail.setSubject("[ICEWATER 커뮤니티 이메일 인증메일 입니다.]"); //메일제목
sendMail.setText(
"<h1>메일인증</h1>" +
"<br/>"+memberVO.getMemberId()+"님 "+
"<br/>ICEWATER에 회원가입해주셔서 감사합니다."+
"<br/>아래 [이메일 인증 확인]을 눌러주세요."+
"<a href='http://localhost:8080/member/registerEmail?memberEmail=" + memberVO.getMemberEmail() +
"&key=" + key +
"' target='_blenk'>이메일 인증 확인</a>");
sendMail.setFrom("[발송 이메일 주소]", "[발송자 이름]");
sendMail.setTo(memberVO.getMemberEmail());
sendMail.send();
}
@Override
public void memberAuth(String memberEmail) throws Exception{
memberDAO.memberAuth(memberEmail);
}
회원가입 서비스에
인증키 생성로직과 메일발송 로직을 넣어줬다.
아까 작성한 TempKey와 MailUtils 클래스를 사용한다.
제목, 내용, 발송인, 작성자, send를 입력해준다.
회원가입 로직에 넣었기 때문에, 사용자가 입력한 항목을 모두 가져올 수 있다.
닉네임이나.. 전화번호나.. 생각한 형태로 VO에서 값을 가져와서 만들어주자.
Controller
@RequestMapping(value = "/register", method=RequestMethod.POST)
public String register(MemberVO memberVO, RedirectAttributes rttr, Model model)throws Exception{
logger.info("register");
String hashedPw = BCrypt.hashpw(memberVO.getMemberPw(), BCrypt.gensalt());
memberVO.setMemberPw(hashedPw);
memberService.register(memberVO);
model.addAttribute("member", memberVO);
rttr.addFlashAttribute("msg", "가입이 완료되었습니다");
rttr.addAttribute("memberEmail", memberVO.getMemberEmail());
rttr.addAttribute("memberId", memberVO.getMemberId());
return "redirect:/member/registerAuth";
}
@RequestMapping(value="registerEmail", method=RequestMethod.GET)
public String emailConfirm(String memberEmail,Model model)throws Exception{
memberService.memberAuth(memberEmail);
model.addAttribute("memberEmail", memberEmail);
return "/member/registerEmail";
}
회원가입과 이메일 인증 컨트롤러이다.
회원가입쪽은 memberVO(회원가입입력정보) 를 받아와서 서비스로 넘겨주는 부분이라
뷰단으로 리다이렉트 데이터를 주는 것 외엔 변한게 없고,
이메일인증 컨트롤러가 추가됐다.
로그인 컨트롤러
@RequestMapping(value="/login" , method= RequestMethod.POST)
public String loginPost(LoginVO loginVO, HttpSession httpSession,Model model)throws Exception{
logger.info("loginVO"+loginVO.getMemberId());
MemberVO memberVO = memberService.login(loginVO);
logger.info("Pw"+memberVO);
if( memberVO == null || !BCrypt.checkpw(loginVO.getMemberPw(), memberVO.getMemberPw())) {
return "/member/loginCheck";
}
if(memberVO.getMemberAuth() == 0) {
model.addAttribute("Auth", memberVO.getMemberAuth());
return "/member/registerReady";
}
model.addAttribute("member", memberVO);
if(loginVO.isUseCookie()) {
int amount = 60*60*24*7;
Date sessionLimit = new Date(System.currentTimeMillis() + (1000*amount));
memberService.keepLogin(memberVO.getMemberId(), httpSession.getId(), sessionLimit);
}
return "board/list";
}
만약 이메일 인증을 받지 않았다면, 특정페이지로 가게 끔 컨트롤러에
if(memberVO.getMemberAuth() == 0) {
model.addAttribute("Auth", memberVO.getMemberAuth());
return "/member/registerReady";
}
코드를 추가해준다
이제 이메일 인증 기능을 구현했으니, 로직의 흐름대로 결과사진,뷰단 코드과 함께 설명해보려고 한다.
회원가입뷰에서 회원정보를 입력하고 가입을 누르면 /member/register 를 호출한다.
이제 해당 컨트롤러 부분을 보자.
입력한 memberVO가 서비스로 넘어가고 memberVO의 Email로 아까 셋팅한 이메일 인증 확인 메일이 날아간다.
registerAuth.jsp 생성
리다이렉트로 해당 페이지를 띄우고 , 로그인페이지에서 RedirectAttribute.addAttribute로 Email과 Id를 보내고,
RequestParam으로 받아서 입력받은 아이디와 이메일을 띄워준다.
@RequestMapping(value="/registerAuth",method= RequestMethod.GET)
public String loginView(HttpServletRequest request,Model model,@RequestParam("memberEmail")String memberEmail,@RequestParam("memberId")String memberId) throws Exception{
logger.info("loginView");
model.addAttribute("memberEmail", memberEmail);
model.addAttribute("memberId", memberId);
return "/member/registerAuth";
}
메일함에 가보니 메일이 잘 도착했다.
인증확인을 누르면
/member/registerEmail 을 호출한다.
이제 컨트롤러에서 get방식으로 전송된(주소창을 확인해보자) 이메일과 인증키를 받는다.
그럼 이제 Mapper.xml의 memberAuth 까지 올라가서
<update id="memberAuth">
<![CDATA[update MP_MEMBER set MEMBER_AUTH = 1 where (select count(*) from MP_MEMBER_AUTH where MEMBER_EMAIL = #{memberEmail}) > 0]]>
</update>
해당 로직을 실행한다.
그런데, 기능에는 별다른 문제가 없어서 암호키를 뺏고 이메일로만 인증을 했다.
보안을 높이기 위해 암호키까지 사용해서 인증을 하고싶다면 간단하다.
컨트롤러에서 매개변수로 String authKey 를 추가하고, 서브쿼리의 where문 뒤에
AND MEMBER_AUTH = #{authKey} 를 추가해주면 되겠다.
DB에 저장된 이메일과, 링크로 전송된(GET) 이메일이 맞다면,
아까 추가한 회원테이블의 MEMBER_AUTH를 기본값인 0에서 1로 바꾼다.
로직을 마친후, /member/registerEmail 뷰로 리턴해서 위의 사진의 내용을 띄운다.
확인버튼을 누르면 로그인페이지로 돌아간다.
registerEmail.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<script type="text/javascript">
var memberEmail = '${memberEmail}';
alert(memberEmail + '님 회원가입을 축하합니다. 이제 로그인이 가능 합니다. 확인버튼을 누르면 로그인 페이지로 이동합니다.');
self.location = "/member/loginView";
</script>
</body>
</html>
만약 이메일 인증을 하지않고 (Member_AUTH가 0인상태로) 로그인을 하면
/member/registerReady로 이동한다.
위에서 올린 로그인 컨트롤러의 일부분
if(memberVO.getMemberAuth() == 0) {
model.addAttribute("Auth", memberVO.getMemberAuth());
return "/member/registerReady";
}
메일전송 라이브러리와, 0과 1로 구분하는 AUTH의 사용방식에 대한 이해가 필요하다.
+++++++++
나중에 백퍼 안할 것 같아서 블로그 글 작성한 후, 바로 암호키도 사용해서 이메일인증을 하도록
만들었다.. 5분 걸렸나..? ㅜㅜ 귀찮아도 바로바로 해치워버리자.. 안좋은 습관..
'SPRING > IceWater Community' 카테고리의 다른 글
[스프링]비밀번호 찾기 구현 (1) | 2021.09.24 |
---|---|
[스프링] 아이디찾기 구현 (1) | 2021.09.24 |
[스프링] 자동로그인 기능 구현 - 로그인4 (0) | 2021.09.17 |
[스프링]로그인 권한설정과(인터셉터) 로그인,로그아웃 전 페이지 기억 기능 - 로그인 3 (1) | 2021.09.16 |
[스프링]인터셉터를 활용한 로그인구현 (세션부여) - 로그인2 (0) | 2021.09.15 |