개발자입니다
[프로젝트] 네이버 메일 링크 클릭으로 인증 구현(SpringBoot) 본문
개요
SpringBoot 프로젝트에서 메일 인증을 네이버 SMTP 이용해서 구현한다.
따로 인증 코드를 입력하지 않고 인증 링크를 클릭하면 메일 인증 되는 방식으로 진행하였다.
흐름은 다음과 같다.
- 회원 가입시 입력한 메일 주소로 인증 링크를 보낸다.
- 메일 사용자가 그 링크를 클릭하면 메일 인증을 완료한다.
- 로그인이 가능해진다. 인증하지 않으면 alert 창을 띄워 메일 인증 후 로그인 하라고 한다.
- 랜덤으로 36자리 문자를 만드는 UUID 사용할 것이기 때문에 편법으로 인증 시도는 매우 힘들 것이라 생각한다.
메일 계정 준비
네이버 메일 서비스를 이용하였다.
인증 메일 발송을 위해 계정을 새로 생성한다.
[환경설정 > POP3/IMAP 설정 > POP3/SMTP 설정] 들어간다.
적용 범위: 기존에 받은 메일을 포함하여 받음 선택
원본 저장 : 네이버 메일에 원본 저장 선택
후 저장한다.
의존 라이브러리 준비
현재 개발환경은 아래와 같다.
JavaSE: 17
SpringBoot : v3.0.5 (jakarta EE)
build.gradle 에 의존 라이브러리 준비한다.
implementation 'org.springframework.boot:spring-boot-starter-mail:3.0.5'
implementation 'org.springframework:spring-context:6.0.7'
implementation 'org.springframework:spring-context-support:6.0.7'
implementation 'com.sun.mail:jakarta.mail:2.0.1'
간혹 $ gradle eclipse 만으로도 의존성 문제로 에러가 발생하는 경우가 있다.
에러는 이런 문구로 시작한다.
DEBUG: Jakarta Mail version 1.0.0
2023-04-08T21:12:43.663+09:00 ERROR 29936 --- [ XNIO-1 task-5] io.undertow.request : UT005023: Exception handling request to /auth/signup
jakarta.servlet.ServletException: Handler dispatch failed: java.util.ServiceConfigurationError: jakarta.mail.Provider: com.sun.mail.imap.IMAPProvider not a subtype
chatGPT 문의 결과 아래 답변 받았으며, 이렇게 하니 해결되었다.
1. 프로젝트를 깨끗한 상태로 만들기 위해 Gradle 캐시를 정리합니다. 프로젝트 디렉토리에서 다음 명령어를 실행합니다.
./gradlew clean
2. 프로젝트의 모든 Gradle 빌드 파일과 캐시를 삭제합니다. 이 작업을 수행하면 Gradle이 모든 의존성을 다시 다운로드하게 됩니다.
rm -rf ~/.gradle/caches
3. 이클립스에서 프로젝트를 다시 가져옵니다. 이렇게 하면 프로젝트 설정이 최신 상태로 업데이트됩니다.
4. 이클립스에서 프로젝트를 "Clean"한 다음 "Build"를 실행합니다.
코드 작성
properties 파일 생성
mailAuth.properties 파일을 생성하고 src/main/resources 에 두었다.
이 파일을 생성하는 이유는 git 에 올릴때 민감한 정보는 .gitignore 에 등록해서 올리지 않도록 하기 위해서이다.
mail.username=네이버아이디
mail.password=네이버비밀번호
config 파일 생성
@PropertySource 로 위에서 작성한 프로퍼티 파일을 읽어들인다.
@ConfigurationProperties(prefix = "mail") 로 mail.username 의 값을 username 으로 받는다.
@Getter, @Setter 가 있어야 username, password 필드에 값을 설정할 수 있다.
네이버 메일 환경설정의 Host, Port 를 입력한다.
package bitcamp.app;
import java.util.Properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Configuration
@PropertySource("classpath:mailAuth.properties")
@ConfigurationProperties(prefix = "mail")
@Getter
@Setter
@ToString
public class MailConfig {
private String username;
private String password;
@Bean
public JavaMailSender javaMailService() {
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost("smtp.naver.com"); // 메인 도메인 서버 주소 => 정확히는 smtp 서버 주소
javaMailSender.setUsername(username); // 네이버 아이디
javaMailSender.setPassword(password); // 네이버 비밀번호
javaMailSender.setPort(465); // 메일 인증서버 포트
javaMailSender.setJavaMailProperties(getMailProperties()); // 메일 인증서버 정보 가져오기
return javaMailSender;
}
private Properties getMailProperties() {
Properties properties = new Properties();
properties.setProperty("mail.transport.protocol", "smtp"); // 프로토콜 설정
properties.setProperty("mail.smtp.auth", "true"); // smtp 인증
properties.setProperty("mail.smtp.starttls.enable", "true"); // smtp strattles 사용
properties.setProperty("mail.debug", "true"); // 디버그 사용
properties.setProperty("mail.smtp.ssl.trust","smtp.naver.com"); // ssl 인증 서버는 smtp.naver.com
properties.setProperty("mail.smtp.ssl.enable","true"); // ssl 사용
return properties;
}
}
이 후 부터는 회원 가입 과 메일 인증 링크 클릭 을 따로 보면 편하다.
먼저 회원 가입 부분부터 쭉 본 후 메일 인증 링크 클릭을 쭉 보면 작업 흐름에 따라가는 것이다.
Controller 코드 수정
회원 가입
회원 가입을 하면 "/auth/signup" 으로 요청을 보낸다.
여기서 UUID 를 생성하고 token 으로 member 객체에 저장한다.
service 객체의 add() 를 이용한다.
메일 인증 링크 클릭
메일 인증 링크 클릭하면 "auth/verify" 로 요청을 보낸다.
링크에 미리 넣어놓은 UUID가 쿼리 스트링으로 입력되어 있다. 이를 가지고 계정을 찾아 state 를 미인증에서 일반 회원으로 변경한다.
package bitcamp.app.controller;
import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import bitcamp.app.service.MemberService;
import bitcamp.app.vo.Member;
import bitcamp.util.ErrorCode;
import bitcamp.util.PasswordChecker;
import bitcamp.util.RestResult;
import bitcamp.util.RestStatus;
import jakarta.servlet.http.HttpSession;
@RestController
@RequestMapping("/auth")
public class AuthController {
Logger log = LogManager.getLogger(getClass());
{
log.trace("AuthController 생성됨!");
}
@Autowired private MemberService memberService;
@PostMapping("signup")
public Object signup(
String nickname,
String email,
String password,
HttpSession session) throws Exception {
if(nickname.length() <= 50 ||
email.contains("@") ||
PasswordChecker.isValidPassword(password)) {
String token = UUID.randomUUID().toString();
Member member = new Member();
member.setNickname(nickname);
member.setEmail(email);
member.setPassword(password);
member.setToken(token);
memberService.add(member);
return new RestResult()
.setStatus(RestStatus.SUCCESS);
}
return new RestResult()
.setErrorCode(ErrorCode.rest.CONTROLLER_EXCEPTION)
.setStatus(RestStatus.FAILURE);
}
@GetMapping("verify")
public Object verifyEmail(HttpSession session, @RequestParam String token) {
Member member = memberService.updateByVerifyToken(token);
if (member != null) {
session.setAttribute("loginUser", member);
return new RestResult()
.setStatus(RestStatus.SUCCESS);
} else {
return new RestResult()
.setErrorCode(ErrorCode.rest.NO_DATA)
.setStatus(RestStatus.FAILURE);
}
}
}
Service 코드 수정
회원 가입
add() 를 수정한다.
인터페이스는 생략하고 구현체만 설명한다.
기존 memberDao.insert() 밑에 memberDao.updateToken() 을 추가한다.
그리고 아래에 메일을 보낼 설정 및 제목, 본문을 작성하고 메일 전송 메서드를 호출한다.
메일 인증 링크 클릭
updateByVerifyToken() 를 추가한다.
memberDao 의 findByToken() 으로 member 객체를 받환받고 객체가 있을 때만 state 를 업데이트 시킨다.
프론트 엔드에서 사용하라고 객체도 리턴해준다.
package bitcamp.app.service.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import bitcamp.app.dao.FollowDao;
import bitcamp.app.dao.MemberDao;
import bitcamp.app.service.MemberService;
import bitcamp.app.vo.Member;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMessage.RecipientType;
@Service
public class DefaultMemberService implements MemberService {
Logger log = LogManager.getLogger(getClass());
@Autowired private MemberDao memberDao;
@Autowired private FollowDao followDao;
@Autowired JavaMailSender mailSender;
@Transactional
@Override
public void add(Member member) throws Exception {
memberDao.insert(member);
memberDao.updateToken(member);
String receiverMail = member.getEmail();
MimeMessage message = mailSender.createMimeMessage();
message.addRecipients(RecipientType.TO, receiverMail);// 보내는 대상
message.setSubject("Artify 회원가입 이메일 인증");// 제목
String body = "<div>"
+ "<h1> 안녕하세요. Artify 입니다</h1>"
+ "<br>"
+ "<p>아래 링크를 클릭하면 이메일 인증이 완료됩니다.<p>"
+ "<a href='http://localhost:3000/auth/verify?token=" + member.getToken() + "'>인증 링크</a>"
+ "</div>";
message.setText(body, "utf-8", "html");// 내용, charset 타입, subtype
// 보내는 사람의 이메일 주소, 보내는 사람 이름
message.setFrom(new InternetAddress("bitcamp1@naver.com", "Artify_Admin"));// 보내는 사람
mailSender.send(message); // 메일 전송
}
@Override
public Member updateByVerifyToken(String token) {
Member member = memberDao.findByToken(token);
if (member != null) {
memberDao.updateStateByToken(token);
return member;
} else {
return null;
}
}
}
Dao 에 코드 추가
회원 가입
Dao 인터페이스에 updateToken() 메서드 추가한다.
메일 인증 링크 클릭
메일 인증 링크를 클릭했을때 사용할 updateStateByToken() 메서드 추가한다.
package bitcamp.app.dao;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import bitcamp.app.vo.Member;
@Mapper
public interface MemberDao {
void updateToken(Member m);
void updateStateByToken(String token);
}
Mapper 에 코드 추가
MemberDao.xml 에 아래 코드 추가한다.
회원 가입
email 을 찾아 token 을 업데이트 한다.
메일 인증 링크 클릭
메일 링크 클릭시 state 를 미인증에서 일반 회원 상태로 전환한다.
<update id="updateToken" parameterType="member">
update aim_member
set
token=#{token}
where
email=#{email}
</update>
<update id="updateStateByToken" parameterType="string">
update aim_member
set
state=0
where
token=#{token}
</update>
테스트
가지고 있는 메일로 회원 가입을 해보았다.
인증 메일이 잘온다.
인증 링크 클릭하면 인증이 완료되고 로그인 된다.
토큰 값이 틀리면 유효하지 않은 링크라고 뜨고 로그인 시키지 않는다.
'네이버클라우드 AIaaS 개발자 양성과정 1기 > 프로젝트' 카테고리의 다른 글
[프로젝트] SSE 방식으로 서버(SpringBoot)에서 클라이언트(React)로 데이터 보내기 (0) | 2023.04.19 |
---|---|
[비트캠프] 113일차(24주차1일) - React 파일 컴파일 (0) | 2023.04.17 |
[프로젝트] 네이버 아이디로 회원가입, 로그인 구현(React, SpringBoot) (0) | 2023.04.09 |
[비트캠프] 92일차(19주차5일) - DB 모델링 (0) | 2023.03.17 |