diff --git a/src/main/java/econo/buddybridge/auth/controller/AuthController.java b/src/main/java/econo/buddybridge/auth/controller/AuthController.java index dc8cfe9..e35e1da 100644 --- a/src/main/java/econo/buddybridge/auth/controller/AuthController.java +++ b/src/main/java/econo/buddybridge/auth/controller/AuthController.java @@ -1,99 +1,37 @@ package econo.buddybridge.auth.controller; -import econo.buddybridge.auth.OAuthProvider; -import econo.buddybridge.auth.dto.kakao.KakaoLoginParams; -import econo.buddybridge.auth.exception.AlreadyLogoutException; -import econo.buddybridge.auth.jwt.AuthToken; -import econo.buddybridge.auth.resolver.MemberToken; -import econo.buddybridge.auth.service.OAuthLoginService; +import econo.buddybridge.auth.service.AuthService; import econo.buddybridge.common.annotation.AllowAnonymous; -import econo.buddybridge.member.dto.MemberResDto; +import econo.buddybridge.member.dto.MemberSignUpReqDto; +import econo.buddybridge.member.dto.MemberSignUpResDto; import econo.buddybridge.utils.api.ApiResponse; import econo.buddybridge.utils.api.ApiResponse.CustomBody; import econo.buddybridge.utils.api.ApiResponseGenerator; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpSession; -import java.net.URI; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; -@Slf4j @RestController @RequiredArgsConstructor -@RequestMapping("/api/oauth") +@RequestMapping("/api/auth") @Tag(name = "인증 API", description = "인증 관련 API") public class AuthController { - private final OAuthLoginService oAuthLoginService; + private final AuthService authService; - @Value("${oauth.kakao.url.front-url}") - private String frontUrl; - - @Operation(summary = "로그아웃", description = "세션을 제거합니다.") + @Operation(summary = "회원 가입", description = "회원을 추가합니다.") + @PostMapping("/signup") @AllowAnonymous - @PostMapping("/logout") - public ApiResponse> logout(HttpServletRequest request) { - HttpSession session = request.getSession(false); - if (session != null) { - session.invalidate(); - oAuthLoginService.logout(OAuthProvider.KAKAO); - return ApiResponseGenerator.success("로그아웃 성공", HttpStatus.OK); - } - throw AlreadyLogoutException.EXCEPTION; - } - - @Operation(summary = "카카오 소셜 로그인 (코드로 로그인)", description = "Redirect URL이 백엔드 주소로 설정될 때 사용합니다.") - @GetMapping("/login") - public ApiResponse> login(@RequestParam("code") String code, HttpServletRequest request) { - KakaoLoginParams params = new KakaoLoginParams(code); - - MemberResDto memberDto = oAuthLoginService.login(params); - - HttpSession session = request.getSession(true); - session.setAttribute("memberId", memberDto.memberId()); - - // 프론트엔드 주소로 redirect - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setLocation(URI.create(frontUrl)); - - return ApiResponseGenerator.success(memberDto, httpHeaders, HttpStatus.PERMANENT_REDIRECT); - } - - @Operation(summary = "카카오 소셜 로그인 (토큰으로 로그인)", description = "Redirect URL이 프론트엔드 주소로 설정될 때 사용합니다.") - @PostMapping("/login") - public ApiResponse> login(@RequestBody KakaoLoginParams params, HttpServletRequest request) { - MemberResDto memberDto = oAuthLoginService.login(params); - - HttpSession session = request.getSession(true); - session.setAttribute("memberId", memberDto.memberId()); - - return ApiResponseGenerator.success(memberDto, HttpStatus.OK); - } - - // 소셜로그인 with JWT - @Operation(summary = "카카오 소셜 로그인 (JWT)", description = "JWT를 이용하여 로그인합니다.") - @PostMapping("/login/jwt") - public ApiResponse> loginWithToken(@RequestBody KakaoLoginParams params) { - AuthToken authToken = oAuthLoginService.loginWithToken(params); - return ApiResponseGenerator.success(authToken, HttpStatus.OK); - } - - // refresh token 재발급 - @Operation(summary = "Access Token, Refresh Token 재발급", description = "Refresh Token을 이용하여 두 토큰 모두 재발급합니다.") - @PostMapping("/reissue") - public ApiResponse> reissue(@MemberToken String token) { - AuthToken authToken = oAuthLoginService.reissue(token); - return ApiResponseGenerator.success(authToken, HttpStatus.OK); + public ApiResponse> signUp( + @Valid @RequestBody MemberSignUpReqDto memberSignUpReqDto + ) { + MemberSignUpResDto memberSignUpResDto = authService.signUp(memberSignUpReqDto); + return ApiResponseGenerator.success(memberSignUpResDto, HttpStatus.OK); } } diff --git a/src/main/java/econo/buddybridge/auth/controller/OAuthController.java b/src/main/java/econo/buddybridge/auth/controller/OAuthController.java new file mode 100644 index 0000000..4c2edc1 --- /dev/null +++ b/src/main/java/econo/buddybridge/auth/controller/OAuthController.java @@ -0,0 +1,100 @@ +package econo.buddybridge.auth.controller; + +import econo.buddybridge.auth.OAuthProvider; +import econo.buddybridge.auth.dto.kakao.KakaoLoginParams; +import econo.buddybridge.auth.exception.AlreadyLogoutException; +import econo.buddybridge.auth.jwt.AuthToken; +import econo.buddybridge.auth.resolver.MemberToken; +import econo.buddybridge.auth.service.OAuthLoginService; +import econo.buddybridge.common.annotation.AllowAnonymous; +import econo.buddybridge.member.dto.MemberResDto; +import econo.buddybridge.utils.api.ApiResponse; +import econo.buddybridge.utils.api.ApiResponse.CustomBody; +import econo.buddybridge.utils.api.ApiResponseGenerator; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.net.URI; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/oauth") +@Tag(name = "인증 API", description = "인증 관련 API") +public class OAuthController { + + private final OAuthLoginService oAuthLoginService; + + @Value("${oauth.kakao.url.front-url}") + private String frontUrl; + + @Operation(summary = "로그아웃", description = "세션을 제거합니다.") + @AllowAnonymous + @PostMapping("/logout") + public ApiResponse> logout(HttpServletRequest request) { + HttpSession session = request.getSession(false); + if (session != null) { + session.invalidate(); + oAuthLoginService.logout(OAuthProvider.KAKAO); + return ApiResponseGenerator.success("로그아웃 성공", HttpStatus.OK); + } + throw AlreadyLogoutException.EXCEPTION; + } + + @Operation(summary = "카카오 소셜 로그인 (코드로 로그인)", description = "Redirect URL이 백엔드 주소로 설정될 때 사용합니다.") + @GetMapping("/login") + public ApiResponse> login(@RequestParam("code") String code, HttpServletRequest request) { + KakaoLoginParams params = new KakaoLoginParams(code); + + MemberResDto memberDto = oAuthLoginService.login(params); + + HttpSession session = request.getSession(true); + session.setAttribute("memberId", memberDto.memberId()); + + // 프론트엔드 주소로 redirect + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setLocation(URI.create(frontUrl)); + + return ApiResponseGenerator.success(memberDto, httpHeaders, HttpStatus.PERMANENT_REDIRECT); + } + + @Operation(summary = "카카오 소셜 로그인 (토큰으로 로그인)", description = "Redirect URL이 프론트엔드 주소로 설정될 때 사용합니다.") + @PostMapping("/login") + public ApiResponse> login(@RequestBody KakaoLoginParams params, HttpServletRequest request) { + MemberResDto memberDto = oAuthLoginService.login(params); + + HttpSession session = request.getSession(true); + session.setAttribute("memberId", memberDto.memberId()); + + return ApiResponseGenerator.success(memberDto, HttpStatus.OK); + } + + // 소셜로그인 with JWT + @Operation(summary = "카카오 소셜 로그인 (JWT)", description = "JWT를 이용하여 로그인합니다.") + @PostMapping("/login/jwt") + public ApiResponse> loginWithToken(@RequestBody KakaoLoginParams params) { + AuthToken authToken = oAuthLoginService.loginWithToken(params); + return ApiResponseGenerator.success(authToken, HttpStatus.OK); + } + + // refresh token 재발급 + @Operation(summary = "Access Token, Refresh Token 재발급", description = "Refresh Token을 이용하여 두 토큰 모두 재발급합니다.") + @PostMapping("/reissue") + public ApiResponse> reissue(@MemberToken String token) { + AuthToken authToken = oAuthLoginService.reissue(token); + return ApiResponseGenerator.success(authToken, HttpStatus.OK); + } +} diff --git a/src/main/java/econo/buddybridge/auth/controller/AuthTestController.java b/src/main/java/econo/buddybridge/auth/controller/OAuthTestController.java similarity index 98% rename from src/main/java/econo/buddybridge/auth/controller/AuthTestController.java rename to src/main/java/econo/buddybridge/auth/controller/OAuthTestController.java index cf90cf2..d9ecae8 100644 --- a/src/main/java/econo/buddybridge/auth/controller/AuthTestController.java +++ b/src/main/java/econo/buddybridge/auth/controller/OAuthTestController.java @@ -28,7 +28,7 @@ @RequiredArgsConstructor @RequestMapping("/api/oauth") @Tag(name = "인증 API") -public class AuthTestController { +public class OAuthTestController { private final MemberService memberService; private final AuthTokenService authTokenService; diff --git a/src/main/java/econo/buddybridge/auth/dto/PasswordHashDto.java b/src/main/java/econo/buddybridge/auth/dto/PasswordHashDto.java new file mode 100644 index 0000000..e1f4b8e --- /dev/null +++ b/src/main/java/econo/buddybridge/auth/dto/PasswordHashDto.java @@ -0,0 +1,7 @@ +package econo.buddybridge.auth.dto; + +public record PasswordHashDto( + String hashedPassword, + String salt +) { +} diff --git a/src/main/java/econo/buddybridge/auth/exception/EncoderErrorCode.java b/src/main/java/econo/buddybridge/auth/exception/EncoderErrorCode.java new file mode 100644 index 0000000..ec17b49 --- /dev/null +++ b/src/main/java/econo/buddybridge/auth/exception/EncoderErrorCode.java @@ -0,0 +1,35 @@ +package econo.buddybridge.auth.exception; + +import econo.buddybridge.common.exception.ErrorCode; +import org.springframework.http.HttpStatus; + +public enum EncoderErrorCode implements ErrorCode { + ENCRYPT_FAILED("EN001", HttpStatus.INTERNAL_SERVER_ERROR, "서버에 문제가 발생했습니다. 잠시 후 다시 시도해주세요."), + GENERATE_SALT_FAILED("EN002", HttpStatus.INTERNAL_SERVER_ERROR, "서버에 문제가 발생했습니다. 잠시 후 다시 시도해주세요."), + ; + + private final String code; + private final HttpStatus httpStatus; + private final String message; + + EncoderErrorCode(String code, HttpStatus httpStatus, String message) { + this.code = code; + this.httpStatus = httpStatus; + this.message = message; + } + + @Override + public String getCode() { + return code; + } + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/econo/buddybridge/auth/exception/EncryptFailedException.java b/src/main/java/econo/buddybridge/auth/exception/EncryptFailedException.java new file mode 100644 index 0000000..5cd1b83 --- /dev/null +++ b/src/main/java/econo/buddybridge/auth/exception/EncryptFailedException.java @@ -0,0 +1,12 @@ +package econo.buddybridge.auth.exception; + +import econo.buddybridge.common.exception.BusinessException; + +public class EncryptFailedException extends BusinessException { + + public static BusinessException EXCEPTION = new EncryptFailedException(); + + private EncryptFailedException() { + super(EncoderErrorCode.ENCRYPT_FAILED); + } +} diff --git a/src/main/java/econo/buddybridge/auth/exception/GenerateSaltFailedException.java b/src/main/java/econo/buddybridge/auth/exception/GenerateSaltFailedException.java new file mode 100644 index 0000000..30fbe74 --- /dev/null +++ b/src/main/java/econo/buddybridge/auth/exception/GenerateSaltFailedException.java @@ -0,0 +1,12 @@ +package econo.buddybridge.auth.exception; + +import econo.buddybridge.common.exception.BusinessException; + +public class GenerateSaltFailedException extends BusinessException { + + public static BusinessException EXCEPTION = new GenerateSaltFailedException(); + + private GenerateSaltFailedException() { + super(EncoderErrorCode.GENERATE_SALT_FAILED); + } +} diff --git a/src/main/java/econo/buddybridge/auth/service/AuthService.java b/src/main/java/econo/buddybridge/auth/service/AuthService.java new file mode 100644 index 0000000..55381c9 --- /dev/null +++ b/src/main/java/econo/buddybridge/auth/service/AuthService.java @@ -0,0 +1,18 @@ +package econo.buddybridge.auth.service; + +import econo.buddybridge.member.dto.MemberSignUpReqDto; +import econo.buddybridge.member.dto.MemberSignUpResDto; +import econo.buddybridge.member.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final MemberService memberService; + + public MemberSignUpResDto signUp(MemberSignUpReqDto memberSignUpReqDto) { + return memberService.createSignUpMember(memberSignUpReqDto); + } +} diff --git a/src/main/java/econo/buddybridge/auth/utils/PasswordEncoder.java b/src/main/java/econo/buddybridge/auth/utils/PasswordEncoder.java new file mode 100644 index 0000000..e62234c --- /dev/null +++ b/src/main/java/econo/buddybridge/auth/utils/PasswordEncoder.java @@ -0,0 +1,52 @@ +package econo.buddybridge.auth.utils; + +import econo.buddybridge.auth.dto.PasswordHashDto; +import econo.buddybridge.auth.exception.EncryptFailedException; +import econo.buddybridge.auth.exception.GenerateSaltFailedException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Base64; + +@Slf4j +@Component +public class PasswordEncoder { + + public PasswordHashDto encrypt(String password) { + byte[] salt = generateRandomSalt(); + String hashedPassword = hashPassword(password, salt); + String saltString = Base64.getEncoder().encodeToString(salt); + + return new PasswordHashDto(hashedPassword, saltString); + } + + private String hashPassword(String password, byte[] salt) { + try { + KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 128); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + byte[] hash = factory.generateSecret(spec).getEncoded(); + return Base64.getEncoder().encodeToString(hash); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + log.error("비밀번호 암호화에 실패했습니다.", e); // 서버 에러는 로그로 처리 + throw EncryptFailedException.EXCEPTION; + } + } + + private byte[] generateRandomSalt() { + try { + SecureRandom random = new SecureRandom(); + byte[] salt = new byte[16]; + random.nextBytes(salt); + return salt; + } catch (Exception e) { + log.error("솔트 생성에 실패했습니다.", e); // 서버 에러는 로그로 처리 + throw GenerateSaltFailedException.EXCEPTION; + } + } +} diff --git a/src/main/java/econo/buddybridge/member/dto/MemberSignUpReqDto.java b/src/main/java/econo/buddybridge/member/dto/MemberSignUpReqDto.java new file mode 100644 index 0000000..9f7227a --- /dev/null +++ b/src/main/java/econo/buddybridge/member/dto/MemberSignUpReqDto.java @@ -0,0 +1,28 @@ +package econo.buddybridge.member.dto; + +import econo.buddybridge.member.entity.Gender; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDate; + +public record MemberSignUpReqDto( + @NotBlank(message = "이름을 입력해주세요.") + String name, + + @NotNull(message = "성별을 선택해주세요.") + Gender gender, + + @NotNull(message = "생년월일을 입력해주세요.") + LocalDate birthDate, + + @NotBlank(message = "이메일을 입력해주세요.") + @Email(message = "이메일 형식이 올바르지 않습니다.") + String email, + + @NotBlank(message = "비밀번호를 입력해주세요.") + String password +) { + +} diff --git a/src/main/java/econo/buddybridge/member/dto/MemberSignUpResDto.java b/src/main/java/econo/buddybridge/member/dto/MemberSignUpResDto.java new file mode 100644 index 0000000..b41f748 --- /dev/null +++ b/src/main/java/econo/buddybridge/member/dto/MemberSignUpResDto.java @@ -0,0 +1,10 @@ +package econo.buddybridge.member.dto; + +public record MemberSignUpResDto( + String message +) { + + public MemberSignUpResDto(String message) { + this.message = message; + } +} diff --git a/src/main/java/econo/buddybridge/member/entity/Member.java b/src/main/java/econo/buddybridge/member/entity/Member.java index bce8edd..fc0f0a7 100644 --- a/src/main/java/econo/buddybridge/member/entity/Member.java +++ b/src/main/java/econo/buddybridge/member/entity/Member.java @@ -45,9 +45,13 @@ public class Member extends BaseEntity { private String kakaoToken; + private String password; // 암호화된 비밀번호 + + private String salt; // 비밀번호 암호화에 사용되는 salt + @Builder public Member(String name, String nickname, String profileImageUrl, String email, - Integer age, DisabilityType disabilityType, Gender gender, String kakaoToken) { + Integer age, DisabilityType disabilityType, Gender gender, String kakaoToken, String password, String salt) { this.name = name; this.nickname = nickname; this.profileImageUrl = profileImageUrl; @@ -56,6 +60,8 @@ public Member(String name, String nickname, String profileImageUrl, String email this.disabilityType = disabilityType; this.gender = gender; this.kakaoToken = kakaoToken; + this.password = password; + this.salt = salt; } public void updateKakaoToken(String kakaoToken) { @@ -63,7 +69,7 @@ public void updateKakaoToken(String kakaoToken) { } public void updateMemberInfo(String name, String nickname, String profileImageUrl, String email, Integer age, - DisabilityType disabilityType, Gender gender) { + DisabilityType disabilityType, Gender gender) { this.name = name; this.nickname = nickname; this.profileImageUrl = profileImageUrl; diff --git a/src/main/java/econo/buddybridge/member/exception/MemberEmailAlreadyExistsException.java b/src/main/java/econo/buddybridge/member/exception/MemberEmailAlreadyExistsException.java new file mode 100644 index 0000000..cbda1ef --- /dev/null +++ b/src/main/java/econo/buddybridge/member/exception/MemberEmailAlreadyExistsException.java @@ -0,0 +1,12 @@ +package econo.buddybridge.member.exception; + +import econo.buddybridge.common.exception.BusinessException; + +public class MemberEmailAlreadyExistsException extends BusinessException { + + public static BusinessException EXCEPTION = new MemberEmailAlreadyExistsException(); + + private MemberEmailAlreadyExistsException() { + super(MemberErrorCode.MEMBER_EMAIL_ALREADY_EXISTS); + } +} diff --git a/src/main/java/econo/buddybridge/member/exception/MemberErrorCode.java b/src/main/java/econo/buddybridge/member/exception/MemberErrorCode.java index d385438..ae3992e 100644 --- a/src/main/java/econo/buddybridge/member/exception/MemberErrorCode.java +++ b/src/main/java/econo/buddybridge/member/exception/MemberErrorCode.java @@ -5,6 +5,7 @@ public enum MemberErrorCode implements ErrorCode { MEMBER_NOT_FOUND("M001", HttpStatus.NOT_FOUND, "존재하지 않는 회원입니다."), + MEMBER_EMAIL_ALREADY_EXISTS("M002", HttpStatus.BAD_REQUEST, "이미 사용중인 이메일입니다."), ; private final String code; diff --git a/src/main/java/econo/buddybridge/member/repository/MemberRepository.java b/src/main/java/econo/buddybridge/member/repository/MemberRepository.java index 30f07ba..9c9359e 100644 --- a/src/main/java/econo/buddybridge/member/repository/MemberRepository.java +++ b/src/main/java/econo/buddybridge/member/repository/MemberRepository.java @@ -1,10 +1,13 @@ package econo.buddybridge.member.repository; import econo.buddybridge.member.entity.Member; -import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -public interface MemberRepository extends JpaRepository { +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + + boolean existsByEmail(String email); Optional findByEmail(String email); } diff --git a/src/main/java/econo/buddybridge/member/service/MemberService.java b/src/main/java/econo/buddybridge/member/service/MemberService.java index 6db69f3..8b2ce57 100644 --- a/src/main/java/econo/buddybridge/member/service/MemberService.java +++ b/src/main/java/econo/buddybridge/member/service/MemberService.java @@ -1,20 +1,30 @@ package econo.buddybridge.member.service; +import econo.buddybridge.auth.dto.PasswordHashDto; import econo.buddybridge.auth.dto.kakao.UserInfoWithKakaoToken; +import econo.buddybridge.auth.utils.PasswordEncoder; import econo.buddybridge.member.dto.MemberReqDto; import econo.buddybridge.member.dto.MemberResDto; +import econo.buddybridge.member.dto.MemberSignUpReqDto; +import econo.buddybridge.member.dto.MemberSignUpResDto; +import econo.buddybridge.member.entity.DisabilityType; import econo.buddybridge.member.entity.Member; +import econo.buddybridge.member.exception.MemberEmailAlreadyExistsException; import econo.buddybridge.member.exception.MemberNotFoundException; import econo.buddybridge.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; +import java.time.Period; + @Service @RequiredArgsConstructor public class MemberService { private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; @Transactional(readOnly = true) public MemberResDto findMemberById(Long memberId) { @@ -54,4 +64,37 @@ public void updateMemberById(Long memberId, MemberReqDto memberReqDto) { member.updateMemberInfo(memberReqDto.name(), memberReqDto.nickname(), memberReqDto.profileImageUrl(), memberReqDto.email(), memberReqDto.age(), memberReqDto.disabilityType(), member.getGender()); } + + @Transactional + public MemberSignUpResDto createSignUpMember(MemberSignUpReqDto memberSignUpReqDto) { + boolean existsByEmail = memberRepository.existsByEmail(memberSignUpReqDto.email()); + if (existsByEmail) { + throw MemberEmailAlreadyExistsException.EXCEPTION; + } + signUpMember(memberSignUpReqDto); + return new MemberSignUpResDto("회원가입에 성공하셨습니다."); + } + + private void signUpMember(MemberSignUpReqDto memberSignUpReqDto) { + int age = Period.between(memberSignUpReqDto.birthDate(), LocalDate.now()).getYears(); + + PasswordHashDto passwordHashDto = passwordEncoder.encrypt(memberSignUpReqDto.password()); + String password = passwordHashDto.hashedPassword(); + String salt = passwordHashDto.salt(); + + Member member = Member.builder() + .name(memberSignUpReqDto.name()) + .nickname("닉네임을 설정해주세요") + .profileImageUrl("https://img1.kakaocdn.net/thumb/R640x640.q70/?fname=http://t1.kakaocdn.net/account_images/default_profile.jpeg") + .email(memberSignUpReqDto.email()) + .age(age) + .disabilityType(DisabilityType.없음) + .gender(memberSignUpReqDto.gender()) + .password(password) + .salt(salt) + .build(); + + memberRepository.save(member); + } + }