๋ณธ ํ๋ก์ ํธ ๊ฐ๋ฐ์ ๊ณผ์ ํ์์ ํ์ด๋ ํ๋ก์ ํธ ๊ธฐํ, ๊ฐ๋ฐ, ๋ฐฐํฌ ๊ณผ์ ์ ๋ด๊ณ ์์ต๋๋ค.
OneStack
์๋ฆฐ๋์ฟ ์ต๊ณ ์์ด์! ์์ฑ์ : Kyle โญ๏ธ โญ๏ธ โญ๏ธ โญ๏ธ โญ๏ธ
www.onestack.store
JavaMailSender๋?
JavaMailSender๋ Spring Framework์์ ์ ๊ณตํ๋
์ด๋ฉ์ผ ๋ฐ์ก ๊ธฐ๋ฅ์ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋๋ก ์ง์ํ๋ ์ธํฐํ์ด์ค์
๋๋ค.
SMTP ์ค์ ์ ํ์ฉํ์ฌ ๋ฉ์ผ ์๋ฒ์ ์ฐ๊ฒฐํ๊ณ HTML ํ
ํ๋ฆฟ์ ์ ์ฉํ ์ ์์ผ๋ฉฐ, ๋น๋๊ธฐ ๋ฉ์ผ ๋ฐ์ก๋ ๊ฐ๋ฅํฉ๋๋ค.
JavaMailSender์ ์ฃผ์ ๊ธฐ๋ฅ
- SMTP ์๋ฒ๋ฅผ ์ด์ฉํ ๋ฉ์ผ ๋ฐ์ก
- HTML ํ ํ๋ฆฟ ์ง์ (Thymeleaf ๋ฑ ํ์ฉ ๊ฐ๋ฅ)
- ์ฒจ๋ถ ํ์ผ ์ ์ก ๊ธฐ๋ฅ ์ ๊ณต
- ๋น๋๊ธฐ ๋ฉ์ผ ๋ฐ์ก ๊ฐ๋ฅ
๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๊ธฐ๋ฅ ๊ตฌํ
๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๊ธฐ๋ฅ์ ์ฌ์ฉ์๊ฐ ๋น๋ฐ๋ฒํธ๋ฅผ ์์์ ๋ ์ด๋ฉ์ผ์ ํตํด ์ฌ์ค์ ํ ์ ์๋๋ก ๋์์ฃผ๋ ๊ธฐ๋ฅ์
๋๋ค.
์๋์ ๊ฐ์ ๋จ๊ณ๋ก ๊ตฌํ๋ฉ๋๋ค.
1. ์์กด์ฑ ์ถ๊ฐ
implementation 'org.springframework.boot:spring-boot-starter-mail'
2. SMTP ์ค์ (application.properties)
spring.mail.host=smtp.gmail.com
spring.mail.port=587 spring.mail.username=${GMAIL_USERNAME}
spring.mail.password=${GMAIL_PASSWORD}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
๐น ํ๊ฒฝ ๋ณ์๋ก ๊ด๋ฆฌํ์ฌ ๋ณด์์ฑ์ ๋์ด๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
๐น Gmail์ ๊ฒฝ์ฐ '์ฑ ๋น๋ฐ๋ฒํธ'๋ฅผ ๋ฐ๊ธ๋ฐ์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
3. ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ํ ํฐ ์ํฐํฐ
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class PasswordResetToken {
private String token;
private String memberId;
private LocalDateTime expiryDate;
private boolean used;
}
๐น UUID๋ฅผ ์ฌ์ฉํ์ฌ ๊ณ ์ ํ ํ ํฐ์ ์์ฑํ๊ณ ,
๐น 24์๊ฐ ๋์๋ง ์ ํจํ๋๋ก ๋ง๋ฃ ์๊ฐ์ ์ค์ ํฉ๋๋ค.
4. MemberService์์์ ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋ก์ง
@Service
@Slf4j
@RequiredArgsConstructor
public class MemberService {
private final JavaMailSender mailSender;
private final PasswordEncoder passwordEncoder;
public void sendPasswordResetEmail(String memberId, String email) {
try {
// ํ ํฐ ์์ฑ
String token = UUID.randomUUID().toString();
LocalDateTime expiryDate = LocalDateTime.now().plusHours(24);
// ํ ํฐ ์ ์ฅ
PasswordResetToken resetToken = new PasswordResetToken(token, memberId, expiryDate, false);
memberMapper.savePasswordResetToken(resetToken);
// ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋งํฌ ์์ฑ
String resetLink = "https://www.onestack.store/resetPassword?token=" + token;
// ์ด๋ฉ์ผ ๋ด์ฉ ๊ตฌ์ฑ
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email);
message.setSubject("OneStack ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ");
message.setText("์๋
ํ์ธ์!\n" +
"๋น๋ฐ๋ฒํธ ์ฌ์ค์ ์ ์ํ ๋งํฌ๋ฅผ ๋ณด๋ด๋๋ฆฝ๋๋ค.\n\n" +
resetLink + "\n\n" +
"์ด ๋งํฌ๋ 24์๊ฐ ๋์ ์ ํจํฉ๋๋ค.");
// ๋ฉ์ผ ๋ฐ์ก
mailSender.send(message);
log.info("๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋ฉ์ผ ๋ฐ์ก ์๋ฃ: {}", email);
} catch (Exception e) {
log.error("๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋ฉ์ผ ๋ฐ์ก ์คํจ: {}", e.getMessage());
throw new RuntimeException("๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋ฉ์ผ ๋ฐ์ก์ ์คํจํ์ต๋๋ค.");
}
}
public void resetPassword(String token, String newPassword) {
PasswordResetToken resetToken = memberMapper.findByToken(token);
if (resetToken == null || resetToken.isUsed() || resetToken.getExpiryDate().isBefore(LocalDateTime.now())) {
throw new RuntimeException("์ ํจํ์ง ์๊ฑฐ๋ ๋ง๋ฃ๋ ํ ํฐ์
๋๋ค.");
}
try {
// ๋น๋ฐ๋ฒํธ ์ํธํ ํ ์
๋ฐ์ดํธ
String encodedPassword = passwordEncoder.encode(newPassword);
memberMapper.updatePassword(resetToken.getMemberId(), encodedPassword);
// ํ ํฐ ์ฌ์ฉ ์๋ฃ ์ฒ๋ฆฌ
memberMapper.deletePasswordResetToken(token);
log.info("๋น๋ฐ๋ฒํธ ์ฌ์ค์ ์๋ฃ: {}", resetToken.getMemberId());
} catch (Exception e) {
log.error("๋น๋ฐ๋ฒํธ ์ฌ์ค์ ์คํจ: {}", e.getMessage());
throw new RuntimeException("๋น๋ฐ๋ฒํธ ์ฌ์ค์ ์ ์คํจํ์ต๋๋ค.");
}
}
}
๐น ํ ํฐ์ ์์ฑํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๊ณ ,
๐น ์ด๋ฉ์ผ์ ํตํด ์ฌ์ฉ์๊ฐ ๋งํฌ๋ฅผ ํด๋ฆญํ๋ฉด ๋น๋ฐ๋ฒํธ๋ฅผ ์ฌ์ค์ ํ ์ ์๋๋ก ์ฒ๋ฆฌํฉ๋๋ค.
5. ์ปจํธ๋กค๋ฌ์์์ ์ฒ๋ฆฌ
@Controller
@Slf4j
public class MemberController {
@PostMapping("/findPass")
@ResponseBody
public Map<String, Object> findPass(@RequestParam String memberId, @RequestParam String email) {
Map<String, Object> response = new HashMap<>();
try {
memberService.sendPasswordResetEmail(memberId, email);
response.put("success", true);
response.put("message", "๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋งํฌ๊ฐ ์ด๋ฉ์ผ๋ก ์ ์ก๋์์ต๋๋ค.");
} catch (Exception e) {
response.put("success", false);
response.put("message", e.getMessage());
}
return response;
}
@PostMapping("/resetPassword")
@ResponseBody
public Map<String, Object> resetPassword(@RequestParam String token, @RequestParam String newPassword) {
Map<String, Object> response = new HashMap<>();
try {
memberService.resetPassword(token, newPassword);
response.put("success", true);
response.put("message", "๋น๋ฐ๋ฒํธ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค.");
} catch (Exception e) {
response.put("success", false);
response.put("message", e.getMessage());
}
return response;
}
}
๐น ์์ฒญ์ด ๋ค์ด์ค๋ฉด ์ ์ ํ ์๋น์ค ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ์ฒ๋ฆฌํฉ๋๋ค.
๐น ์๋ฌ ๋ฐ์ ์ ์ฌ์ฉ์์๊ฒ ๋ช
ํํ ๋ฉ์์ง๋ฅผ ๋ฐํํฉ๋๋ค.
๊ตฌํ์ ํน์ง๊ณผ ์ฅ์
โ ๋ณด์ ๊ฐํ
- UUID๋ฅผ ํ์ฉํ ๊ณ ์ ํ ํฐ ์์ฑ
- 24์๊ฐ ์ ํ๋ ํ ํฐ ์ ํจ๊ธฐ๊ฐ
- ๋น๋ฐ๋ฒํธ ์ํธํ ์ ์ฅ
- ์ฌ์ฉ๋ ํ ํฐ ์ฆ์ ์ญ์
โ ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์
- ์ง๊ด์ ์ธ ์ด๋ฉ์ผ ์๋ด ๋ฉ์์ง
- ํด๋ฆญ ํ ๋ฒ์ผ๋ก ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ํ์ด์ง ์ ๊ทผ
- ์ค์๊ฐ ํผ๋๋ฐฑ ์ ๊ณต
โ ํ์ฅ์ฑ ๊ณ ๋ ค
- HTML ํ ํ๋ฆฟ ์ ์ฉ ๊ฐ๋ฅ
- ๋น๋๊ธฐ ๋ฉ์ผ ๋ฐ์ก ๊ฐ๋ฅ
- ๋ค์ํ ๋ฉ์ผ ์๋น์ค ์ ์ฉ ๊ฐ๋ฅ
๊ฐ์ ๊ฐ๋ฅ ํฌ์ธํธ
๐น HTML ํ ํ๋ฆฟ ์ ์ฉ
- Thymeleaf ๋ฑ์ ํ์ฉํ์ฌ ๋ณด๋ค ์๊ฐ์ ์ผ๋ก ๊ฐ์ ๋ ๋ฉ์ผ ํ ํ๋ฆฟ ์ ์ฉ
- ๋ธ๋๋ ๋ก๊ณ ๋ฐ ์คํ์ผ ์ถ๊ฐ
๐น ๋น๋๊ธฐ ์ฒ๋ฆฌ ์ ์ฉ
- @Async ์ด๋ ธํ ์ด์ ์ ํ์ฉํ์ฌ ๋น๋๊ธฐ ๋ฐฉ์์ผ๋ก ๋ฉ์ผ ๋ฐ์ก
- ์ฌ์ฉ์ ๋๊ธฐ ์๊ฐ์ ๋จ์ถํ๊ณ ์ฑ๋ฅ ๊ฐ์
๐น ๋ณด์ ๊ฐํ
- IP ๊ธฐ๋ฐ ์์ฒญ ์ ํ
- ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์ ์ถ๊ฐ ์ธ์ฆ ๋จ๊ณ ๋์
- ๋น์ ์์ ์ธ ์์ฒญ ํ์ง ๋ฐ ์ฐจ๋จ
๋ง๋ฌด๋ฆฌ
JavaMailSender๋ฅผ ํ์ฉํ ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๊ธฐ๋ฅ์ ๋ณด์์ฑ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ์ ๋์์ ๊ณ ๋ คํด์ผ ํ๋ ์ค์ํ ๊ธฐ๋ฅ์
๋๋ค.
์ ๊ตฌํ ๋ฐฉ์์ ์ฐธ๊ณ ํ์ฌ ์ฌ๋ฌ๋ถ์ ํ๋ก์ ํธ์ ๋ง๊ฒ ์ ์ฉํด ๋ณด์ธ์!
๊ถ๊ธํ ์ ์ด ์๊ฑฐ๋, ๋ ์ข์ ๋ฐฉ๋ฒ์ด ์๋ค๋ฉด ๋๊ธ๋ก ๊ณต์ ํด์ฃผ์ธ์! ๐
'๐ Web > OneStack' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [OneStack]ํ ํ๋ก์ ํธ ๊ฐ๋ฐ ๊ณผ์ - WebSocket ์ค์๊ฐ ์ฑํ (0) | 2025.03.04 |
|---|---|
| [OneStack]ํ ํ๋ก์ ํธ ๊ฐ๋ฐ ๊ณผ์ - OAuth2 ์์ ๋ก๊ทธ์ธ (0) | 2025.03.03 |
| [OneStack]ํ ํ๋ก์ ํธ ๊ฐ๋ฐ ๊ณผ์ - ๊ธฐํ (1) | 2025.02.26 |