Skip to content

Commit

Permalink
feat: email로 비밀번호 재설정
Browse files Browse the repository at this point in the history
feat: email로 비밀번호 재설정
  • Loading branch information
hellomatia authored May 22, 2024
2 parents c08d6f2 + bf23343 commit 2d42931
Show file tree
Hide file tree
Showing 14 changed files with 1,814 additions and 10 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'

compileOnly 'org.projectlombok:lombok'

Expand All @@ -54,6 +56,8 @@ dependencies {

asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

implementation 'org.springframework.boot:spring-boot-starter-mail'
}

tasks.named('test') {
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/mlog/config/MailConfigure.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.mlog.config;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@Getter
@Setter
@ConfigurationProperties(prefix = "mail")
public class MailConfigure {
private String root;
private String senderEmail;
}
2 changes: 1 addition & 1 deletion src/main/java/com/mlog/config/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 모든 경로에 대해
.allowedOrigins("http://localhost:5173", "http://m-log.site")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 모든 HTTP 메소드 허용
.allowedHeaders("*") // 모든 헤더 허용
.allowCredentials(true); // 쿠키를 포함한 요청 허용
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/com/mlog/config/WebSecurityConfigure.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,17 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti

.headers(AbstractHttpConfigurer::disable)

.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/password-reset-request", "/user/reset-password", "/user/login", "/user/join", "/", "/index.html", "/password-reset-success.html").permitAll()
.anyRequest().authenticated())

.exceptionHandling(exceptionHandling -> exceptionHandling
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(unauthorizedHandler))

.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/login","/user/join", "/", "/index.html").permitAll()
.anyRequest().authenticated())

.formLogin(AbstractHttpConfigurer::disable);

http
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.mlog.user.controller;

import com.mlog.user.service.PasswordResetService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestParam;

import static com.mlog.util.ApiUtils.ApiResult;
import static com.mlog.util.ApiUtils.success;

