Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.ezcode.codetest.application.usermanagement.auth.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
@Schema(description = "비밀번호 찾기 요청")
public class FindPasswordRequest {
private String email;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.ezcode.codetest.application.usermanagement.auth.dto.response;

public record FindPasswordResponse(
String message
) {
public static FindPasswordResponse from(String message) {
return new FindPasswordResponse(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.ezcode.codetest.application.usermanagement.auth.dto.request.FindPasswordRequest;
import org.ezcode.codetest.application.usermanagement.auth.dto.request.VerifyEmailCodeRequest;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.FindPasswordResponse;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.RefreshTokenResponse;
import org.ezcode.codetest.application.usermanagement.auth.dto.request.SigninRequest;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.SendEmailCodeResponse;
Expand Down Expand Up @@ -99,15 +101,16 @@ private void updateExistingUser(User existUser, String encodedPassword) {

@Transactional
public SendEmailCodeResponse sendEmailCode(Long userId, String email) {
mailService.sendMail(userId, email);
mailService.sendButtonMail(userId, email);
return SendEmailCodeResponse.from("인증 코드를 전송했습니다.");
}

@Transactional
public VerifyEmailCodeResponse verifyEmailCode(Long userId, VerifyEmailCodeRequest verifyEmailCodeRequest) {
boolean isMatch = mailService.verifyCode(userId, verifyEmailCodeRequest.getVerificationCode());
public VerifyEmailCodeResponse verifyEmailCode(String email, String key) {
User user = userDomainService.getUserByEmail(email);

boolean isMatch = mailService.verifyCode(user.getId(), key);

User user = userDomainService.getUserById(userId);
if (isMatch){
user.setVerified();
return VerifyEmailCodeResponse.from("인증되었습니다");
Expand Down Expand Up @@ -202,9 +205,43 @@ public RefreshTokenResponse refreshToken(String token) {

User user = userDomainService.getUserById(userId);

String newAccessToken = createAccessToken(user);
String newAccessToken = jwtUtil.createAccessToken(
user.getId(),
user.getEmail(),
user.getRole(),
user.getUsername(),
user.getNickname(),
user.getTier()
);

return RefreshTokenResponse.from(newAccessToken);
}

//비밀번호 찾기 메일 전송
@Transactional
public FindPasswordResponse findPassword(FindPasswordRequest request) {

User user = userDomainService.getUserByEmail(request.getEmail());
if(user == null || user.isDeleted()){
throw new AuthException(AuthExceptionCode.USER_NOT_FOUND);
}

mailService.sendPasswordMail(user.getId(), request.getEmail());

return FindPasswordResponse.from("이메일로 전송되었습니다.");
}

//메일로 받은 링크를 통해 비번 변경
public FindPasswordResponse changePasswordByEmail(String email, String key) {

User user = userDomainService.getUserByEmail(email);

boolean isMatch = mailService.verifyCode(user.getId(), key);

if (isMatch){
return FindPasswordResponse.from("인증되었습니다");
} else {
throw new UserException(UserExceptionCode.NOT_MATCH_CODE);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.ezcode.codetest.common.security.config;

import java.util.List;

import org.ezcode.codetest.common.security.util.SecurityPath;
import org.ezcode.codetest.domain.user.service.CustomOAuth2UserService;
import org.ezcode.codetest.common.security.hander.CustomSuccessHandler;
Expand All @@ -10,6 +12,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
Expand All @@ -20,6 +23,9 @@
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import jakarta.servlet.DispatcherType;
import jakarta.servlet.http.HttpServletResponse;
Expand All @@ -42,6 +48,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti

return http
// CSRF, Form 로그인, HTTP Basic 인증 비활성화
.cors(Customizer.withDefaults()) // CORS 설정 추가
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
Expand Down Expand Up @@ -121,5 +128,20 @@ public AccessDeniedHandler customAccessDeniedHandler() {
};
}

//CORS 설정 추가
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();

configuration.setAllowedOrigins(List.of("*")); // 다 허용
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(false);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,82 @@ public class MailService {
@Value("${spring.mail.username}")
private String senderEmail;

private static final long EXPIRATION_MINUTES = 3;
private static final long EXPIRATION_MINUTES = 10;

public void sendMail(Long userId, String mail) {
MimeMessage message = CreateMail(userId, mail);
public void sendCodeMail(Long userId, String email) {
MimeMessage message = CreateMail(userId, email);
javaMailSender.send(message);
}

public void sendButtonMail(Long userId, String email) {
MimeMessage message = CreateButtonMail(userId, email);
javaMailSender.send(message);
}

public void sendPasswordMail(Long userId, String email) {
MimeMessage message = CreatePasswordMail(userId, email);
javaMailSender.send(message);
}

public MimeMessage CreateButtonMail(Long userId, String email) {
MimeMessage message = javaMailSender.createMimeMessage();
String key = createNumber(userId); //radis에 유저id&코드로 저장 (10분)

try {
message.setFrom(senderEmail);
message.setRecipients(MimeMessage.RecipientType.TO, email);
message.setSubject("EZcode 이메일 인증");
String body = "";
body += "<h3>" + "아래 버튼을 클릭하여 이메일 인증을 완료해 주세요" + "</h3>";
// 이메일 버튼
body += "<a href='http://localhost:8080/api/auth/verify?email="+ email + "&key=" + key + "' target='_blenk'>이메일 인증 확인</a>";
body += "<h3>" + "감사합니다." + "</h3>";
message.setText(body,"UTF-8", "html");
} catch (MessagingException e) {
throw new RuntimeException(e);
}

return message;
}

public MimeMessage CreatePasswordMail(Long userId, String email){
MimeMessage message = javaMailSender.createMimeMessage();

String redisKey = "PASSWORD_KEY:" + userId;
String verificationCode = generateRandomCode();

redisTemplate.opsForValue().set(
redisKey,
verificationCode,
EXPIRATION_MINUTES,
TimeUnit.MINUTES
);

try {
message.setFrom(senderEmail);
message.setRecipients(MimeMessage.RecipientType.TO, email);
message.setSubject("EZcode 이메일 인증");
String body = "";
body += "<h3>" + "아래 버튼을 클릭하여 비밀번호 변경을 완료해 주세요" + "</h3>";
// 이메일 버튼
body += "<a href='http://localhost:8080/api/auth/password?email="+ email + "&key=" + verificationCode + "' target='_blenk'>비밀번호 변경하기</a>";
body += "<h3>" + "감사합니다." + "</h3>";
message.setText(body,"UTF-8", "html");
} catch (MessagingException e) {
throw new RuntimeException(e);
}

return message;
}

//메일 보내기
public MimeMessage CreateMail(Long userId, String mail) {
public MimeMessage CreateMail(Long userId, String email) {
String code = createNumber(userId);
MimeMessage message = javaMailSender.createMimeMessage();

try {
message.setFrom(senderEmail);
message.setRecipients(MimeMessage.RecipientType.TO, mail);
message.setRecipients(MimeMessage.RecipientType.TO, email);
message.setSubject("EZcode 이메일 인증");
String body = "";
body += "<h3>" + "요청하신 인증 번호입니다." + "</h3>";
Expand Down Expand Up @@ -93,4 +154,5 @@ public boolean verifyCode(Long userId, String inputCode) {
}
return isMatch;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import java.util.Optional;

import org.ezcode.codetest.application.usermanagement.auth.dto.request.FindPasswordRequest;
import org.ezcode.codetest.application.usermanagement.auth.dto.request.VerifyEmailCodeRequest;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.FindPasswordResponse;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.RefreshTokenResponse;
import org.ezcode.codetest.application.usermanagement.auth.dto.request.SigninRequest;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.SendEmailCodeResponse;
Expand All @@ -18,10 +20,13 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -87,12 +92,29 @@ public ResponseEntity<SendEmailCodeResponse> sendMailCode(
return ResponseEntity.status(HttpStatus.CREATED).body(authService.sendEmailCode(authUser.getId(), authUser.getEmail()));
}

//이메일에서 버튼 클릭하면 자동으로 연결
@Operation(summary = "이메일 코드 입력 및 인증", description = "이메일로 받은 코드를 입력하여 이메일 인증된 회원으로 전환합니다")
@PutMapping("/email/verify")
@GetMapping("/auth/verify")
public ResponseEntity<VerifyEmailCodeResponse> verifyEmailCode(
@AuthenticationPrincipal AuthUser authUser,
@Valid @RequestBody VerifyEmailCodeRequest verifyEmailCodeRequest
@RequestParam String email,
@RequestParam String key
){
return ResponseEntity.status(HttpStatus.OK).body(authService.verifyEmailCode(authUser.getId(), verifyEmailCodeRequest));
return ResponseEntity.status(HttpStatus.OK).body(authService.verifyEmailCode(email, key));
}


@PostMapping("/auth/find-password")
public ResponseEntity<FindPasswordResponse> findPassword(
@RequestBody FindPasswordRequest request
){
return ResponseEntity.status(HttpStatus.OK).body(authService.findPassword(request));
}

@GetMapping("/auth/verify-password-code")
public ResponseEntity<FindPasswordResponse> changePasswordByEmail(
@RequestParam String email,
@RequestParam String key
){
return ResponseEntity.status(HttpStatus.OK).body(authService.changePasswordByEmail(email, key));
}
}