티스토리 뷰

이제 인증 메일과 비밀번호 암호화 기능을 추가 해보려고 한다.

사실 요즘엔 핸드폰 인증을 더 많이 쓰는 것 같긴한데...더 간단하고 스프링에서 기본으로 제공하는 API가 있고 돈이 안들어가는 이메일 인증 방법을 택했다.

 

이메일 인증은 회원가입 프로세스에서 중요한 단계 중 하나이다.

이메일 인증을 통해 회원 신원을 확인할 수 있으며 불법적인 가입이나 다른 사용자의 계정 사용을 방지해서 보안을 강화시킬 수 있는 것이 장점이다. 또한, 더 나아가면 중요한 서비스 안내나 소식 전달도 이메일을 통해 할 수 있을 것!

 

 


 

 

1️⃣ 링크 참고하여 이메일 설정 ( 2단계 인증 활성화 ) ⇒ https://myaccount.google.com/u/0/security?hl=k

 

Google 계정

 

myaccount.google.com

  • 여기서 설정해준 구글 계정이 이메일을 보내는 발신 계정이 된다.
  • Google 계정 관리 → 보안 탭을 클릭해 직접 이동해도 된다.

1. 주소로 들어가면 2단계 인증을 클릭해서 인증을 한다.

2. 1번을 진행하면 앱 비밀번호 라는 부분이 추가되어 있다.

3. 앱 비밀번호를 생성할 앱 및 기기를 선택한다.

4. 앱 비밀번호를 생성하면 16자리의 비밀번호가 생성된다.  ->  gmail 비밀번호 대신 사용하게 될 비밀번호이므로 기억해두기

 

 

 

2️⃣ pom.xml에 메일 라이브러리 추가

먼저 JavaMail API와 Spring Framework의 Context Support를 사용하여 메일 전송과 관련된 라이브러리를 추가해줬다.

		<!-- mail library -->
		<dependency>
			<groupId>javax.mail</groupId>
			<artifactId>mail</artifactId>
			<version>1.4.7</version>
		</dependency>
  • JavaMail API는 Java 언어를 사용하여 이메일 전송을 위한 표준 API를 제공한다.
  • 메일 전송, 수신 및 관련된 작업을 처리하는 데 사용된다.
		<!-- mail 서포트 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
  • spirng-context-support는 Spring에서 제공하는 컨텍스트 지원과 관련된 라이브러리로, 이메일 전송과 같은 작업을 더 쉽게 구성하고 관리할 수 있도록 도와준다.

라이브러리를 추가 한 후 꼭 업데이트를 해주자!  ( update project )

 

 

 

3️⃣ email-context.xml 또는 MailConfig.java 추가

src/main/java > com.config > MailConfig.java

package com.config;

import java.util.Properties;  // 이메일 송신에 필요한 속성들을 설정

import org.springframework.context.annotation.Bean;   
import org.springframework.context.annotation.Configuration;   
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import com.info.Info;

@Configuration   // 스프링의 설정 클래스임
public class MailConfig {
	
private final Info info = new Info();

    @Bean   // 빈 등록
    public JavaMailSender javaMailSender() {
    	

        Properties mailProperties = new Properties();
        mailProperties.put("mail.transport.protocol", "smtp");  // 메일 전송에 사용할 프로토콜
        mailProperties.put("mail.smtp.auth", "true");  // SMTP 서버에 사용자 인증 활성화
        mailProperties.put("mail.smtp.starttls.enable", "true");  // STARTTLS 암호화
        mailProperties.put("mail.smtp.debug", "true");  // 디버그 모드를 활성화
        mailProperties.put("mail.smtp.ssl.trust", "smtp.gmail.com");  // SSL/TLS 연결에서 신뢰할 수 있는 호스트를 지정
        mailProperties.put("mail.smtp.ssl.protocols", "TLSv1.2");  // 사용할 SSL/TLS 프로토콜

        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setJavaMailProperties(mailProperties);  // 앞서 설정한 메일 속성들을 JavaMailSenderImpl에 적용
        mailSender.setHost("smtp.gmail.com");  // 메일을 보낼 SMTP 서버의 호스트를 설정
        mailSender.setPort(587);  // SMTP 서버의 포트 번호를 설정
        mailSender.setUsername(info.getMailId());  // 이메일을 보내는 데 사용될 계정의 사용자 이름을 설정
        mailSender.setPassword(info.getMailPassword());  // 이메일 계정의 암호를 설정
        mailSender.setDefaultEncoding("utf-8");  // 이메일의 기본 인코딩을 설정
        return mailSender;
    }
}

 

