엔틸롭 캐년 근처의 홀 슈 밴드로 이동하여 비가 너무 많이 와서 잠시 쉬는 동안에 뜬 쌍무지개🌈 와 말도 안되게 아름다웠던 홀 슈 밴드

 

절벽의 바로 앞까지 가서 구경할 수 있었던 홀 슈 밴드, 마치 말 발굽 같이 생겼다고 해서 이름이 홀 슈 밴드라고 한다

 

 

 

 

 

- TIL -

문제 발생)

 

[TIL] 2024.11.18 - jsp 를 활용하여 이메일 인증 코드 발송하기

- TIL - 구현 목표)오늘은 프론트로부터 이메일 정보를 넘겨받아 백에서 랜덤한 회원가입 코드를 생성한 뒤 전달 받은 회원의 이메일로 회원가입 코드를 전송하는 코드를작성해보려 한다. 보통

youngho3358.tistory.com

 

이전에 구현했던 [TIL] 2024.11.18 - jsp 를 활용하여 이메일 인증 코드 발송하기 코드 부분에서 빌드 이후 배포과정까지 진행하니 오류가 발생했다.

이메일 발송 요청을 보내면 403 Forbidden 코드가 응답으로 돌아왔으며 도커 컨테이너 로그를 확인한 겨로가 오류는 아래와 같았다.

오류 내용은 다음과 같다.

 

 

 

문제 해결)

클래스 패스에서 해당 리소스의 파일을 찾을 수 없다는 얘기인데....

 

검색해보니 Spring Boot 프로젝트는 JAR 파일로 배포하게되면 JSP 파일이 resources/templates 경로에 존재하지 않고

WEB-INF/classes 경로로 이동하게 된다고 한다.

 

resources/templates 경로의 경우는 JSP를 제외한 템플릿 엔진(Thymeleaf, FreeMarker 등) 을 저장하는데 사용되는 경로라고 한다.

 

나는 해당 프로젝트에서 JSP 를 웹 뷰로 사용하지 않고 파일 자체를 읽어서 텍스트로 사용하는 형태이기 때문에

src/main/resources/static/emailForm.jsp

위의 경로로 jsp 파일을 저장하여 사용하기로 하였다.

 

resources 하위의 static 경로의 경우 JAR 파일로 빌드한 뒤에도 해당 경로를 유지한다.

 

기존 경로 > 변경 경로

 

기존 코드 경로

 

변경 코드 경로

 

 

현재 코드에서는 resource.getFile() 을 통해 호출하는데

JAR 파일로 패키징된 애플리케이션에서는 JAR 내부 리소스를 파일 시스템의 파일로 처리하려고 하면 예외가 발생하게 된다.

 

그러므로 나는 이 부분의 코드를 InputStream 으로 대체하여 빌드하였다.

InputStream 으로 스트림으로 내부 파일을 읽어들여 스트림 빌더를 사용하여 문자열로 변환하며 저장하게끔 하여 처리하였다.

 

 

 

경로와 코드를 변경한 뒤 build 하여 서버에 다시 배포한 뒤 테스트를 진행하였다.

scp -P [포트 번호] [로컬의 전송할 파일 경로] [서버 유저 계정]@[서버 아이피]:[전송 받을 서버의 파일 경로]

 

기존 도커 컨테이너 종료 및 삭제

 

변경 사항을 --build 옵션으로 빌드하면서 컨테이너를 실행

 

 

 

테스트 결과 이메일 인증이 정상적으로 날아가는 것을 확인하였다!!!

 

인증 메일 전송 완료

 

 

 

728x90
728x90

 

 

 

 

 

한국에는 판매하지 않는 기아의 텔루라이드 모델과 맥도날드의 Filet-O-Fish(필레오피쉬)

 

 

 

 

- TIL -

 

구현 목표)

오늘은 프론트로부터 이메일 정보를 넘겨받아 백에서 랜덤한 회원가입 코드를 생성한 뒤 전달 받은 회원의 이메일로 회원가입 코드를 전송하는 코드를

작성해보려 한다.

 

보통은 이메일 인증을 redis 와 같은 인메모리 DB 로 구현하지만 나는 사용 중인 주 DB 인 MySQL 을 사용하여 이메일 인증 정보를 저장하고 사용하고자 하였다.

 

코드의 흐름은 간단하게 아래와 같이 구현하고자 했다.

 

더보기

1. 사용자가 회원가입시 자신의 이메일을 작성한 뒤 인증번호 받기 버튼을 클릭

 