@Controller
@RequiredArgsConstructor
public class PasswordResetController {

private final PasswordResetService passwordResetService;

// 비밀번호 재설정 요청
@PostMapping("/user/password-reset-request")
@ResponseBody
public ApiResult<Boolean> passwordResetRequest(@RequestParam String email) {
return success(passwordResetService.sendPasswordResetMail(email));
}

// 비밀번호 재설정 토큰 검증 및 비밀번호 재설정
@PostMapping("/user/reset-password")
public String resetPassword(@RequestParam String token, @RequestParam String newPassword) {
passwordResetService.resetPassword(token, newPassword);
return "password-reset-success";
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/mlog/user/repository/UserMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ public interface UserMapper {
@Update("update users set email=#{email}, name=#{name}, password=#{password}, role=#{role}, updated_at=now() " +
"where id = #{id}")
void update(User user);

@Update("update users set password=#{password}, updated_at=now() " +
"where email = #{email}")
void updatePassword(String email, String password);
}
85 changes: 85 additions & 0 deletions src/main/java/com/mlog/user/service/PasswordResetService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.mlog.user.service;

import com.mlog.config.MailConfigure;
import com.mlog.error.UnauthorizedException;
import com.mlog.user.repository.UserMapper;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class PasswordResetService {

private final JavaMailSender javaMailSender;
private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;
private final MailConfigure mailConfigure;
private final Map<String, String> tokenStore = new HashMap<>();

public Boolean sendPasswordResetMail(String email) {
userMapper.findByEmail(email)
.orElseThrow(() -> new UnauthorizedException("not valid email"));
String token = generateToken();
tokenStore.put(token, email);
MimeMessage message = createPasswordResetMail(email, token);
javaMailSender.send(message);
return true;
}

private String generateToken() {
UUID uuid = UUID.randomUUID();
String uuidAsString = uuid.toString();
return Base64.getUrlEncoder().withoutPadding().encodeToString(uuidAsString.getBytes());
}

private MimeMessage createPasswordResetMail(String email, String token) {
MimeMessage message = javaMailSender.createMimeMessage();

try {
message.setFrom(mailConfigure.getSenderEmail());
message.setRecipients(MimeMessage.RecipientType.TO, email);
message.setSubject("M-LOG 계정 비밀번호 재설정 요청");
String body = "";
body += "<div style=\"font-family: Arial, sans-serif; padding: 20px; background-color: #f4f4f4;\">";
body += "<div style=\"max-width: 600px; margin: auto; background-color: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);\">";
body += "<img src=\"https://m-log-photo-s3.s3.ap-southeast-2.amazonaws.com/26e1ca9d-bf7f-41ee-89cb-103a7379270b.png\" style=\"display: block; margin: 0 auto 20px; width: 100px;\">";
body += "<h2 style=\"color: #333; text-align: center;\">비밀번호 재설정 요청</h2>"; // 제목 색상 수정
body += "<p>안녕하세요,</p>";
body += "<p>M-LOG 계정 비밀번호 재설정을 요청하셨습니다. 아래 양식을 사용하여 비밀번호를 재설정하세요!</p>";
body += "<form action=\"" + mailConfigure.getRoot() + "/user/reset-password\" method=\"post\" target=\"_blank\" style=\"background-color: #f9f9f9; padding: 20px; border: 1px solid #ddd; border-radius: 5px;\">";
body += "<input type=\"hidden\" name=\"token\" value=\"" + token + "\">";
body += "<label for=\"newPassword\" style=\"display: block; margin-bottom: 10px; font-weight: bold; color: #333;\">새 비밀번호:</label>";
body += "<input type=\"password\" id=\"newPassword\" name=\"newPassword\" style=\"width: 90%; padding: 10px; margin-bottom: 20px; border: 1px solid #ccc; border-radius: 5px;\">";
body += "<button type=\"submit\" style=\"background-color: #333; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; width: 100%;\">비밀번호 재설정</button>";
body += "</form>";
body += "<p style=\"margin-top: 20px;\">감사합니다,<br>M-LOG 팀</p>";
body += "</div>";
body += "</div>";

message.setText(body, "UTF-8", "html");
} catch (MessagingException e) {
e.printStackTrace();
}

return message;
}

public Boolean resetPassword(String token, String newPassword) {
String email = tokenStore.get(token);
if (email == null) {
throw new UnauthorizedException("not valid email");
}
userMapper.updatePassword(email, passwordEncoder.encode(newPassword));
tokenStore.remove(token);
return true;
}
}
7 changes: 3 additions & 4 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
spring:
profiles:
active:
# - dev
include:
# - local
- prod
#

mybatis:
configuration:
map-underscore-to-camel-case: true
map-underscore-to-camel-case: true

18 changes: 18 additions & 0 deletions src/main/resources/profiles/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ spring:
sql:
init:
platform: h2
mail:
host: smtp-mail.outlook.com
port: 587
username: ${MAIL.USERNAME}
password: ${MAIL.PASSWORD}
properties:
mail:
smtp:
auth: true
starttls:
enable: true
timeout: 5000
enable: true

mail:
root: ${MAIL.ROOT.local}
sender-email: ${MAIL.USERNAME}

jwt:
token:
header: X-MLOG-AUTH
Expand Down
18 changes: 18 additions & 0 deletions src/main/resources/profiles/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ spring:
ddl-auto: none
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
mail:
host: smtp-mail.outlook.com
port: 587
username: ${MAIL.USERNAME}
password: ${MAIL.PASSWORD}
properties:
mail:
smtp:
auth: true
starttls:
enable: true
timeout: 5000
enable: true

mail:
root: ${MAIL.ROOT.local}
sender-email: ${MAIL.USERNAME}

jwt:
token:
header: X-MLOG-AUTH
Expand Down
17 changes: 17 additions & 0 deletions src/main/resources/profiles/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@ spring:
ddl-auto: none
properties:
hibernate.dialect: org.hibernate.dialect.MySQLDialect
mail:
host: smtp-mail.outlook.com
port: 587
username: ${MAIL.USERNAME}
password: ${MAIL.PASSWORD}
properties:
mail:
smtp:
auth: true
starttls:
enable: true
timeout: 5000
enable: true

mail:
root: ${MAIL.ROOT.prod}
sender-email: ${MAIL.USERNAME}

jwt:
token:
Expand Down
Loading

0 comments on commit 2d42931

Please sign in to comment.