여기에 아까 발급받은 앱 비밀번호를 넣어준다. (google계정 비밀번호 아님 주의)

 

 

 

4️⃣ 만약 email-context.xml 으로 했다면 email-context.xml을 읽을 수 있도록 web.xml에 코드 추가

>> 나는 java 파일로 했으므로 pass !

 

 

 

5️⃣ MaiHandler.java, TempKey.java 추가

파일 위치

 

 

MailHandler.java

package com.mail;

import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;

import javax.activation.DataSource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;

public class MailHandler {
    private JavaMailSender mailSender;
    private MimeMessage message;
    private MimeMessageHelper messageHelper;

    public MailHandler(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);
    }
}
  • 메일 전송 라이브러리의 setter이다.
  • 메일 제목, 내용, 발송자, 수신자, 보내기로 이루어져 있다.

 

TempKey.java

package com.mail;

import java.util.Random;

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();
    }
}
  • 인증번호를 보낼 때 사용할 클래스이다. 이 클래스를 호출할 때는 몇 자리 수로 할 건지 사이즈를 파라미터로 보내면 된다.
  • 실제 이메일을 보내는 코드에 위 코드를 넣는 경우도 있지만, 각 클래스나 메서드를 각자의 역할에 맞게 코드를 분리했고, 추후 비밀번호 찾기를 할 때도 위 클래스를 사용할 것 이기 때문에 따로 분리를 해뒀다.

 

여기까지 기본 설정과 꼭 필요한 파일들을 생성해줬다.

이제 로직을 짜고 이메일을 보내보자.

 

 


 

 

1️⃣ 테이블 설계하고 DTO 작성

package com.dto;

import org.apache.ibatis.type.Alias;

@Alias("MemberDTO")
public class MemberDTO {

	private String userID;
	private String passwd;
	private String name;
	private String email;
	private String post;
	private String addr1;
	private String addr2;
	private String phone;
	private int mail_auth;     // (추가) 이메일 인증 여부를 나타내는 값
	private String mail_key;    // (추가) 이메일 인증에 사용되는 키
  • mail_auth ⇒ 기본값 0을 넣어 놓고, 이메일 인증을 했을 경우 값을 1로 변경시켜 로그인 되게 함
  • mail_key ⇒ TempKey.java에서 생성한 난수를 저장하는데 사용

 

2️⃣ memberMapper.xml에 3개의 쿼리 추가

회원가입 시 이메일 인증을 위한 랜덤번호 저장

<!-- 이메일 인증을 위한 랜덤번호 저장 -->
  <update id="updateMailKey" parameterType="MemberDTO">
	    update member set mail_key=#{mail_key} where email=#{email} and userID=#{userID}
	</update>

 

메일 인증을 하면 mail_auth 컬럼을 기본값 0에서 1로 바꿔 로그인을 허용

<!-- 메일 인증 후 mail_auto 0 -> 1 변경 -->
	<update id="updateMailAuth" parameterType="MemberDTO">
	    update member set mail_auth=1 where userID=#{userID} and email=#{email}
	</update>

 

이메일 인증을 안 했으면 0을 반환, 로그인 시 인증했나 안 했나 체크

<!-- 로그인 시 인증 유무 체크 -->
	<select id="emailAuthFail" parameterType="string" resultType="int">
	    select count(*) from member where userID=#{userID} and mail_auth=1
	</select>

 

이메일 당 가입된 아이디 개수 확인 ( 필수 xx 부가적인 기능으로 추가 )

<!-- 이메일당 가입된 아이디 개수 -->
	<select id="idPerEmailCount" parameterType="string" resultType="int">
		select count(*)
		from member
		where email = #{email}
	</select>

 

 

 

3️⃣ MemberDao, MemberService, MemberServiceImpl에 코드 추가

MemberDAO.java

package com.dao;

import java.util.HashMap;
import java.util.List;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.dto.MemberDTO;

@Repository
public class MemberDAO {
	