2. 프론트에서 작성된 이메일 정보를 백엔드에게 전달

 

3. 백엔드에서 이메일 정보를 전달 받은뒤 회원가입된 이메일인지 아닌지 확인

( 이미 가입된 회원이라면 상태코드 409 반환 )

 

4. 한 이메일을 기준으로 5분에 3번까지만 인증 시도를 허용할 것이므로 DB 를 시간 기준으로 출력하여 5분 내에 회원가입 시도가 5회 이상인지 확인

( 5회 이상으로 시도한 회원이라면 상태코드 429 반환 )

 

5. 가입되지 않은 이메일 정보라면 랜덤 회원가입 코드를 생성하여 DB 에 이메일 정보와 인증 시간 값을 저장한 뒤 회원가입 코드를 전송

 

위 과정 중 오늘 구현할 부분은 회원가입 시도 횟수 체크 부분인 4번을 제외한 모든 부분을 구현해 볼 것이다.

 

 

 

 

 

구현 코드)

더보기

1. Controller

package com.randomchat.main.controller.register;

import com.randomchat.main.dto.register.EmailVerificationDTO;
import com.randomchat.main.service.email.EmailVerificationService;
import com.randomchat.main.service.register.RegisterService;
import jakarta.mail.MessagingException;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RequiredArgsConstructor
@RestController
@RequestMapping("/register")
public class EmailVerificationController {

    private final EmailVerificationService emailVerificationService;
    private final RegisterService registerService;

    @PostMapping("/email/verification")
    public ResponseEntity<String> emailVerification(@RequestBody EmailVerificationDTO emailVerificationDTO) throws MessagingException, IOException {

        String email = emailVerificationDTO.getEmail();

        // 1. 이메일 인증이 들어온 이메일 정보로 가입된 계정이 있다면 deny
        if(registerService.checkEmailDuplication(email)) return ResponseEntity.status(409).body("이미 가입된 이메일입니다.");

        // 2. 이메일 인증이 들어온 기준 시간으로 부터 5분 내로 DB 내에 5회 인증 요청이 있다면 deny
        // emailVerificationService.esExceededLimit(email);

        // 3. 이메일 인증용 난수 생성
        String verificationCode = emailVerificationService.createCode();

        // 4. 이메일을 발송한 뒤 이메일 인증 데이터 DB 에 저장
        emailVerificationService.sendEmail(email, verificationCode); // 인증 이메일 발송
        emailVerificationService.saveEmailVerification(email, verificationCode); // DB 에 인증 내용 저장

        return ResponseEntity.ok("이메일이 발송되었습니다.");
    }
}

 

2. RegisterService ( JPA 쿼리 메소드를 사용하여 동일한 메일이 있는지 체크 )

package com.randomchat.main.service.register;

import com.randomchat.main.domain.users.Users;
import com.randomchat.main.dto.register.RegisterDTO;
import com.randomchat.main.repository.UsersRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class RegisterService {
    private final UsersRepository usersRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
    public void register(RegisterDTO registerDTO) {
        String email = registerDTO.getEmail();
        String password = registerDTO.getPassword();

        // 받아온 비밀번호 정보를 암호화
        registerDTO.setPassword(bCryptPasswordEncoder.encode(registerDTO.getPassword()));

        Users users = new Users();
        Users user = users.createUser(registerDTO);
        usersRepository.save(user);
    }

    public boolean checkEmailDuplication(String email) {
        return usersRepository.existsByEmail(email);
    }

    public boolean checkNicknameDuplication(String nickname) {
        return usersRepository.existsByNickname(nickname);
    }
}

 

3. emailVerificationService

 

- createCode 메소드 -

1) 난수에 사용될 문자들을 미리 선언 후 RANDOM 클래스의 nextInt 메소드를 사용하여 문자 길이만큼의 수 내에서 랜덤 숫자를 생성

2) 랜덤 숫자를 다시 CHARACTERS 문자열의 인덱스로 접근하여 랜덤한 문자를 뽑아 StringBuilder 에 추가한 뒤 반복문이 다 돌면 빌드

 

- renderJspToString 메소드 -

1) templates 디렉토리에 있는 jsp 파일을 불러와 Resource 객체로 생성

2) 생성된 객체의 정보를 UTF-8 으로 인코딩된 String 으로 변환

3) String 으로 변환된 내용 중 ${verificationCode} 문자를 실제 인증 코드로 변경

 

- sendEmail 메소드 -

1) 이메일을 전송하기 위해 Mimemessage 객체를 생성

