본문 바로가기
Java/Spring Boot

[Spring Boot] email 전송하기

by curious week 2025. 1. 29.

gmail 설정하기

 

서버 관련(Springboot)

 

클라이언트 관련(Next)


gmail 설정하기

gmail 가입 -> google 계정 -> 보안 -> 2단계 인증 설정 후 상단 계정 검색에서 '앱 비밀번호' 검색

앱 비밀번호를 복사해 둔다.


Server

// build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-mail'
}
# application.properties
# mail
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username= 메일 주소 (메일@gmail.com)
spring.mail.password= 앱 비밀번호 (띄어쓰기 지우기)
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.timeout=5000
spring.mail.properties.mail.smtp.starttls.enable=true

emailService.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

import java.security.SecureRandom;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class EmailService {

    private final JavaMailSender mailSender;
    private ConcurrentHashMap<String, String> codeStorage = new ConcurrentHashMap<>();


    @Autowired
    public EmailService(JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }
    
    // 메일 보내기
    public void sendSimpleMessage(String to, String subject, String text) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom("상대에게 표시할 메일 주소");
        message.setTo(to);
        message.setSubject(subject);
        message.setText(text);

        mailSender.send(message);
    }

    // 무작위 번호 생성
    public static String generateRandomNumber(int length) {
        SecureRandom random = new SecureRandom();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(random.nextInt(10)); // 0~9 사이 숫자
        }
        return builder.toString();
    }

    // 코드 저장 (email, code)
    public void saveCode(String identifier, String code) {
        codeStorage.put(identifier, code);
    }

    // 코드 검증 client(email, code)
    public boolean verifyCode(String identifier, String code) {
        String storedCode = codeStorage.get(identifier);
        return storedCode != null && storedCode.equals(code);
    }

    // 코드 삭제 (검증 후 email로 map 삭제)
    public void deleteCode(String identifier) {
        codeStorage.remove(identifier);
    }


}

Restcontroller.java

    // 비밀번호 재설정
    // 메일로 -> 임시 코드 보내서 -> 수정
    @GetMapping("/sendEmail")
    public ResponseEntity<Void> sendEmail(@RequestParam String email) {
        String cord = emailService.generateRandomNumber(4);
        emailService.saveCode(email, cord);
        String to = email;
        String subject = "비밀번호 재설정 안내";
        String text = "비밀번호 재설정을 위한 인증번호 입니다. \n 인증번호: " + cord + "\n 인증 완료 후 인증번호는 임시 비밀번호로 설정됩니다.";
        emailService.sendSimpleMessage(to, subject, text);
        return null;
    }

    // 메일 전송 번호 확인
    @GetMapping("/checkPassCord")
    public ResponseEntity<Boolean> checkPassCord(@RequestParam String email,@RequestParam String passCord) {
        boolean check = emailService.verifyCode(email, passCord);
        if(check) {
            userService.changePassword(email, passCord);
        }
        return ResponseEntity.ok(check);
    }

client

// page.tsx
'use client';
import Link from 'next/link';
import React, { useState, useRef, useEffect } from 'react';
import { findEmail, checkPassCord, sendEmail } from '@/app/api/apiService';

export default function Page() {
  // email 값 설정
  const [sendedEmail, setSendedEmail] = useState('');
  // 인증번호 input 나타내기
  const [passCordOn, setPassCordOn] = useState(false);

  // 비밀번호 찾기
  const handleFindPassword = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = e.target as HTMLFormElement;
    const email = (form.elements.namedItem('email') as HTMLInputElement).value;
    if (email === '') {
      alert('이메일을 입력해주세요.');
      return;
    }
    if (email.includes('@') === false) {
      alert('이메일 형식이 올바르지 않습니다.');
      return;
    }
    setSendedEmail(email);
    sendEmail(email);
    setPassCordOn(true);
  };
  
  // 인증번호 확인
  const handlePassCordSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = e.target as HTMLFormElement;
    const email = (form.elements.namedItem('email') as HTMLInputElement).value;
    const passCord = (form.elements.namedItem('passCord') as HTMLInputElement)
      .value;
    checkPassCord(passCord, email)
      .then((response) => {
        if (response.data === true) {
          alert(
            '인증번호가 일치합니다. \n 인증번호는 임시 비밀번호로 설정됩니다.',
          );
          setPassCordOn(false);
        } else {
          alert('인증번호가 일치하지 않습니다.');
        }
      })
      .catch(() => {
        alert('인증번호가 일치하지 않습니다.');
      });
  };
  return (
      <div>
        <h2>
          비밀번호 찾기
        </h2>
        <form onSubmit={handleFindPassword}>
          <label >이메일</label>
          <input
            type="email"
            placeholder="이메일"
            name="email"
          />
          <button
            type="submit">
            비밀번호 찾기
          </button>
        </form>
        {passCordOn && (
          <div>
            <form
              onSubmit={handlePassCordSubmit}>
              <input type="hidden" name="email" value={sendedEmail} />
              <label className="text-black">
                인증번호 (인증완료 후 인증번호는 임시 비밀번호로 설정됩니다.)
              </label>
              <input
                type="text"
                placeholder="메일(아이디)로 전송된 인증번호를 입력해주세요."
                name="passCord"
              />
              <button
                type="submit">
                인증번호 확인
              </button>
            </form>
          </div>
        )}
      </div>
  );
}

axios를 이용해 서버 통신

// aixosApi.ts
import axios from 'axios';

const apiClient = axios.create({
  baseURL: 'http://localhost:8080',
  timeout: 5000, // 요청 제한 시간
  headers: { 'Content-Type': 'application/json' },
});

// API 요청 함수
export const sendEmail = async (email: string) => {
  return apiClient.get(`/user/sendEmail?email=${email}`);
};

export const checkPassCord = async (passCord: string, email: string) => {
  return apiClient.get(
    `/user/checkPassCord?passCord=${passCord}&email=${email}`,
  );
};