	@Autowired // SqlSessionTemplate 주입
	SqlSessionTemplate session;

	// 이메일 인증을 위한 랜덤번호 저장
	public int updateMailKey(MemberDTO dto) throws Exception {
	    return session.update("MemberMapper.updateMailKey", dto);
	}

	// 메일 인증 후 mail_auto 0 -> 1 변경
	public int updateMailAuth(MemberDTO dto) throws Exception {
	    return session.update("MemberMapper.updateMailAuth", dto);
	}

	// 로그인 시 인증 유무 체크 => 인증 실패 횟수를 반환
	public int emailAuthFail(String userID) throws Exception {
	    return session.selectOne("MemberMapper.emailAuthFail", userID);
	}
	
	// 이메일당 가입된 아이디 개수 => 특정 이메일에 가입된 아이디의 개수를 반환
	public int idPerEmailCount(String email) {
		return session.selectOne("MemberMapper.idPerEmailCount", email);
	}
	
}

 

 

MemberService.java

package com.service;

import java.util.HashMap;
import java.util.List;

import com.dto.MemberDTO;

public interface MemberService {

	// 회원가입 
	public void register(MemberDTO dto) throws Exception;

	// 로그인
	public MemberDTO login(HashMap<String, String> map);
	public MemberDTO idCheck(String userID);
	
	// 이메일 인증
	public int updateMailKey(MemberDTO dto) throws Exception;
	public int updateMailAuth(MemberDTO dto) throws Exception;
	public int emailAuthFail(String userID) throws Exception;
	
	// 이메일당 가입된 아이디 개수
	public int idPerEmailCount(String email);

}

 

 

MemberServiceImpl.java

@Service
public class MemberServiceImpl implements MemberService {

    @Autowired
    MemberDAO dao;
    @Autowired
    JavaMailSender mailSender;

    // 다른 메서드들...

    @Transactional
    @Override
    public void register(MemberDTO dto) throws Exception {
        // 랜덤 문자열 생성 -> mail_key 컬럼에 넣기
        String mail_key = new TempKey().getKey(30, false); // 랜덤 키 길이 30
        dto.setMail_key(mail_key);

        // 회원가입
        dao.register(dto);

        // 회원가입 완료 후 인증 이메일 발송
        MailHandler sendMail = new MailHandler(mailSender);
        sendMail.setSubject("[여담 인증메일 입니다.]"); // 메일 제목
        sendMail.setText( // 메일 내용
            "<h1>여담 메일 인증</h1>" +
            "<br>안녕하세요 " + dto.getUserID() + "님. 여담 가입을 환영합니다!" +
            "<br>아래 <b>[이메일 인증 확인]</b>을 눌러주세요." +
            "<br><h3>해당 링크의 유효시간은 3분입니다.</h3>" +
            "<br><a href='http://localhost:8091/app/registerEmail?userID=" + dto.getUserID() + "&email=" + dto.getEmail() +
            "&mail_key=" + mail_key +
            "' target='_black'>이메일 인증 확인</a>");
        sendMail.setFrom("pjtravelplan@gmail.com", "여담"); // 보내는 사람
        sendMail.setTo(dto.getEmail()); // 받는 사람
        sendMail.send(); // 메일 보내기
    }

// 이메일당 가입된 아이디 개수
	@Override
	public int idPerEmailCount(String email) {
		int num = dao.idPerEmailCount(email);
		if(num >= 3) {
			return 0;
		}
		return 1;
	}
    // 나머지 메서드들...

// 이메일 인증을 위한 랜덤번호 저장 - redis로 대체
	@Override
	public int updateMailKey(MemberDTO dto) throws Exception {
		return dao.updateMailKey(dto); 
		return 0;
	}

// 메일 인증 후 mail_auto 0 -> 1 변경
	@Override
	public int updateMailAuth(MemberDTO dto) throws Exception {
		RedisTemplate<String, Object> redisTemplate = redis.getRedisTemplate();
		
		// 인증 키 확인
		String mail_key = (String) redisTemplate.opsForValue().get(dto.getUserID());
		if(mail_key == null) { // 인증키 timeout
			return 0;
		}
		if(!mail_key.equals(dto.getMail_key())) { // DB에 저장된 인증키와 url에 전달된 인증키가 다름
			return 0;
		}
		
		redisTemplate.delete(dto.getUserID()); // 인증 후 키값 초기화
		return dao.updateMailAuth(dto);
	}