2) Mimemessage 를 쉽게 설정하도록 도와주는 헬퍼 클래스인 MimemessageHelper 클래스를 선언하여 다음과 같이 설정

 

true : HTML 형식을 지원하도록 설정

UTF-8 : 이메일 내용의 인코딩 방식을 설정

 

3) 헬퍼 플래스를 사용하여 이메일 주소, 제목, 내용을 다음과 같이 설정

 

setTo : 수신자 이메일 작성

setSubject : 메일 제목 설정

setText : 메일 본문을 설정, true 로 HTML 형식 적용

setFrom : 발신자의 이메일 주소를 설정

package com.randomchat.main.service.email;

import com.randomchat.main.domain.email.EmailVerification;
import com.randomchat.main.repository.EmailVerificationRepository;
import com.randomchat.main.repository.UsersRepository;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.SecureRandom;
import java.util.Random;

@Service
@RequiredArgsConstructor
public class EmailVerificationService {

    private final UsersRepository usersRepository;
    private final EmailVerificationRepository emailVerificationRepository;
    private final ResourceLoader resourceLoader;
    private final JavaMailSender mailSender;
    private static final Random RANDOM = new SecureRandom();
    private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; // 난수에 사용될 문자


    public String createCode() {
        StringBuilder stringBuilder = new StringBuilder();

        // 10 자리 난수를 생성할 것이므로 10회 반복
        for (int i = 0; i < 10; i++) {
            // 난수에 사용될 문자의 길이만큼의 랜덤한 값을 추출
            int randomIndex = RANDOM.nextInt(CHARACTERS.length());
            // 랜덤한 index 의 문자를 추출하여 문자열로 추가
            stringBuilder.append(CHARACTERS.charAt(randomIndex));
        }

        return stringBuilder.toString();
    }

    public void saveEmailVerification(String email, String verificationCode) {
        EmailVerification emailVerification = new EmailVerification();
        EmailVerification createEmailVerification = emailVerification.createEmailVerification(email, verificationCode);
        emailVerificationRepository.save(createEmailVerification);
    }

    public String renderJspToString(String verificationCode) throws IOException {
        // JSP 파일 내용을 String 으로 읽어오기
        Resource resource = resourceLoader.getResource("classpath:/templates/emailForm.jsp");
        String content = new String(Files.readAllBytes(resource.getFile().toPath()), StandardCharsets.UTF_8);

        // 인증 코드를 내용에 삽입하여 리턴
        return content.replace("${verificationCode}", verificationCode);
    }

    public void sendEmail(String email, String verificationCode) throws IOException, MessagingException {
        String emailContent = renderJspToString(verificationCode);

        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

        helper.setTo(email);
        helper.setSubject("랜덤 채팅의 이메일 인증 번호입니다.");
        helper.setText(emailContent, true);  // HTML 형식으로 보낼 때 true 설정
        helper.setFrom("youngho3358@gmail.com");

        mailSender.send(message);
    }
}

 

4. emailForm.jsp

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Email Verification</title>
    <style>
        * {
            text-align: center;
        }
        body {
            font-family: Arial, sans-serif;
            text-align: center;
        }
        h1 {
            color: #4CAF50;
        }
        .code {
            font-size: 24px;
            font-weight: bold;
            color: #333;
        }
        p {
            font-size: 16px;
        }
        .footer {
            font-size: 14px;
            color: #999;
        }
    </style>
</head>
<body>
    <h1>이메일 인증</h1>
    <p>RandomChat 가입을 환영합니다!</p>
    <p>아래의 인증코드를 입력하여 회원가입을 완료해주세요.</p>
    <div class="code">${verificationCode}</div>
    <p class="footer">감사합니다.</p>
</body>
</html>

 

5. application.yml 설정

gmail 의 smtp 서버를 활용

 

 

 

 

 

구현 후기)

 

우선은 jsp 를 활용하여 치환하면 더 편할거란 생각으로 jsp 파일을 사용하였는데 단순하게 파일을 문자열로 읽어와서 문자를 치환하는 형태이기 때문에

jsp 파일을 사용할 필요가 없었다.

결국은 간단하게 txt 파일이나 html 파일로도 구현한 부분이었다...

 

그리고 아래는 redis 를 사용하여 구현하였을때의 장, 단점과

주 DB 인 MySQL 로 구현하였을때의 , 점이다.