	// 로그인 시 인증 유무 체크
	@Override
	public int emailAuthFail(String userID) throws Exception {
		return dao.emailAuthFail(userID);
	}
}
  • 의존성 추가
  • sendMail.setFrom(”보내는사람@이메일”, “쑤”) ⇒ 보내는 사람 이메일에는 위 1번에서 설정해주었던 이메일 주소를 넣어주면 된다.

 

 

4️⃣ registerController.java 컨트롤러 추가

package com.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.dto.MemberDTO;
import com.service.MemberService;

@Controller
public class registerController {
	
	@Autowired
	MemberService service;
	
	// 신규 회원 입력화면
	@GetMapping("/memberUI")
	public String memberUI() {
		return "registerForm";
	}
	
	// 신규 회원 등록
	@PostMapping("/register")
	public String register(MemberDTO dto) throws Exception {
		// 이메일당 가입 계정 개수 확인
		if(service.idPerEmailCount(dto.getEmail()) == 0) {
			// 세개 이상이면 회원가입 방지
			return "member/registerFail_email"; 
		}
		service.register(dto);
		return "member/emailAuthInfo";
	}
	
	// 회원가입시 이메일 인증
	@GetMapping("/registerEmail")
	public String emailConfirm(MemberDTO dto) throws Exception {
		int num = service.updateMailAuth(dto);  // 인증키 확인해서 일치하면 해당 회원의 'mail_auth'값을 업데이트
		if(num == 0) { // 인증키가 맞지 않음(만료되었거나, 일치하지 않음)
			return "member/checkKeyFail";
		}
		return "member/emailAuthSuccess";
	}
	
}

 

 

 

5️⃣ JSP 작성

emailAuthInfo.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!-- jquery CDN -->   
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>   
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>여담: 이메일 인증 실패</title>
    
	<!-- alert 커스텀 -->
    <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.9.0/dist/sweetalert2.min.css">
	<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.9.0/dist/sweetalert2.all.min.js"></script>

	<script>
		$(document).ready(function(){
			Swal.fire({	
				icon: 'success',
				title: '이메일 인증 후 \n로그인 가능합니다.' }).then(function(){
				location.href='/app/main';
			});
		});
	</script> 
</head>
<body>
</body>
</html>

 

emailAuthFail.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!-- jquery CDN -->   
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>여담: 이메일 인증 실패</title>
    
	<!-- alert 커스텀 -->
    <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.9.0/dist/sweetalert2.min.css">
	<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.9.0/dist/sweetalert2.all.min.js"></script>

	<script>
		$(document).ready(function(){
			Swal.fire({	
				icon: 'error',
				title: '이메일 인증 후 재시도해주세요.' }).then(function(){
				location.href='/app/loginForm';
			});
		});
	</script> 
</head>
<body>
</body>
</html>

 

emailAuthSuccess.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!-- jquery CDN -->   
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>여담: 이메일 인증</title>
	<!-- <script>
			alert("이메일 인증이 완료되었습니다!");
			location.href="/app/loginForm"
		</script> -->
	<!-- alert 커스텀 -->
    <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.9.0/dist/sweetalert2.min.css">
	<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.9.0/dist/sweetalert2.all.min.js"></script>

	<script>
		$(document).ready(function(){
			Swal.fire({	
				icon: 'success',
				title: '이메일 인증이 완료되었습니다!' }).then(function(){
				location.href='/app/loginForm';
			});
		});
	</script> 
</head>
<body>
</body>
</html>

'Project 여담 > 6주차' 카테고리의 다른 글

11/14 : 수료식과 짧은 회고  (0) 2023.11.14
11/10 : object storage  (0) 2023.11.10
11/09 : 비밀번호 암호화  (0) 2023.11.09
11/07 : 중간발표 / Alert 디자인 추가  (0) 2023.11.07
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함