구분 Redis 사용 MySQL 사용
장점 - 속도: 메모리 기반으로 매우 빠르게 데이터 처리 가능.
- TTL 지원: 인증 코드의 유효 기간 설정을 TTL(Time To Live)로 간편하게 관리.
- 부하 분산: 주 DB에 부하를 덜어주어 시스템의 성능을 최적화.
- 통합성: 기존 시스템(DB)에 통합되어 추가적인 외부 스토리지 필요 없음.
- 데이터 영속성: 인증 기록이 데이터베이스에 저장되어 추적 및 분석 가능.
- 트랜잭션 지원: 인증 관련 데이터와 다른 테이블의 데이터 간 연관성을 보장.
단점 - 데이터 영속성 부족: Redis는 메모리 기반이므로 서버 재시작 시 데이터 손실 위험.
- 추가 인프라 필요: Redis 설치 및 관리 필요.
- 데이터 용량 제한: 메모리 용량에 제한이 있으므로 대규모 데이터 저장 시 비효율적.
- 속도: Redis에 비해 상대적으로 느린 데이터 처리 속도.
- 복잡성 증가: TTL 구현을 별도로 코딩해야 하며 인증 코드 만료 관리가 번거로움.
- 부하 증가: 인증 관련 데이터가 많아지면 주 DB에 부하가 가중될 가능성.
적합한 상황 - 대량의 인증 요청을 처리해야 하는 고성능 시스템.
- 인증 요청 처리 시간이 매우 중요한 서비스.
- 인증 데이터가 비교적 적고, 인증 기록을 영속적으로 저장해야 하는 경우.
- 이미 MySQL 환경에서 동작 중이며 추가적인 데이터베이스 사용이 부담스러운 경우.

 

 

 

[ 배포 시에 해당 부분 코드에 문제가 발생하여 따로 글을 포스트하여 에러 픽스 부분을 기술했습니다 ]

 

[TIL] 2024.12.14 - 인증 코드 발송 오류 해결 ( 이메일 인증 구현 )

절벽의 바로 앞까지 가서 구경할 수 있었던 홀 슈 밴드, 마치 말 발굽 같이 생겼다고 해서 이름이 홀 슈 밴드라고 한다     - TIL -문제 발생) [TIL] 2024.11.18 - jsp 를 활용하여 이메일 인증 코드 발

youngho3358.tistory.com

 

 

 

 

728x90

 

728x90

 

 

 

 

 

이메일 전송 API, 이메일 인증 구현

 

Spring Context Support 라이브러리 버전 아무거나 선택해서 추가 >> 현재 사용중인 스프링 버전으로 변경

 

라이브러리 추가할 것임 ( JavaMail API JAR, JavaMail API )

 

1.5.4 버전 라이브러리 추가

 

1.5.3 버전 라이브러리 추가

 

google 계정으로 발송해볼 것임 >> google 로그인 후 Google 계정 관리 클릭

 

파일 세팅

 

Mailconfig 파일 작성 ( 비밀번호 칸에는 앱 비밀번호가 들어가야 한다 )

 

- 앱 비밀번호 설정하기 -

앱 비밀번호 설정하기 >> 앱 이름 설정 후 만들기 클릭 >> 생성된 앱 비밀번호를 setPassword 부분에 넣어준다

 

sendmail 경로로 접속 시 메일을 보냈습니다. 출력되게 설정

 

Service 쪽으로 받을 메일 주소, 제목, 내용을 전달

 

MailServiceImpl 작성 후 테스트

 

sendmail 경로로 접속하면 정상적으로 메일이 발송된다

 

메일 전송된 것 확인

 

 

 

 

 

이제 메일의 내용을 html 형식으로 보내볼 것임

Controller 에 HTML 형식으로 메일 보내는 것 추가

 

Service 에서 text 형식이 아니라고 명시한 뒤 메일 다시 전송해봄 ( 이미지는 구글에 검색해서 나온 이미지주소를 아무거나 사용했음 )

 

html 형식으로 메일 정상적으로 전달되는 모습!!!

 

 

 

 

 

이메일 인증을 만들어볼 것임

Controller 에 경로 추가

 

auth.jsp 작성

 

Controller 에 경로 추가

 

Service 에 랜덤 수 생성 코드 추가, session 을 발급

 

Controller 에 이메일 인증 내용을 작성하여 아까 만들어둔 sendMail02 메소드를 사용하여 이메일 전송 ( a 태그 안에 랜덤으로 생성한 키를 userId 값으로 넘겨준다 )

 

이메일 전송 후 확인 ( 이동 경로도 정상적으로 들어오는지 확인 )

 

세션의 값과 파라미터로 넘어온 값이 같은지 확인하는 코드 작성 후 확인

 

확인

 

 

728x90

+ Recent posts