From 88804bac0b721bc7c6007ad3541e847b642c6799 Mon Sep 17 00:00:00 2001 From: euntae Date: Thu, 24 Aug 2023 01:59:54 +0900 Subject: [PATCH 1/4] refactored AuthService. --- .../Config/Jwt/JwtTokenProvider.java | 45 +++- .../Controller/AuthController.java | 14 +- .../TeamingServer/Domain/entity/Member.java | 13 ++ .../Exception/BadRequestException.java | 7 + .../TeamingServer/Service/AuthService.java | 5 +- .../Service/AuthServiceImpl.java | 211 +++++++----------- .../TeamingServer/Service/EmailService.java | 69 ++---- 7 files changed, 170 insertions(+), 194 deletions(-) create mode 100644 src/main/java/com/teaming/TeamingServer/Exception/BadRequestException.java diff --git a/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProvider.java b/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProvider.java index 1ba8b01..410384e 100644 --- a/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProvider.java +++ b/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProvider.java @@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -30,9 +31,11 @@ public class JwtTokenProvider { private final Key key; private final Long expiration = System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 30; // 유효 기간 한달! + private final AuthenticationManagerBuilder authenticationManagerBuilder; private final MemberRepository memberRepository; - public JwtTokenProvider(@Value("${jwt.secret}") String secretKey, MemberRepository memberRepository) { + public JwtTokenProvider(@Value("${jwt.secret}") String secretKey, AuthenticationManagerBuilder authenticationManagerBuilder, MemberRepository memberRepository) { + this.authenticationManagerBuilder = authenticationManagerBuilder; this.memberRepository = memberRepository; byte[] secretByteKey = DatatypeConverter.parseBase64Binary(secretKey); this.key = Keys.hmacShaKeyFor(secretByteKey); @@ -65,12 +68,42 @@ public JwtToken generateToken(Authentication authentication) { .build(); } + public JwtToken generateToken(Member member) { + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(member.getAuthenticationToken()); + + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + // Access token 생성 + String accessToken = Jwts.builder() + .setSubject(authentication.getName()) + .claim("auth", authorities) + .setExpiration(new Date(expiration)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + + log.info("expiration = " + new Date(expiration)); + +// // Refresh token 생성 +// String refreshToken = Jwts.builder() +// .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 36)) +// .signWith(key, SignatureAlgorithm.HS256) +// .compact(); + + return JwtToken.builder() + .grantType("Bearer") + .accessToken(accessToken) + .memberId(member.getMemberId()) + .build(); + } + public Authentication getAuthentication(String accessToken) { // 토큰 복호화 Claims claims = parseClaims(accessToken); - if(claims.get("auth") == null) { + if (claims.get("auth") == null) { throw new RuntimeException("권한 정보가 없는 토큰입니다."); } @@ -115,7 +148,7 @@ public void checkMemberId(Authentication authentication, HttpServletRequest requ String[] parts = requestURI.split("/"); // /auth ~ 기능들인지 확인 - if(checkApiURL(parts)) { + if (checkApiURL(parts)) { return; // throw new BaseException(HttpStatus.FORBIDDEN.value(), "auth 에 속한 기능들은 authorization 이 필요하지 않습니다."); } @@ -125,7 +158,7 @@ public void checkMemberId(Authentication authentication, HttpServletRequest requ Member member = memberRepository.findById(memberId) .orElseThrow(() -> new BaseException(HttpStatus.FORBIDDEN.value(), "유효하지 않은 회원 ID")); - if(!tokenEmail.equals(member.getEmail())) { + if (!tokenEmail.equals(member.getEmail())) { throw new BaseException(HttpStatus.FORBIDDEN.value(), "유효하지 않은 AccessToken"); } } @@ -133,14 +166,14 @@ public void checkMemberId(Authentication authentication, HttpServletRequest requ // api/~ 이렇게 요청이 들어오면 parts[2] 를 검사 // auth/~ 이렇게 요청이 들어오면 parts[1] 을 검사 private boolean checkApiURL(String[] parts) { - if(parts[1].equals("api")) { + if (parts[1].equals("api")) { return parts[2].equals("auth"); } return parts[1].equals("auth"); } private Long getMemberId(String[] parts) { - if(parts[1].equals("api")) { + if (parts[1].equals("api")) { return Long.parseLong(parts[3]); } diff --git a/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java b/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java index 4bd73b6..d628f05 100644 --- a/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java +++ b/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java @@ -1,11 +1,10 @@ package com.teaming.TeamingServer.Controller; -import com.teaming.TeamingServer.Config.Jwt.JwtToken; import com.teaming.TeamingServer.Domain.Dto.*; +import com.teaming.TeamingServer.Exception.BadRequestException; import com.teaming.TeamingServer.Service.AuthService; import com.teaming.TeamingServer.common.BaseErrorResponse; -import com.teaming.TeamingServer.common.BaseResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -35,7 +34,7 @@ public ResponseEntity signup(@RequestBody MemberRequestDto memberRequestDto) { // 이메일 중복체크 @PostMapping("/auth/email-duplication") public ResponseEntity duplicateEmail(@RequestBody MemberSignUpEmailDuplicationRequestDto memberSignUpEmailDuplicationRequestDto) throws Exception { - return authService.validateDuplicateMember(memberSignUpEmailDuplicationRequestDto); + return authService.validateEmailRequest(memberSignUpEmailDuplicationRequestDto.getEmail()); } // 이메일 인증 @@ -52,8 +51,7 @@ public ResponseEntity login(@RequestBody MemberLoginRequestDto memberLoginReques try { response = authService.login(memberLoginRequestDto.getEmail(), memberLoginRequestDto.getPassword()); - } - catch (IllegalArgumentException | AuthenticationException illegalArgumentException) { + } catch (IllegalArgumentException | AuthenticationException illegalArgumentException) { return ResponseEntity.status(HttpStatus.FORBIDDEN) .body(new BaseErrorResponse(HttpStatus.FORBIDDEN.value(), "잘못된 email 혹은 password 입니다.")); } @@ -67,5 +65,9 @@ public ResponseEntity resetPassword(@RequestBody MemberResetPasswordRequestDto m return authService.resetPassword(memberResetPasswordRequestDto); } - + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(BadRequestException.class) + BaseErrorResponse handleBadRequestException(Exception ex) { + return new BaseErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); + } } \ No newline at end of file diff --git a/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java b/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java index 80a65f8..93b8bf6 100644 --- a/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java +++ b/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import java.util.ArrayList; import java.util.List; @@ -78,4 +79,16 @@ public Member updateMemberProject(MemberProject memberProject) { this.memberProjects.add(memberProject); return this; } + + public boolean isPasswordMatched(String password) { + return this.password.equals(password); + } + + public UsernamePasswordAuthenticationToken getAuthenticationToken() { + return new UsernamePasswordAuthenticationToken(email, password); + } + + public long getMemberId() { + return member_id; + } } \ No newline at end of file diff --git a/src/main/java/com/teaming/TeamingServer/Exception/BadRequestException.java b/src/main/java/com/teaming/TeamingServer/Exception/BadRequestException.java new file mode 100644 index 0000000..c71173e --- /dev/null +++ b/src/main/java/com/teaming/TeamingServer/Exception/BadRequestException.java @@ -0,0 +1,7 @@ +package com.teaming.TeamingServer.Exception; + +public class BadRequestException extends RuntimeException { + public BadRequestException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/teaming/TeamingServer/Service/AuthService.java b/src/main/java/com/teaming/TeamingServer/Service/AuthService.java index cc55bb7..e3464cf 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/AuthService.java +++ b/src/main/java/com/teaming/TeamingServer/Service/AuthService.java @@ -1,16 +1,13 @@ package com.teaming.TeamingServer.Service; -import com.teaming.TeamingServer.Config.Jwt.JwtToken; import com.teaming.TeamingServer.Domain.Dto.MemberRequestDto; import com.teaming.TeamingServer.Domain.Dto.MemberResetPasswordRequestDto; -import com.teaming.TeamingServer.Domain.Dto.MemberSignUpEmailDuplicationRequestDto; -import com.teaming.TeamingServer.Domain.Dto.MemberVerificationEmailRequestDto; import org.springframework.http.ResponseEntity; public interface AuthService { ResponseEntity join(MemberRequestDto memberRequestDto); - ResponseEntity validateDuplicateMember(MemberSignUpEmailDuplicationRequestDto memberSignUpEmailDuplicationRequestDto) throws Exception; + ResponseEntity validateEmailRequest(String email) throws Exception; ResponseEntity verificationEmail(String inputCode); diff --git a/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java b/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java index 655c307..459a71f 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java +++ b/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java @@ -1,22 +1,21 @@ package com.teaming.TeamingServer.Service; -import com.teaming.TeamingServer.Config.Jwt.JwtToken; import com.teaming.TeamingServer.Config.Jwt.JwtTokenProvider; -import com.teaming.TeamingServer.Domain.Dto.*; +import com.teaming.TeamingServer.Domain.Dto.MemberLoginResponse; +import com.teaming.TeamingServer.Domain.Dto.MemberRequestDto; +import com.teaming.TeamingServer.Domain.Dto.MemberResetPasswordRequestDto; import com.teaming.TeamingServer.Domain.entity.Member; +import com.teaming.TeamingServer.Exception.BadRequestException; import com.teaming.TeamingServer.Repository.MemberRepository; -import com.teaming.TeamingServer.common.BaseErrorResponse; import com.teaming.TeamingServer.common.BaseResponse; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.RandomStringUtils; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; +import java.util.Random; @Service @Transactional @@ -30,7 +29,6 @@ public class AuthServiceImpl implements AuthService { private String emailCode; // jwt - private final AuthenticationManagerBuilder authenticationManagerBuilder; private final JwtTokenProvider jwtTokenProvider; @@ -40,166 +38,117 @@ public class AuthServiceImpl implements AuthService { @Transactional @Override public ResponseEntity join(MemberRequestDto memberRequestDto) { - - // 회원가입 정보 모두 입력 체크 - if(!checkBlank(memberRequestDto)) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(new BaseErrorResponse(HttpStatus.BAD_REQUEST.value(), "회원가입에 필요한 모든 데이터를 입력해주세요.")); - } - - Member member = Member.builder() - .name(memberRequestDto.getName()) - .email(memberRequestDto.getEmail()) - .password(memberRequestDto.getPassword()) - .agreement(true).build(); - - // 중복 회원 검증 - if(!checkDuplicateEmail(member.getEmail())) { - throw new IllegalArgumentException("이미 회원가입된 이메일입니다."); - }; - - // 이메일 인증 - - // 회원 DB 에 저장 - memberRepository.save(member); - - return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "회원가입이 완료되었습니다.")); - } - - @Transactional - @Override - public ResponseEntity validateDuplicateMember(MemberSignUpEmailDuplicationRequestDto memberSignUpEmailDuplicationRequestDto) throws Exception { - // 이메일 중복 체크 - if(!checkDuplicateEmail(memberSignUpEmailDuplicationRequestDto.getEmail())) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(new BaseErrorResponse(HttpStatus.BAD_REQUEST.value(), "이미 회원가입된 이메일입니다.")); - } - - // 이메일 인증 번호 발급 - emailCode = mailConfirm(memberSignUpEmailDuplicationRequestDto.getEmail()); - - // 이메일 검증 및 전송 정상 통과 - return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "사용 가능한 이메일입니다.")); + validateMemberRequest(memberRequestDto); + validateDuplicateEmail(memberRequestDto.getEmail()); + memberRepository.save(mapToMember(memberRequestDto)); + return getSuccessResponse("회원가입이 완료되었습니다.", null); } @Transactional @Override public ResponseEntity verificationEmail(String inputCode) { - if(checkCode(inputCode, emailCode)) { - return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "사용자 이메일 인증 성공")); - } - - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(new BaseErrorResponse(HttpStatus.BAD_REQUEST.value(), "인증번호가 일치하지 않습니다.")); + verifyEmail(inputCode); + return getSuccessResponse("사용자 이메일 인증 성공", null); } @Transactional(readOnly = true) @Override public ResponseEntity login(String email, String password) { - - // DB 에 계정이 있는지와 그 계정과 이메일, 비밀번호가 일치한지 - Member findMember = memberRepository.findByEmail(email).stream().filter(it -> password.equals(it.getPassword())) // 암호화된 비밀번호와 비교하도록 수정 - .findFirst().orElseThrow(() -> new IllegalArgumentException("아이디 또는 비밀번호가 일치하지 않습니다.")); - - Long memberId = findMember.getMember_id(); - - // Authentication 객체 생성 - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password); - - Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); - - // 검증된 인증 정보로 JWT 토큰 생성 - JwtToken token = jwtTokenProvider.generateToken(authentication); - token.setMemberId(memberId); - - // Login Response 생성 - MemberLoginResponse memberLoginResponse = MemberLoginResponse.builder() - .name(findMember.getName()) - .jwtToken(token).build(); - - return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), "로그인 성공", memberLoginResponse)); + return getSuccessResponse("로그인 성공", getMemberLoginResponse(email, password)); } @Transactional @Override public ResponseEntity resetPassword(MemberResetPasswordRequestDto memberResetPasswordRequestDto) throws Exception { + Member member = getMember(memberResetPasswordRequestDto.getEmail()); + member.updatePassword(createRandomPassword()); + emailService.sendResetPasswordMessage(member); + return getSuccessResponse("비밀번호 재설정 메일이 발송되었습니다.", null); + } - // 1. 이메일이 회원 DB 에 있는지 체크 한다. - List findMember = memberRepository.findByEmail(memberResetPasswordRequestDto.getEmail()); - - if(findMember.isEmpty()) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(new BaseErrorResponse(HttpStatus.BAD_REQUEST.value(), "회원가입되지 않은 이메일입니다.")); - } - - try { - // 2. 회원가입된 이메일이라면, 랜덤 비밀번호를 이메일로 보낸 뒤 DB 에 반영한다. - String resetPassword = passwordResetMailConfirm(memberResetPasswordRequestDto.getEmail()); - findMember.stream().findFirst().get().setPassword(resetPassword); - } catch (Exception exception) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(new BaseErrorResponse(HttpStatus.BAD_REQUEST.value(), exception.getMessage())); - } - + @Transactional + @Override + public ResponseEntity validateEmailRequest(String email) throws Exception { + validateDuplicateEmail(email); - return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), "비밀번호가 재설정되었습니다.")); + emailCode = createKey(); + // 이메일 인증 번호 발급 + emailService.sendValidateEmailRequestMessage(email, emailCode); + // 이메일 검증 및 전송 정상 통과 + return getSuccessResponse("사용 가능한 이메일입니다.", null); } + // 인증코드 만들기 + public static String createKey() { + StringBuffer key = new StringBuffer(); + Random rnd = new Random(); + for (int i = 0; i < 6; i++) { // 인증코드 6자리 + key.append((rnd.nextInt(10))); + } + return key.toString(); + } - private boolean checkCode(String authentication, String emailCode) { - return authentication.equals(emailCode); + private static Member mapToMember(MemberRequestDto memberRequestDto) { + return Member.builder() + .name(memberRequestDto.getName()) + .email(memberRequestDto.getEmail()) + .password(memberRequestDto.getPassword()) + .agreement(true).build(); } - private String mailConfirm(String email) throws Exception { - String code = emailService.sendSimpleMessage(email); - // log.info("인증코드 : " + code); - return code; + private void validateDuplicateEmail(String email) { + if (memberRepository.findByEmail(email).size() > 0) { + throw new BadRequestException("이미 회원가입된 이메일입니다."); + } } - private String passwordResetMailConfirm(String email) throws Exception { - String resetPassword = emailService.sendResetPasswordMessage(email); - // log.info("인증코드 : " + code); - return resetPassword; + private void validateMemberRequest(MemberRequestDto memberRequestDto) { + if ((memberRequestDto.getName() != null) + && (memberRequestDto.getEmail() != null) + && (memberRequestDto.getPassword() != null)) + return; + + throw new BadRequestException("회원가입 정보를 모두 입력해주세요."); + } - private boolean checkDuplicateEmail(String email) { - List findMember = memberRepository.findByEmail(email); - return findMember.isEmpty(); + private static ResponseEntity> getSuccessResponse(String message, Object object) { + return ResponseEntity.status(HttpStatus.OK) + .body(new BaseResponse<>(HttpStatus.OK.value(), message)); } - private boolean checkBlank(MemberRequestDto memberRequestDto) { - if((memberRequestDto.getName() == null) - || (memberRequestDto.getEmail() == null) - || (memberRequestDto.getPassword() == null)) { - return false; - } + private void verifyEmail(String inputCode) { + if (!inputCode.equals(emailCode)) + throw new BadRequestException("인증번호가 일치하지 않습니다."); + } - return true; + private MemberLoginResponse getMemberLoginResponse(String email, String password) { + Member member = getMatchedMember(email, password); + return MemberLoginResponse.builder() + .name(member.getName()) + .jwtToken(jwtTokenProvider.generateToken(member)) + .build(); } - //회원 전체 조회 - public List findMembers() { - return memberRepository.findAll(); + private Member getMatchedMember(String email, String password) { + return memberRepository.findByEmail(email).stream() + .filter(member -> member.isPasswordMatched(password)) // 암호화된 비밀번호와 비교하도록 수정 + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("아이디 또는 비밀번호가 일치하지 않습니다.")); } - public Member findOne(Long id) { - return memberRepository.findById(id).get(); + + // 랜덤 비밀번호 만들기 + private static String createRandomPassword() { + return RandomStringUtils.randomAlphanumeric(10); } - /** - * 회원 수정 : 프로필 사진 업데이트 + 비밀번호 - */ - @Transactional - public void updateProfileImage(Long id, String profile_image) { - Member member = (memberRepository.findById(id)).get(); - member.updateProfileImage(profile_image); + private Member getMember(String email) { + return memberRepository.findByEmail(email).stream() + .findFirst() + .orElseThrow(() -> new BadRequestException("회원가입되지 않은 이메일입니다.")); } } diff --git a/src/main/java/com/teaming/TeamingServer/Service/EmailService.java b/src/main/java/com/teaming/TeamingServer/Service/EmailService.java index f0c22eb..6fc69bf 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/EmailService.java +++ b/src/main/java/com/teaming/TeamingServer/Service/EmailService.java @@ -1,99 +1,77 @@ package com.teaming.TeamingServer.Service; +import com.teaming.TeamingServer.Domain.entity.Member; +import com.teaming.TeamingServer.Exception.BadRequestException; import jakarta.mail.MessagingException; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.mail.MailException; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Service; + import java.io.UnsupportedEncodingException; -import java.util.Random; @PropertySource("classpath:application.properties") @Slf4j @RequiredArgsConstructor @Service public class EmailService { - private final JavaMailSender javaMailSender; - //인증번호 생성 - // private final String ePw = createKey(); - private String ePw; - private String randomPassword; - @Value("${spring.mail.username}") private String id; - public MimeMessage createMessage(String to) throws MessagingException, UnsupportedEncodingException { - log.info("보내는 대상 : "+ to); - log.info("인증 번호 : " + ePw); + public MimeMessage createMessage(String to, String verificationCode) throws MessagingException, UnsupportedEncodingException { + log.info("보내는 대상 : " + to); + log.info("인증 번호 : " + verificationCode); MimeMessage message = javaMailSender.createMimeMessage(); - ePw = createKey(); message.addRecipients(MimeMessage.RecipientType.TO, to); // to 보내는 대상 message.setSubject("Teaming 회원가입 인증 코드 메일"); //메일 제목 // 메일 내용 메일의 subtype을 html로 지정하여 html문법 사용 가능 - String msg=""; + String msg = ""; msg += "

이메일 인증

"; msg += "

"; msg += "

아래 확인 코드를 회원가입 화면에서 입력해주세요.

"; msg += "
"; - msg += ePw; + msg += verificationCode; msg += "
"; message.setText(msg, "utf-8", "html"); //내용, charset타입, subtype - message.setFrom(new InternetAddress(id,"prac_Admin")); //보내는 사람의 메일 주소, 보내는 사람 이름 + message.setFrom(new InternetAddress(id, "prac_Admin")); //보내는 사람의 메일 주소, 보내는 사람 이름 return message; } - public MimeMessage createResetPasswordMessage(String to) throws MessagingException, UnsupportedEncodingException { - log.info("보내는 대상 : "+ to); - log.info("인증 번호 : " + ePw); + public MimeMessage createResetPasswordMessage(String to, String newPassword) throws MessagingException, UnsupportedEncodingException { + log.info("보내는 대상 : " + to); + log.info("인증 번호 : " + newPassword); MimeMessage message = javaMailSender.createMimeMessage(); - randomPassword = createRandomPassword(); message.addRecipients(MimeMessage.RecipientType.TO, to); // to 보내는 대상 message.setSubject("Teaming 비밀번호 초기화 메일"); //메일 제목 // 메일 내용 메일의 subtype을 html로 지정하여 html문법 사용 가능 - String msg=""; + String msg = ""; msg += "

초기화된 비밀번호

"; msg += "

"; msg += "

아래 비밀번호를 로그인 시에 비밀번호로 사용해주세요.

"; msg += "

로그인 후에는 보안을 위해 꼭 비밀번호 변경을 해주세요.

"; msg += "
"; - msg += randomPassword; + msg += newPassword; msg += "
"; message.setText(msg, "utf-8", "html"); //내용, charset타입, subtype - message.setFrom(new InternetAddress(id,"prac_Admin")); //보내는 사람의 메일 주소, 보내는 사람 이름 + message.setFrom(new InternetAddress(id, "prac_Admin")); //보내는 사람의 메일 주소, 보내는 사람 이름 return message; } - // 인증코드 만들기 - public static String createKey() { - StringBuffer key = new StringBuffer(); - Random rnd = new Random(); - - for (int i = 0; i < 6; i++) { // 인증코드 6자리 - key.append((rnd.nextInt(10))); - } - return key.toString(); - } - - // 랜덤 비밀번호 만들기 - public static String createRandomPassword() { - return RandomStringUtils.randomAlphanumeric(10); - } /* 메일 발송 @@ -101,26 +79,23 @@ public static String createRandomPassword() { MimeMessage 객체 안에 내가 전송할 메일의 내용을 담아준다. bean으로 등록해둔 javaMailSender 객체를 사용하여 이메일 send */ - public String sendSimpleMessage(String to) throws Exception { - MimeMessage message = createMessage(to); - try{ - javaMailSender.send(message); // 메일 발송 - }catch(MailException es){ + public void sendValidateEmailRequestMessage(String to, String verificationCode) { + try { + javaMailSender.send(createMessage(to, verificationCode)); // 메일 발송 + } catch (Exception es) { es.printStackTrace(); - throw new IllegalArgumentException(); + throw new BadRequestException("이메일 발송에 실패했습니다."); } - return ePw; // 메일로 보냈던 인증 코드를 서버로 리턴 } // 비밀번호 재설정 메일 발송 - public String sendResetPasswordMessage(String to) throws Exception { - MimeMessage message = createResetPasswordMessage(to); + public void sendResetPasswordMessage(Member member) throws Exception { + MimeMessage message = createResetPasswordMessage(member.getEmail(), member.getPassword()); try { javaMailSender.send(message); // 메일 발송 } catch (MailException es) { es.printStackTrace(); throw new IllegalArgumentException(); } - return randomPassword; } } \ No newline at end of file From ed51defa369cdc06e879be02ddf3020b92212a3c Mon Sep 17 00:00:00 2001 From: euntae Date: Thu, 24 Aug 2023 09:49:49 +0900 Subject: [PATCH 2/4] refactored join in auth service. --- .../Controller/AuthController.java | 13 ++--- .../Controller/FileController.java | 13 +++-- .../Controller/MemberController.java | 9 ++-- .../Controller/ProjectController.java | 49 +++++++++---------- .../TeamingServer/Domain/entity/Member.java | 13 +++++ .../TeamingServer/Service/AuthService.java | 3 +- .../Service/AuthServiceImpl.java | 42 +++++----------- .../TeamingServer/Service/AwsS3Service.java | 6 +-- .../Service/MemberServiceImpl.java | 21 ++++---- .../TeamingServer/Service/ProjectService.java | 5 +- .../TeamingServer/common/BaseResponse.java | 12 ++--- 11 files changed, 84 insertions(+), 102 deletions(-) diff --git a/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java b/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java index d628f05..58575fd 100644 --- a/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java +++ b/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java @@ -5,6 +5,7 @@ import com.teaming.TeamingServer.Exception.BadRequestException; import com.teaming.TeamingServer.Service.AuthService; import com.teaming.TeamingServer.common.BaseErrorResponse; +import com.teaming.TeamingServer.common.BaseResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -19,16 +20,8 @@ public class AuthController { // 회원가입 @PostMapping("/auth/signup") - public ResponseEntity signup(@RequestBody MemberRequestDto memberRequestDto) { - ResponseEntity response = null; - try { - response = authService.join(memberRequestDto); - } catch (IllegalArgumentException illegalArgumentException) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(new BaseErrorResponse(HttpStatus.BAD_REQUEST.value(), illegalArgumentException.getMessage())); - } - - return response; + public BaseResponse signup(@RequestBody MemberRequestDto memberRequestDto) { + return authService.join(memberRequestDto); } // 이메일 중복체크 diff --git a/src/main/java/com/teaming/TeamingServer/Controller/FileController.java b/src/main/java/com/teaming/TeamingServer/Controller/FileController.java index fb3ea5b..b71c75e 100644 --- a/src/main/java/com/teaming/TeamingServer/Controller/FileController.java +++ b/src/main/java/com/teaming/TeamingServer/Controller/FileController.java @@ -4,7 +4,6 @@ import com.teaming.TeamingServer.Domain.entity.File; import com.teaming.TeamingServer.Exception.BaseException; import com.teaming.TeamingServer.Repository.FileRepository; -import com.teaming.TeamingServer.Repository.ProjectRepository; import com.teaming.TeamingServer.Service.CommentService; import com.teaming.TeamingServer.Service.FileService; import com.teaming.TeamingServer.Service.ProjectService; @@ -48,13 +47,13 @@ public ResponseEntity> makeComment( return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "댓글을 등록하였습니다", commentEnrollResponseDto)); + .body(new BaseResponse<>("댓글을 등록하였습니다", commentEnrollResponseDto)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -68,13 +67,13 @@ public ResponseEntity>> searchComments(@Pa List list = fileService.searchComment(memberId, fileId); return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "댓글 정보를 불러왔습니다", list)); + .body(new BaseResponse<>("댓글 정보를 불러왔습니다", list)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -87,13 +86,13 @@ public ResponseEntity deleteComment(@PathVariable("fileId") Long f commentService.deleteComment(memberId, fileId, commentId); return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), "댓글을 삭제했습니다", null)); + .body(new BaseResponse("댓글을 삭제했습니다", null)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } diff --git a/src/main/java/com/teaming/TeamingServer/Controller/MemberController.java b/src/main/java/com/teaming/TeamingServer/Controller/MemberController.java index 1cbc1f8..d69e6e6 100644 --- a/src/main/java/com/teaming/TeamingServer/Controller/MemberController.java +++ b/src/main/java/com/teaming/TeamingServer/Controller/MemberController.java @@ -17,7 +17,6 @@ import java.io.IOException; -import java.time.LocalDate; import java.util.List; @Slf4j @@ -96,13 +95,13 @@ public ResponseEntity>> scheduleByDate( List filteredSchedules = scheduleService.findSchedules(memberId,filteringScheduleRequestDto); return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "사용자의 일정", filteredSchedules)); + .body(new BaseResponse<>("사용자의 일정", filteredSchedules)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -114,12 +113,12 @@ public ResponseEntity>> searchDateList(@Pa try { return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), "날짜 리스트를 가져왔습니다", monthlyResponseDtos)); + .body(new BaseResponse("날짜 리스트를 가져왔습니다", monthlyResponseDtos)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } } \ No newline at end of file diff --git a/src/main/java/com/teaming/TeamingServer/Controller/ProjectController.java b/src/main/java/com/teaming/TeamingServer/Controller/ProjectController.java index 3c080f9..1fecd55 100644 --- a/src/main/java/com/teaming/TeamingServer/Controller/ProjectController.java +++ b/src/main/java/com/teaming/TeamingServer/Controller/ProjectController.java @@ -2,7 +2,6 @@ import com.teaming.TeamingServer.Domain.Dto.*; -import com.teaming.TeamingServer.Domain.entity.Project; import com.teaming.TeamingServer.Exception.BaseException; import com.teaming.TeamingServer.Service.*; import com.teaming.TeamingServer.common.BaseResponse; @@ -37,13 +36,13 @@ public ResponseEntity> makeSchedule( return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "일정이 추가되었습니다.:)", scheduleCreateResponseDto)); + .body(new BaseResponse<>("일정이 추가되었습니다.:)", scheduleCreateResponseDto)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -56,13 +55,13 @@ public ResponseEntity>> searchSchedules( return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "프로젝트의 스케줄", list)); + .body(new BaseResponse<>("프로젝트의 스케줄", list)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -77,13 +76,13 @@ public ResponseEntity deleteSchedule(@PathVariable("memberId") Lon return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), "스케줄 삭제 성공", null)); + .body(new BaseResponse("스케줄 삭제 성공", null)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -99,13 +98,13 @@ public ResponseEntity> uploadFile(@PathVaria FileUploadResponseDto fileUploadResponseDto = fileService.generateFile(projectId, memberId, file, false); return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "파일을 업로드하였습니다", fileUploadResponseDto)); + .body(new BaseResponse<>("파일을 업로드하였습니다", fileUploadResponseDto)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -120,13 +119,13 @@ public ResponseEntity> readSchedule( return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "프로젝트의 스케줄", scheduleRead)); + .body(new BaseResponse<>("프로젝트의 스케줄", scheduleRead)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -139,13 +138,13 @@ public ResponseEntity deleteFile(@PathVariable Long projectId, @Pa fileService.deleteFile(projectId, memberId, fileId); return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "파일을 삭제하였습니다", null)); + .body(new BaseResponse<>("파일을 삭제하였습니다", null)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -159,13 +158,13 @@ public ResponseEntity> uploadFinalFile(@Path FileUploadResponseDto fileUploadResponseDto = fileService.generateFile(projectId, memberId, file, true); return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "최종 파일을 업로드하였습니다", fileUploadResponseDto)); + .body(new BaseResponse<>("최종 파일을 업로드하였습니다", fileUploadResponseDto)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -178,13 +177,13 @@ public ResponseEntity>> searchFiles(@Path List fileInfoList = fileService.searchFile(memberId, projectId); return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "프로젝트 파일들을 불러왔습니다", fileInfoList)); + .body(new BaseResponse<>("프로젝트 파일들을 불러왔습니다", fileInfoList)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -196,14 +195,14 @@ public ResponseEntity>> searchFinalFiles( List finalInfoList = fileService.searchFinalFile(memberId, projectId); return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "프로젝트 최종 파일들을 불러왔습니다", finalInfoList)); + .body(new BaseResponse<>("프로젝트 최종 파일들을 불러왔습니다", finalInfoList)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -230,13 +229,13 @@ public ResponseEntity> searchOneFile(@PathVa return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "파일 상세 정보를 불러왔습니다", information)); + .body(new BaseResponse<>("파일 상세 정보를 불러왔습니다", information)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } @@ -266,7 +265,7 @@ public ResponseEntity createProject(@PathVariable("memberId") Long memberId, return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "프로젝트를 생성하였습니다", projectCreateResponseDto)); + .body(new BaseResponse<>("프로젝트를 생성하였습니다", projectCreateResponseDto)); } catch (BaseException e) { return ResponseEntity @@ -300,7 +299,7 @@ public ResponseEntity modifyProject(@PathVariable("projectId") Long projectId, return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "프로젝트를 수정하였습니다", projectCreateResponseDto)); + .body(new BaseResponse<>("프로젝트를 수정하였습니다", projectCreateResponseDto)); } catch (BaseException e) { @@ -317,12 +316,12 @@ public ResponseEntity> getProject(@PathVariable ProjectResponseDto projectDetail = projectService.getProject(memberId,projectId); return ResponseEntity .status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), "프로젝트 정보를 불러왔습니다", projectDetail)); + .body(new BaseResponse<>("프로젝트 정보를 불러왔습니다", projectDetail)); } catch (BaseException e) { BaseErrorResponse errorResponse = new BaseErrorResponse(e.getCode(), e.getMessage()); return ResponseEntity .status(e.getCode()) - .body(new BaseResponse<>(e.getCode(), e.getMessage(), null)); + .body(new BaseResponse<>(e.getMessage(), null)); } } } diff --git a/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java b/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java index 93b8bf6..af87e83 100644 --- a/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java +++ b/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java @@ -1,11 +1,13 @@ package com.teaming.TeamingServer.Domain.entity; +import com.teaming.TeamingServer.Domain.Dto.MemberRequestDto; import jakarta.persistence.*; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.apache.commons.lang3.RandomStringUtils; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import java.util.ArrayList; @@ -60,6 +62,12 @@ public Member(String name, String email, String password, boolean agreement) { this.agreement = agreement; } + public Member(MemberRequestDto dto) { + this.name = dto.getName(); + this.email = dto.getEmail(); + this.password = dto.getPassword(); + } + public Member updateProfileImage(String profile_image) { this.profile_image = profile_image; return this; @@ -80,6 +88,11 @@ public Member updateMemberProject(MemberProject memberProject) { return this; } + public Member setRandomPassword() { + this.password = RandomStringUtils.randomAlphanumeric(10); + return this; + } + public boolean isPasswordMatched(String password) { return this.password.equals(password); } diff --git a/src/main/java/com/teaming/TeamingServer/Service/AuthService.java b/src/main/java/com/teaming/TeamingServer/Service/AuthService.java index e3464cf..4cfbfc6 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/AuthService.java +++ b/src/main/java/com/teaming/TeamingServer/Service/AuthService.java @@ -2,10 +2,11 @@ import com.teaming.TeamingServer.Domain.Dto.MemberRequestDto; import com.teaming.TeamingServer.Domain.Dto.MemberResetPasswordRequestDto; +import com.teaming.TeamingServer.common.BaseResponse; import org.springframework.http.ResponseEntity; public interface AuthService { - ResponseEntity join(MemberRequestDto memberRequestDto); + BaseResponse join(MemberRequestDto memberRequestDto); ResponseEntity validateEmailRequest(String email) throws Exception; diff --git a/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java b/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java index 459a71f..0664c64 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java +++ b/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java @@ -9,7 +9,6 @@ import com.teaming.TeamingServer.Repository.MemberRepository; import com.teaming.TeamingServer.common.BaseResponse; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -37,33 +36,36 @@ public class AuthServiceImpl implements AuthService { */ @Transactional @Override - public ResponseEntity join(MemberRequestDto memberRequestDto) { + public BaseResponse join(MemberRequestDto memberRequestDto) { validateMemberRequest(memberRequestDto); validateDuplicateEmail(memberRequestDto.getEmail()); - memberRepository.save(mapToMember(memberRequestDto)); - return getSuccessResponse("회원가입이 완료되었습니다.", null); + memberRepository.save(new Member(memberRequestDto)); + return new BaseResponse("회원가입이 완료되었습니다."); } @Transactional @Override public ResponseEntity verificationEmail(String inputCode) { verifyEmail(inputCode); - return getSuccessResponse("사용자 이메일 인증 성공", null); + return ResponseEntity.status(HttpStatus.OK) + .body(new BaseResponse("사용자 이메일 인증 성공")); } @Transactional(readOnly = true) @Override public ResponseEntity login(String email, String password) { - return getSuccessResponse("로그인 성공", getMemberLoginResponse(email, password)); + getMemberLoginResponse(email, password); + return ResponseEntity.status(HttpStatus.OK) + .body(new BaseResponse("로그인 성공")); } @Transactional @Override public ResponseEntity resetPassword(MemberResetPasswordRequestDto memberResetPasswordRequestDto) throws Exception { - Member member = getMember(memberResetPasswordRequestDto.getEmail()); - member.updatePassword(createRandomPassword()); + Member member = getMember(memberResetPasswordRequestDto.getEmail()).setRandomPassword(); emailService.sendResetPasswordMessage(member); - return getSuccessResponse("비밀번호 재설정 메일이 발송되었습니다.", null); + return ResponseEntity.status(HttpStatus.OK) + .body(new BaseResponse("비밀번호 재설정 메일이 발송되었습니다.")); } @Transactional @@ -76,7 +78,8 @@ public ResponseEntity validateEmailRequest(String email) throws Exception { emailService.sendValidateEmailRequestMessage(email, emailCode); // 이메일 검증 및 전송 정상 통과 - return getSuccessResponse("사용 가능한 이메일입니다.", null); + return ResponseEntity.status(HttpStatus.OK) + .body(new BaseResponse("사용 가능한 이메일입니다.")); } // 인증코드 만들기 @@ -90,14 +93,6 @@ public static String createKey() { return key.toString(); } - private static Member mapToMember(MemberRequestDto memberRequestDto) { - return Member.builder() - .name(memberRequestDto.getName()) - .email(memberRequestDto.getEmail()) - .password(memberRequestDto.getPassword()) - .agreement(true).build(); - } - private void validateDuplicateEmail(String email) { if (memberRepository.findByEmail(email).size() > 0) { throw new BadRequestException("이미 회원가입된 이메일입니다."); @@ -115,11 +110,6 @@ private void validateMemberRequest(MemberRequestDto memberRequestDto) { } - private static ResponseEntity> getSuccessResponse(String message, Object object) { - return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse<>(HttpStatus.OK.value(), message)); - } - private void verifyEmail(String inputCode) { if (!inputCode.equals(emailCode)) throw new BadRequestException("인증번호가 일치하지 않습니다."); @@ -140,12 +130,6 @@ private Member getMatchedMember(String email, String password) { .orElseThrow(() -> new IllegalArgumentException("아이디 또는 비밀번호가 일치하지 않습니다.")); } - - // 랜덤 비밀번호 만들기 - private static String createRandomPassword() { - return RandomStringUtils.randomAlphanumeric(10); - } - private Member getMember(String email) { return memberRepository.findByEmail(email).stream() .findFirst() diff --git a/src/main/java/com/teaming/TeamingServer/Service/AwsS3Service.java b/src/main/java/com/teaming/TeamingServer/Service/AwsS3Service.java index ceeacc3..d55cc70 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/AwsS3Service.java +++ b/src/main/java/com/teaming/TeamingServer/Service/AwsS3Service.java @@ -3,10 +3,8 @@ import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.ObjectMetadata; import com.teaming.TeamingServer.Domain.entity.Member; -import com.teaming.TeamingServer.Domain.entity.Project; import com.teaming.TeamingServer.Exception.BaseException; import com.teaming.TeamingServer.Repository.MemberRepository; -import com.teaming.TeamingServer.Repository.ProjectRepository; import com.teaming.TeamingServer.common.BaseErrorResponse; import com.teaming.TeamingServer.common.BaseResponse; import lombok.RequiredArgsConstructor; @@ -62,8 +60,8 @@ public ResponseEntity profileImageUpload(MultipartFile multipartFile, String key member.updateProfileImage(fileUrl); return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value() - , "프로필 변경이 완료되었습니다.", fileUrl)); + .body(new BaseResponse( + "프로필 변경이 완료되었습니다.", fileUrl)); } catch (Exception e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) diff --git a/src/main/java/com/teaming/TeamingServer/Service/MemberServiceImpl.java b/src/main/java/com/teaming/TeamingServer/Service/MemberServiceImpl.java index 7494672..32c89a2 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/MemberServiceImpl.java +++ b/src/main/java/com/teaming/TeamingServer/Service/MemberServiceImpl.java @@ -21,7 +21,6 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; -import java.time.LocalDate; import java.util.*; @Service @@ -81,7 +80,7 @@ public ResponseEntity changePassword(Long memberId, MemberChangePasswordRequestD return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), "비밀번호 변경이 완료되었습니다.", newToken)); + .body(new BaseResponse("비밀번호 변경이 완료되었습니다.", newToken)); } @Override @@ -99,7 +98,7 @@ public ResponseEntity checkCurrentPassword(Long memberId, CheckCurrentPasswordRe } return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), "비밀번호가 일치합니다.")); + .body(new BaseResponse("비밀번호가 일치합니다.")); } @Override @@ -115,7 +114,7 @@ public ResponseEntity MemberMyPage(Long memberId) { .profileImage(member.getProfile_image()).build(); return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), memberMyPageResponseDto)); + .body(new BaseResponse(memberMyPageResponseDto)); } @Override @@ -136,7 +135,7 @@ public ResponseEntity changeNickName(Long memberId, MemberNicknameChangeRequestD member.updateNickName(memberNicknameChangeRequestDto.getChange_nickname()); return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), "닉네임 변경이 완료되었습니다.")); + .body(new BaseResponse("닉네임 변경이 완료되었습니다.")); } @Override @@ -156,7 +155,7 @@ public ResponseEntity mainPage(Long memberId) { .progressProject(null) .portfolio(null).build(); return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), mainPageResponseDto)); + .body(new BaseResponse(mainPageResponseDto)); } // (2) 찾은 프로젝트들이 있다면, 프로젝트 최근 시작 기준으로 정렬 - 최근 프로젝트 @@ -178,7 +177,7 @@ public ResponseEntity mainPage(Long memberId) { .portfolio(portfolios).build(); return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), mainPageResponseDto)); + .body(new BaseResponse(mainPageResponseDto)); } @Override @@ -196,7 +195,7 @@ public ResponseEntity portfolioPage(Long memberId) { .member_name(member.getName()) .portfolio(null).build(); return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), portfolioPageResponseDto)); + .body(new BaseResponse(portfolioPageResponseDto)); } // (2) 있다면, 끝난 순으로 project 정렬 @@ -209,7 +208,7 @@ public ResponseEntity portfolioPage(Long memberId) { .portfolio(portfolios).build(); return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), portfolioPageResponseDto)); + .body(new BaseResponse(portfolioPageResponseDto)); } @@ -229,7 +228,7 @@ public ResponseEntity progressProjectsPage(Long memberId) { .member_name(member.getName()) .progressProjects(null).build(); return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), progressProjectsPageResponseDto)); + .body(new BaseResponse(progressProjectsPageResponseDto)); } // (2) 있다면, 끝난 순으로 project 정렬 @@ -242,7 +241,7 @@ public ResponseEntity progressProjectsPage(Long memberId) { .progressProjects(progressProjects).build(); return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), progressProjectsPageResponseDto)); + .body(new BaseResponse(progressProjectsPageResponseDto)); } diff --git a/src/main/java/com/teaming/TeamingServer/Service/ProjectService.java b/src/main/java/com/teaming/TeamingServer/Service/ProjectService.java index 7f68f94..b45263b 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/ProjectService.java +++ b/src/main/java/com/teaming/TeamingServer/Service/ProjectService.java @@ -16,7 +16,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; import java.time.LocalDate; import java.util.ArrayList; @@ -186,7 +185,7 @@ public ResponseEntity projectChangeStatus(ProjectStatusRequestDto projectStatusR .endDate(result.getEnd_date()).build(); return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), "프로젝트가 종료되었습니다.", projectStatusResponse)); + .body(new BaseResponse("프로젝트가 종료되었습니다.", projectStatusResponse)); } @@ -253,7 +252,7 @@ public ResponseEntity inviteMember(ProjectInviteRequestDto projectInviteRequestD .members(inviteMembers).build(); return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse(HttpStatus.OK.value(), "초대가 완료되었습니다.", projectInviteResponseDto)); + .body(new BaseResponse("초대가 완료되었습니다.", projectInviteResponseDto)); } diff --git a/src/main/java/com/teaming/TeamingServer/common/BaseResponse.java b/src/main/java/com/teaming/TeamingServer/common/BaseResponse.java index 3b5bd91..9ff6218 100644 --- a/src/main/java/com/teaming/TeamingServer/common/BaseResponse.java +++ b/src/main/java/com/teaming/TeamingServer/common/BaseResponse.java @@ -1,30 +1,28 @@ package com.teaming.TeamingServer.common; import lombok.Getter; +import org.springframework.http.HttpStatus; @Getter public class BaseResponse { - private final int status; + private static final int status = HttpStatus.OK.value(); private String message; // 응답에 메시지가 포함 안될 수도 있으므로 final 뺌 private T data; // 응답에 데이터가 포함 안될 수도 있으므로 final 뺌 // status, message, data 모두 넘겨주는 Response - public BaseResponse(int status, String message, T data) { - this.status = status; + public BaseResponse(String message, T data) { this.message = message; this.data = data; } // status, message 만 넘겨주는 Response - public BaseResponse(int status, String message) { - this.status = status; + public BaseResponse(String message) { this.message = message; } // status, data 만 넘겨주는 Response - public BaseResponse(int status, T data) { - this.status = status; + public BaseResponse(T data) { this.data = data; } } From c0a8efeecc509b02a923781f5d4370dbe6670330 Mon Sep 17 00:00:00 2001 From: euntae Date: Thu, 24 Aug 2023 10:36:02 +0900 Subject: [PATCH 3/4] refactored Auth controller and service. --- .../Controller/AuthController.java | 32 +++---- .../Domain/Dto/MemberLoginResponse.java | 1 - .../TeamingServer/Domain/entity/Member.java | 7 ++ .../TeamingServer/Service/AuthService.java | 13 ++- .../Service/AuthServiceImpl.java | 85 ++++++------------- .../TeamingServer/common/KeyGenerator.java | 15 ++++ 6 files changed, 65 insertions(+), 88 deletions(-) create mode 100644 src/main/java/com/teaming/TeamingServer/common/KeyGenerator.java diff --git a/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java b/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java index 58575fd..6463fe8 100644 --- a/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java +++ b/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java @@ -21,41 +21,35 @@ public class AuthController { // 회원가입 @PostMapping("/auth/signup") public BaseResponse signup(@RequestBody MemberRequestDto memberRequestDto) { - return authService.join(memberRequestDto); + authService.join(memberRequestDto); + return new BaseResponse("회원가입이 완료되었습니다."); } // 이메일 중복체크 @PostMapping("/auth/email-duplication") - public ResponseEntity duplicateEmail(@RequestBody MemberSignUpEmailDuplicationRequestDto memberSignUpEmailDuplicationRequestDto) throws Exception { - return authService.validateEmailRequest(memberSignUpEmailDuplicationRequestDto.getEmail()); + public BaseResponse duplicateEmail(@RequestBody MemberSignUpEmailDuplicationRequestDto memberSignUpEmailDuplicationRequestDto) throws Exception { + authService.validateEmailRequest(memberSignUpEmailDuplicationRequestDto.getEmail()); + return new BaseResponse("사용 가능한 이메일입니다."); } // 이메일 인증 @PostMapping("/auth/email-verification") - public ResponseEntity verificationEmail(@RequestBody MemberVerificationEmailRequestDto memberVerificationEmailRequestDto) { - return authService.verificationEmail(memberVerificationEmailRequestDto.getAuthentication()); + public BaseResponse verifyEmailCode(@RequestBody MemberVerificationEmailRequestDto memberVerificationEmailRequestDto) { + authService.verifyEmailCode(memberVerificationEmailRequestDto.getAuthentication()); + return new BaseResponse("사용자 이메일 인증 성공"); } // 로그인 @PostMapping("/auth/login") - public ResponseEntity login(@RequestBody MemberLoginRequestDto memberLoginRequestDto) { - - ResponseEntity response; - - try { - response = authService.login(memberLoginRequestDto.getEmail(), memberLoginRequestDto.getPassword()); - } catch (IllegalArgumentException | AuthenticationException illegalArgumentException) { - return ResponseEntity.status(HttpStatus.FORBIDDEN) - .body(new BaseErrorResponse(HttpStatus.FORBIDDEN.value(), "잘못된 email 혹은 password 입니다.")); - } - - return response; + public BaseResponse login(@RequestBody MemberLoginRequestDto memberLoginRequestDto) { + return new BaseResponse("로그인 성공", authService.login(memberLoginRequestDto.getEmail(), memberLoginRequestDto.getPassword())); } // 비밀번호 재설정 @PatchMapping("/auth/reset-password") - public ResponseEntity resetPassword(@RequestBody MemberResetPasswordRequestDto memberResetPasswordRequestDto) throws Exception { - return authService.resetPassword(memberResetPasswordRequestDto); + public BaseResponse resetPassword(@RequestBody MemberResetPasswordRequestDto memberResetPasswordRequestDto) throws Exception { + authService.resetPassword(memberResetPasswordRequestDto); + return new BaseResponse("비밀번호 재설정 메일이 발송되었습니다."); } @ResponseStatus(HttpStatus.BAD_REQUEST) diff --git a/src/main/java/com/teaming/TeamingServer/Domain/Dto/MemberLoginResponse.java b/src/main/java/com/teaming/TeamingServer/Domain/Dto/MemberLoginResponse.java index bfc2090..84828a0 100644 --- a/src/main/java/com/teaming/TeamingServer/Domain/Dto/MemberLoginResponse.java +++ b/src/main/java/com/teaming/TeamingServer/Domain/Dto/MemberLoginResponse.java @@ -9,7 +9,6 @@ @Data @AllArgsConstructor @NoArgsConstructor -@Builder public class MemberLoginResponse { private String name; private JwtToken jwtToken; diff --git a/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java b/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java index af87e83..ff1c11e 100644 --- a/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java +++ b/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java @@ -2,6 +2,7 @@ import com.teaming.TeamingServer.Domain.Dto.MemberRequestDto; +import com.teaming.TeamingServer.Exception.BadRequestException; import jakarta.persistence.*; import lombok.Builder; import lombok.Getter; @@ -104,4 +105,10 @@ public UsernamePasswordAuthenticationToken getAuthenticationToken() { public long getMemberId() { return member_id; } + + public void validatePassword(String password) { + if (!isPasswordMatched(password)) { + throw new BadRequestException("비밀번호가 일치하지 않습니다."); + } + } } \ No newline at end of file diff --git a/src/main/java/com/teaming/TeamingServer/Service/AuthService.java b/src/main/java/com/teaming/TeamingServer/Service/AuthService.java index 4cfbfc6..308a779 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/AuthService.java +++ b/src/main/java/com/teaming/TeamingServer/Service/AuthService.java @@ -1,18 +1,17 @@ package com.teaming.TeamingServer.Service; +import com.teaming.TeamingServer.Domain.Dto.MemberLoginResponse; import com.teaming.TeamingServer.Domain.Dto.MemberRequestDto; import com.teaming.TeamingServer.Domain.Dto.MemberResetPasswordRequestDto; -import com.teaming.TeamingServer.common.BaseResponse; -import org.springframework.http.ResponseEntity; public interface AuthService { - BaseResponse join(MemberRequestDto memberRequestDto); + void join(MemberRequestDto memberRequestDto); - ResponseEntity validateEmailRequest(String email) throws Exception; + void validateEmailRequest(String email) throws Exception; - ResponseEntity verificationEmail(String inputCode); + void verifyEmailCode(String inputCode); - ResponseEntity login(String email, String password); + MemberLoginResponse login(String email, String password); - ResponseEntity resetPassword(MemberResetPasswordRequestDto memberResetPasswordRequestDto) throws Exception; + void resetPassword(MemberResetPasswordRequestDto memberResetPasswordRequestDto) throws Exception; } diff --git a/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java b/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java index 0664c64..8436c83 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java +++ b/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java @@ -7,15 +7,11 @@ import com.teaming.TeamingServer.Domain.entity.Member; import com.teaming.TeamingServer.Exception.BadRequestException; import com.teaming.TeamingServer.Repository.MemberRepository; -import com.teaming.TeamingServer.common.BaseResponse; +import com.teaming.TeamingServer.common.KeyGenerator; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Random; - @Service @Transactional @RequiredArgsConstructor // 밑에 MemberRepository 의 생성자를 쓰지 않기 위해 @@ -36,61 +32,52 @@ public class AuthServiceImpl implements AuthService { */ @Transactional @Override - public BaseResponse join(MemberRequestDto memberRequestDto) { + public void join(MemberRequestDto memberRequestDto) { validateMemberRequest(memberRequestDto); validateDuplicateEmail(memberRequestDto.getEmail()); memberRepository.save(new Member(memberRequestDto)); - return new BaseResponse("회원가입이 완료되었습니다."); } @Transactional @Override - public ResponseEntity verificationEmail(String inputCode) { - verifyEmail(inputCode); - return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse("사용자 이메일 인증 성공")); + public void verifyEmailCode(String inputCode) { + if (isEmailVerified(inputCode)) return; + throw new BadRequestException("인증번호가 일치하지 않습니다."); } @Transactional(readOnly = true) @Override - public ResponseEntity login(String email, String password) { - getMemberLoginResponse(email, password); - return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse("로그인 성공")); + public MemberLoginResponse login(String email, String password) { + Member member = getMember(email); + member.validatePassword(password); + return new MemberLoginResponse(member.getName(), jwtTokenProvider.generateToken(member)); } @Transactional @Override - public ResponseEntity resetPassword(MemberResetPasswordRequestDto memberResetPasswordRequestDto) throws Exception { + public void resetPassword(MemberResetPasswordRequestDto memberResetPasswordRequestDto) throws Exception { Member member = getMember(memberResetPasswordRequestDto.getEmail()).setRandomPassword(); emailService.sendResetPasswordMessage(member); - return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse("비밀번호 재설정 메일이 발송되었습니다.")); } @Transactional @Override - public ResponseEntity validateEmailRequest(String email) throws Exception { + public void validateEmailRequest(String email) { validateDuplicateEmail(email); + createVerificationCode(); + emailService.sendValidateEmailRequestMessage(email, getVerificationCode()); + } - emailCode = createKey(); - // 이메일 인증 번호 발급 - emailService.sendValidateEmailRequestMessage(email, emailCode); - - // 이메일 검증 및 전송 정상 통과 - return ResponseEntity.status(HttpStatus.OK) - .body(new BaseResponse("사용 가능한 이메일입니다.")); + private boolean isEmailVerified(String inputCode) { + return emailCode.equals(inputCode); } - // 인증코드 만들기 - public static String createKey() { - StringBuffer key = new StringBuffer(); - Random rnd = new Random(); + private void createVerificationCode() { + emailCode = KeyGenerator.createKey(); + } - for (int i = 0; i < 6; i++) { // 인증코드 6자리 - key.append((rnd.nextInt(10))); - } - return key.toString(); + private String getVerificationCode() { + return emailCode; } private void validateDuplicateEmail(String email) { @@ -100,36 +87,12 @@ private void validateDuplicateEmail(String email) { } private void validateMemberRequest(MemberRequestDto memberRequestDto) { - if ((memberRequestDto.getName() != null) - && (memberRequestDto.getEmail() != null) - && (memberRequestDto.getPassword() != null)) - return; - - throw new BadRequestException("회원가입 정보를 모두 입력해주세요."); - + if (memberRequestDto.getName() == null) throw new BadRequestException("이름을 입력해주세요."); + if (memberRequestDto.getEmail() == null) throw new BadRequestException("이메일을 입력해주세요."); + if (memberRequestDto.getPassword() == null) throw new BadRequestException("비밀번호를 입력해주세요."); } - private void verifyEmail(String inputCode) { - if (!inputCode.equals(emailCode)) - throw new BadRequestException("인증번호가 일치하지 않습니다."); - } - - private MemberLoginResponse getMemberLoginResponse(String email, String password) { - Member member = getMatchedMember(email, password); - return MemberLoginResponse.builder() - .name(member.getName()) - .jwtToken(jwtTokenProvider.generateToken(member)) - .build(); - } - - private Member getMatchedMember(String email, String password) { - return memberRepository.findByEmail(email).stream() - .filter(member -> member.isPasswordMatched(password)) // 암호화된 비밀번호와 비교하도록 수정 - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("아이디 또는 비밀번호가 일치하지 않습니다.")); - } - private Member getMember(String email) { return memberRepository.findByEmail(email).stream() .findFirst() diff --git a/src/main/java/com/teaming/TeamingServer/common/KeyGenerator.java b/src/main/java/com/teaming/TeamingServer/common/KeyGenerator.java new file mode 100644 index 0000000..6007429 --- /dev/null +++ b/src/main/java/com/teaming/TeamingServer/common/KeyGenerator.java @@ -0,0 +1,15 @@ +package com.teaming.TeamingServer.common; + +import java.util.Random; + +public class KeyGenerator { + public static String createKey() { + StringBuffer key = new StringBuffer(); + Random rnd = new Random(); + + for (int i = 0; i < 6; i++) { // 인증코드 6자리 + key.append((rnd.nextInt(10))); + } + return key.toString(); + } +} From e4b36ca36bd26b23521532b9479be5cc7e89077c Mon Sep 17 00:00:00 2001 From: euntae Date: Thu, 24 Aug 2023 11:29:56 +0900 Subject: [PATCH 4/4] wrote tests for signup feature. --- .../Config/Jwt/JwtAuthenticationFilter.java | 2 +- .../Config/Jwt/JwtTokenProvider.java | 187 +---------------- .../Config/Jwt/JwtTokenProviderImpl.java | 196 ++++++++++++++++++ .../TeamingServer/Config/SecurityConfig.java | 7 +- .../Controller/AuthController.java | 2 +- .../TeamingServer/Domain/entity/Member.java | 8 +- .../TeamingServer/Service/AuthService.java | 2 +- .../Service/AuthServiceImpl.java | 8 +- .../TeamingServer/Service/EmailService.java | 103 +-------- .../Service/EmailServiceImpl.java | 102 +++++++++ .../Service/MemberServiceImpl.java | 4 +- .../TeamingServer/common/BaseResponse.java | 6 +- .../TeamingServer/AuthControllerTests.java | 129 ++++++++++++ .../TeamingServer/FakeMemberRepository.java | 177 ++++++++++++++++ .../TeamingServer/SpyEmailService.java | 15 ++ .../TeamingServer/SpyJwtTokenProvider.java | 34 +++ 16 files changed, 684 insertions(+), 298 deletions(-) create mode 100644 src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProviderImpl.java create mode 100644 src/main/java/com/teaming/TeamingServer/Service/EmailServiceImpl.java create mode 100644 src/test/java/com/teaming/TeamingServer/AuthControllerTests.java create mode 100644 src/test/java/com/teaming/TeamingServer/FakeMemberRepository.java create mode 100644 src/test/java/com/teaming/TeamingServer/SpyEmailService.java create mode 100644 src/test/java/com/teaming/TeamingServer/SpyJwtTokenProvider.java diff --git a/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtAuthenticationFilter.java b/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtAuthenticationFilter.java index 8c0c1bf..a86d4d5 100644 --- a/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtAuthenticationFilter.java @@ -17,7 +17,7 @@ @AllArgsConstructor public class JwtAuthenticationFilter extends GenericFilterBean { - private final JwtTokenProvider jwtTokenProvider; + private final JwtTokenProviderImpl jwtTokenProvider; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException, BaseException { diff --git a/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProvider.java b/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProvider.java index 410384e..1c55de3 100644 --- a/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProvider.java +++ b/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProvider.java @@ -1,191 +1,18 @@ package com.teaming.TeamingServer.Config.Jwt; import com.teaming.TeamingServer.Domain.entity.Member; -import com.teaming.TeamingServer.Exception.BaseException; -import com.teaming.TeamingServer.Repository.MemberRepository; -import io.jsonwebtoken.*; -import io.jsonwebtoken.security.Keys; import jakarta.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Component; -import javax.xml.bind.DatatypeConverter; -import java.security.Key; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.stream.Collectors; +public interface JwtTokenProvider { + JwtToken generateToken(Authentication authentication); -@Slf4j -@Component -public class JwtTokenProvider { + JwtToken generateToken(Member member); - private final Key key; - private final Long expiration = System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 30; // 유효 기간 한달! - private final AuthenticationManagerBuilder authenticationManagerBuilder; - private final MemberRepository memberRepository; - - public JwtTokenProvider(@Value("${jwt.secret}") String secretKey, AuthenticationManagerBuilder authenticationManagerBuilder, MemberRepository memberRepository) { - this.authenticationManagerBuilder = authenticationManagerBuilder; - this.memberRepository = memberRepository; - byte[] secretByteKey = DatatypeConverter.parseBase64Binary(secretKey); - this.key = Keys.hmacShaKeyFor(secretByteKey); - } - - public JwtToken generateToken(Authentication authentication) { - String authorities = authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.joining(",")); - - // Access token 생성 - String accessToken = Jwts.builder() - .setSubject(authentication.getName()) - .claim("auth", authorities) - .setExpiration(new Date(expiration)) - .signWith(key, SignatureAlgorithm.HS256) - .compact(); - - log.info("expiration = " + new Date(expiration)); - -// // Refresh token 생성 -// String refreshToken = Jwts.builder() -// .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 36)) -// .signWith(key, SignatureAlgorithm.HS256) -// .compact(); - - return JwtToken.builder() - .grantType("Bearer") - .accessToken(accessToken) - .build(); - } - - public JwtToken generateToken(Member member) { - Authentication authentication = authenticationManagerBuilder.getObject().authenticate(member.getAuthenticationToken()); - - String authorities = authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.joining(",")); - - // Access token 생성 - String accessToken = Jwts.builder() - .setSubject(authentication.getName()) - .claim("auth", authorities) - .setExpiration(new Date(expiration)) - .signWith(key, SignatureAlgorithm.HS256) - .compact(); - - log.info("expiration = " + new Date(expiration)); - -// // Refresh token 생성 -// String refreshToken = Jwts.builder() -// .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 36)) -// .signWith(key, SignatureAlgorithm.HS256) -// .compact(); - - return JwtToken.builder() - .grantType("Bearer") - .accessToken(accessToken) - .memberId(member.getMemberId()) - .build(); - } - - - public Authentication getAuthentication(String accessToken) { - // 토큰 복호화 - Claims claims = parseClaims(accessToken); - - if (claims.get("auth") == null) { - throw new RuntimeException("권한 정보가 없는 토큰입니다."); - } - -// // Member 의 권한 별로 접근 가능 페이지가 다를 때 ex) 관리자, 이용자 등 -// Collection authorities = -// Arrays.stream(claims.get("auth").toString().split(",")) -// .map(SimpleGrantedAuthority::new) -// .collect(Collectors.toList()); - - Collection authorities = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")); - - UserDetails principal = new User(claims.getSubject(), "", authorities); - - return new UsernamePasswordAuthenticationToken(principal, "", authorities); - } - - public boolean validateToken(String token) { - try { - Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); - return true; - } catch (io.jsonwebtoken.security.SignatureException | MalformedJwtException e) { - log.info("Invalid JWT Token", e); - } catch (ExpiredJwtException e) { - log.info("Expired JWT Token", e); - } catch (UnsupportedJwtException e) { - log.info("Unsupported JWT Token", e); - } catch (IllegalArgumentException e) { - log.info("JWT claims string is empty.", e); - } - - return false; - } + Authentication getAuthentication(String accessToken); + boolean validateToken(String token); // MemberId 검증 - public void checkMemberId(Authentication authentication, HttpServletRequest request) { - String tokenEmail = authentication.getName(); - - // Extract MemberID from the request - // '/projects/{memberId}/{projectId}/files-upload' - String requestURI = ((HttpServletRequest) request).getRequestURI(); - String[] parts = requestURI.split("/"); - - // /auth ~ 기능들인지 확인 - if (checkApiURL(parts)) { - return; -// throw new BaseException(HttpStatus.FORBIDDEN.value(), "auth 에 속한 기능들은 authorization 이 필요하지 않습니다."); - } - - Long memberId = getMemberId(parts); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new BaseException(HttpStatus.FORBIDDEN.value(), "유효하지 않은 회원 ID")); - - if (!tokenEmail.equals(member.getEmail())) { - throw new BaseException(HttpStatus.FORBIDDEN.value(), "유효하지 않은 AccessToken"); - } - } - - // api/~ 이렇게 요청이 들어오면 parts[2] 를 검사 - // auth/~ 이렇게 요청이 들어오면 parts[1] 을 검사 - private boolean checkApiURL(String[] parts) { - if (parts[1].equals("api")) { - return parts[2].equals("auth"); - } - return parts[1].equals("auth"); - } - - private Long getMemberId(String[] parts) { - if (parts[1].equals("api")) { - return Long.parseLong(parts[3]); - } - - return Long.parseLong(parts[2]); - } - - private Claims parseClaims(String accessToken) { - try { - return Jwts.parserBuilder().setSigningKey(key) - .build().parseClaimsJws(accessToken).getBody(); - } catch (ExpiredJwtException e) { - return e.getClaims(); - } - } -} \ No newline at end of file + void checkMemberId(Authentication authentication, HttpServletRequest request); +} diff --git a/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProviderImpl.java b/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProviderImpl.java new file mode 100644 index 0000000..ea57e8d --- /dev/null +++ b/src/main/java/com/teaming/TeamingServer/Config/Jwt/JwtTokenProviderImpl.java @@ -0,0 +1,196 @@ +package com.teaming.TeamingServer.Config.Jwt; + +import com.teaming.TeamingServer.Domain.entity.Member; +import com.teaming.TeamingServer.Exception.BaseException; +import com.teaming.TeamingServer.Repository.MemberRepository; +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import javax.xml.bind.DatatypeConverter; +import java.security.Key; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class JwtTokenProviderImpl implements JwtTokenProvider { + + private final Key key; + private final Long expiration = System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 30; // 유효 기간 한달! + private final AuthenticationManagerBuilder authenticationManagerBuilder; + private final MemberRepository memberRepository; + + public JwtTokenProviderImpl(@Value("${jwt.secret}") String secretKey, AuthenticationManagerBuilder authenticationManagerBuilder, MemberRepository memberRepository) { + this.authenticationManagerBuilder = authenticationManagerBuilder; + this.memberRepository = memberRepository; + byte[] secretByteKey = DatatypeConverter.parseBase64Binary(secretKey); + this.key = Keys.hmacShaKeyFor(secretByteKey); + } + + @Override + public JwtToken generateToken(Authentication authentication) { + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + // Access token 생성 + String accessToken = Jwts.builder() + .setSubject(authentication.getName()) + .claim("auth", authorities) + .setExpiration(new Date(expiration)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + + log.info("expiration = " + new Date(expiration)); + +// // Refresh token 생성 +// String refreshToken = Jwts.builder() +// .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 36)) +// .signWith(key, SignatureAlgorithm.HS256) +// .compact(); + + return JwtToken.builder() + .grantType("Bearer") + .accessToken(accessToken) + .build(); + } + + @Override + public JwtToken generateToken(Member member) { + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(member.getAuthenticationToken()); + + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + // Access token 생성 + String accessToken = Jwts.builder() + .setSubject(authentication.getName()) + .claim("auth", authorities) + .setExpiration(new Date(expiration)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + + log.info("expiration = " + new Date(expiration)); + +// // Refresh token 생성 +// String refreshToken = Jwts.builder() +// .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 36)) +// .signWith(key, SignatureAlgorithm.HS256) +// .compact(); + + return JwtToken.builder() + .grantType("Bearer") + .accessToken(accessToken) + .memberId(member.getMemberId()) + .build(); + } + + + @Override + public Authentication getAuthentication(String accessToken) { + // 토큰 복호화 + Claims claims = parseClaims(accessToken); + + if (claims.get("auth") == null) { + throw new RuntimeException("권한 정보가 없는 토큰입니다."); + } + +// // Member 의 권한 별로 접근 가능 페이지가 다를 때 ex) 관리자, 이용자 등 +// Collection authorities = +// Arrays.stream(claims.get("auth").toString().split(",")) +// .map(SimpleGrantedAuthority::new) +// .collect(Collectors.toList()); + + Collection authorities = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")); + + UserDetails principal = new User(claims.getSubject(), "", authorities); + + return new UsernamePasswordAuthenticationToken(principal, "", authorities); + } + + @Override + public boolean validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + return true; + } catch (io.jsonwebtoken.security.SignatureException | MalformedJwtException e) { + log.info("Invalid JWT Token", e); + } catch (ExpiredJwtException e) { + log.info("Expired JWT Token", e); + } catch (UnsupportedJwtException e) { + log.info("Unsupported JWT Token", e); + } catch (IllegalArgumentException e) { + log.info("JWT claims string is empty.", e); + } + + return false; + } + + + @Override + // MemberId 검증 + public void checkMemberId(Authentication authentication, HttpServletRequest request) { + String tokenEmail = authentication.getName(); + + // Extract MemberID from the request + // '/projects/{memberId}/{projectId}/files-upload' + String requestURI = ((HttpServletRequest) request).getRequestURI(); + String[] parts = requestURI.split("/"); + + // /auth ~ 기능들인지 확인 + if (checkApiURL(parts)) { + return; +// throw new BaseException(HttpStatus.FORBIDDEN.value(), "auth 에 속한 기능들은 authorization 이 필요하지 않습니다."); + } + + Long memberId = getMemberId(parts); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new BaseException(HttpStatus.FORBIDDEN.value(), "유효하지 않은 회원 ID")); + + if (!tokenEmail.equals(member.getEmail())) { + throw new BaseException(HttpStatus.FORBIDDEN.value(), "유효하지 않은 AccessToken"); + } + } + + // api/~ 이렇게 요청이 들어오면 parts[2] 를 검사 + // auth/~ 이렇게 요청이 들어오면 parts[1] 을 검사 + private boolean checkApiURL(String[] parts) { + if (parts[1].equals("api")) { + return parts[2].equals("auth"); + } + return parts[1].equals("auth"); + } + + private Long getMemberId(String[] parts) { + if (parts[1].equals("api")) { + return Long.parseLong(parts[3]); + } + + return Long.parseLong(parts[2]); + } + + private Claims parseClaims(String accessToken) { + try { + return Jwts.parserBuilder().setSigningKey(key) + .build().parseClaimsJws(accessToken).getBody(); + } catch (ExpiredJwtException e) { + return e.getClaims(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/teaming/TeamingServer/Config/SecurityConfig.java b/src/main/java/com/teaming/TeamingServer/Config/SecurityConfig.java index 6e7a3eb..160a230 100644 --- a/src/main/java/com/teaming/TeamingServer/Config/SecurityConfig.java +++ b/src/main/java/com/teaming/TeamingServer/Config/SecurityConfig.java @@ -1,7 +1,7 @@ package com.teaming.TeamingServer.Config; import com.teaming.TeamingServer.Config.Jwt.JwtAuthenticationFilter; -import com.teaming.TeamingServer.Config.Jwt.JwtTokenProvider; +import com.teaming.TeamingServer.Config.Jwt.JwtTokenProviderImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -13,19 +13,18 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.header.writers.StaticHeadersWriter; @Configuration @EnableWebSecurity public class SecurityConfig { - private final JwtTokenProvider jwtTokenProvider; + private final JwtTokenProviderImpl jwtTokenProvider; private String[] possibleAccess = {"/api/auth/signup" , "/api/auth/email-duplication", "/api/auth/email-verification", "/api/auth/login" , "/api/auth/reset-password", "/api/error", "/api", "/error", "/auth/**"}; - public SecurityConfig(JwtTokenProvider jwtTokenProvider) { + public SecurityConfig(JwtTokenProviderImpl jwtTokenProvider) { this.jwtTokenProvider = jwtTokenProvider; } diff --git a/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java b/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java index 6463fe8..ddd28dc 100644 --- a/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java +++ b/src/main/java/com/teaming/TeamingServer/Controller/AuthController.java @@ -47,7 +47,7 @@ public BaseResponse login(@RequestBody MemberLoginRequestDto memberLoginRequestD // 비밀번호 재설정 @PatchMapping("/auth/reset-password") - public BaseResponse resetPassword(@RequestBody MemberResetPasswordRequestDto memberResetPasswordRequestDto) throws Exception { + public BaseResponse resetPassword(@RequestBody MemberResetPasswordRequestDto memberResetPasswordRequestDto) { authService.resetPassword(memberResetPasswordRequestDto); return new BaseResponse("비밀번호 재설정 메일이 발송되었습니다."); } diff --git a/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java b/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java index ff1c11e..5b10a18 100644 --- a/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java +++ b/src/main/java/com/teaming/TeamingServer/Domain/entity/Member.java @@ -4,18 +4,14 @@ import com.teaming.TeamingServer.Domain.Dto.MemberRequestDto; import com.teaming.TeamingServer.Exception.BadRequestException; import jakarta.persistence.*; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import java.util.ArrayList; import java.util.List; -@Getter -@Setter +@Data @NoArgsConstructor @Entity public class Member extends Time { diff --git a/src/main/java/com/teaming/TeamingServer/Service/AuthService.java b/src/main/java/com/teaming/TeamingServer/Service/AuthService.java index 308a779..d383cf6 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/AuthService.java +++ b/src/main/java/com/teaming/TeamingServer/Service/AuthService.java @@ -13,5 +13,5 @@ public interface AuthService { MemberLoginResponse login(String email, String password); - void resetPassword(MemberResetPasswordRequestDto memberResetPasswordRequestDto) throws Exception; + void resetPassword(MemberResetPasswordRequestDto memberResetPasswordRequestDto); } diff --git a/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java b/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java index 8436c83..8f0f0f4 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java +++ b/src/main/java/com/teaming/TeamingServer/Service/AuthServiceImpl.java @@ -55,9 +55,13 @@ public MemberLoginResponse login(String email, String password) { @Transactional @Override - public void resetPassword(MemberResetPasswordRequestDto memberResetPasswordRequestDto) throws Exception { + public void resetPassword(MemberResetPasswordRequestDto memberResetPasswordRequestDto) { Member member = getMember(memberResetPasswordRequestDto.getEmail()).setRandomPassword(); - emailService.sendResetPasswordMessage(member); + try { + emailService.sendResetPasswordMessage(member.getEmail(), member.getPassword()); + } catch (Exception e) { + throw new BadRequestException(e.getMessage()); + } } @Transactional diff --git a/src/main/java/com/teaming/TeamingServer/Service/EmailService.java b/src/main/java/com/teaming/TeamingServer/Service/EmailService.java index 6fc69bf..3104196 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/EmailService.java +++ b/src/main/java/com/teaming/TeamingServer/Service/EmailService.java @@ -1,101 +1,6 @@ package com.teaming.TeamingServer.Service; -import com.teaming.TeamingServer.Domain.entity.Member; -import com.teaming.TeamingServer.Exception.BadRequestException; -import jakarta.mail.MessagingException; -import jakarta.mail.internet.InternetAddress; -import jakarta.mail.internet.MimeMessage; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.PropertySource; -import org.springframework.mail.MailException; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.stereotype.Service; - -import java.io.UnsupportedEncodingException; - -@PropertySource("classpath:application.properties") -@Slf4j -@RequiredArgsConstructor -@Service -public class EmailService { - private final JavaMailSender javaMailSender; - - @Value("${spring.mail.username}") - private String id; - - public MimeMessage createMessage(String to, String verificationCode) throws MessagingException, UnsupportedEncodingException { - log.info("보내는 대상 : " + to); - log.info("인증 번호 : " + verificationCode); - MimeMessage message = javaMailSender.createMimeMessage(); - - message.addRecipients(MimeMessage.RecipientType.TO, to); // to 보내는 대상 - message.setSubject("Teaming 회원가입 인증 코드 메일"); //메일 제목 - - // 메일 내용 메일의 subtype을 html로 지정하여 html문법 사용 가능 - String msg = ""; - msg += "

이메일 인증

"; - msg += "

"; - msg += "

아래 확인 코드를 회원가입 화면에서 입력해주세요.

"; - msg += "
"; - msg += verificationCode; - msg += "
"; - - message.setText(msg, "utf-8", "html"); //내용, charset타입, subtype - message.setFrom(new InternetAddress(id, "prac_Admin")); //보내는 사람의 메일 주소, 보내는 사람 이름 - - return message; - } - - public MimeMessage createResetPasswordMessage(String to, String newPassword) throws MessagingException, UnsupportedEncodingException { - log.info("보내는 대상 : " + to); - log.info("인증 번호 : " + newPassword); - MimeMessage message = javaMailSender.createMimeMessage(); - - message.addRecipients(MimeMessage.RecipientType.TO, to); // to 보내는 대상 - message.setSubject("Teaming 비밀번호 초기화 메일"); //메일 제목 - - // 메일 내용 메일의 subtype을 html로 지정하여 html문법 사용 가능 - String msg = ""; - msg += "

초기화된 비밀번호

"; - msg += "

"; - msg += "

아래 비밀번호를 로그인 시에 비밀번호로 사용해주세요.

"; - msg += "

로그인 후에는 보안을 위해 꼭 비밀번호 변경을 해주세요.

"; - msg += "
"; - msg += newPassword; - msg += "
"; - - message.setText(msg, "utf-8", "html"); //내용, charset타입, subtype - message.setFrom(new InternetAddress(id, "prac_Admin")); //보내는 사람의 메일 주소, 보내는 사람 이름 - - return message; - } - - - /* - 메일 발송 - sendSimpleMessage의 매개변수로 들어온 to는 인증번호를 받을 메일주소 - MimeMessage 객체 안에 내가 전송할 메일의 내용을 담아준다. - bean으로 등록해둔 javaMailSender 객체를 사용하여 이메일 send - */ - public void sendValidateEmailRequestMessage(String to, String verificationCode) { - try { - javaMailSender.send(createMessage(to, verificationCode)); // 메일 발송 - } catch (Exception es) { - es.printStackTrace(); - throw new BadRequestException("이메일 발송에 실패했습니다."); - } - } - - // 비밀번호 재설정 메일 발송 - public void sendResetPasswordMessage(Member member) throws Exception { - MimeMessage message = createResetPasswordMessage(member.getEmail(), member.getPassword()); - try { - javaMailSender.send(message); // 메일 발송 - } catch (MailException es) { - es.printStackTrace(); - throw new IllegalArgumentException(); - } - } -} \ No newline at end of file +public interface EmailService { + void sendValidateEmailRequestMessage(String to, String verificationCode); + void sendResetPasswordMessage(String email, String newPassword) throws Exception; +} diff --git a/src/main/java/com/teaming/TeamingServer/Service/EmailServiceImpl.java b/src/main/java/com/teaming/TeamingServer/Service/EmailServiceImpl.java new file mode 100644 index 0000000..cc2e4ba --- /dev/null +++ b/src/main/java/com/teaming/TeamingServer/Service/EmailServiceImpl.java @@ -0,0 +1,102 @@ +package com.teaming.TeamingServer.Service; + +import com.teaming.TeamingServer.Exception.BadRequestException; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.mail.MailException; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +import java.io.UnsupportedEncodingException; + +@PropertySource("classpath:application.properties") +@Slf4j +@RequiredArgsConstructor +@Service +public class EmailServiceImpl implements EmailService { + private final JavaMailSender javaMailSender; + + @Value("${spring.mail.username}") + private String id; + + private MimeMessage createValidateEmailRequestMessage(String to, String verificationCode) throws MessagingException, UnsupportedEncodingException { + log.info("보내는 대상 : " + to); + log.info("인증 번호 : " + verificationCode); + MimeMessage message = javaMailSender.createMimeMessage(); + + message.addRecipients(MimeMessage.RecipientType.TO, to); // to 보내는 대상 + message.setSubject("Teaming 회원가입 인증 코드 메일"); //메일 제목 + + // 메일 내용 메일의 subtype을 html로 지정하여 html문법 사용 가능 + String msg = ""; + msg += "

이메일 인증

"; + msg += "

"; + msg += "

아래 확인 코드를 회원가입 화면에서 입력해주세요.

"; + msg += "
"; + msg += verificationCode; + msg += "
"; + + message.setText(msg, "utf-8", "html"); //내용, charset타입, subtype + message.setFrom(new InternetAddress(id, "prac_Admin")); //보내는 사람의 메일 주소, 보내는 사람 이름 + + return message; + } + + private MimeMessage createResetPasswordMessage(String to, String newPassword) throws MessagingException, UnsupportedEncodingException { + log.info("보내는 대상 : " + to); + log.info("인증 번호 : " + newPassword); + MimeMessage message = javaMailSender.createMimeMessage(); + + message.addRecipients(MimeMessage.RecipientType.TO, to); // to 보내는 대상 + message.setSubject("Teaming 비밀번호 초기화 메일"); //메일 제목 + + // 메일 내용 메일의 subtype을 html로 지정하여 html문법 사용 가능 + String msg = ""; + msg += "

초기화된 비밀번호

"; + msg += "

"; + msg += "

아래 비밀번호를 로그인 시에 비밀번호로 사용해주세요.

"; + msg += "

로그인 후에는 보안을 위해 꼭 비밀번호 변경을 해주세요.

"; + msg += "
"; + msg += newPassword; + msg += "
"; + + message.setText(msg, "utf-8", "html"); //내용, charset타입, subtype + message.setFrom(new InternetAddress(id, "prac_Admin")); //보내는 사람의 메일 주소, 보내는 사람 이름 + + return message; + } + + + /* + 메일 발송 + sendSimpleMessage의 매개변수로 들어온 to는 인증번호를 받을 메일주소 + MimeMessage 객체 안에 내가 전송할 메일의 내용을 담아준다. + bean으로 등록해둔 javaMailSender 객체를 사용하여 이메일 send + */ + @Override + public void sendValidateEmailRequestMessage(String to, String verificationCode) { + try { + javaMailSender.send(createValidateEmailRequestMessage(to, verificationCode)); // 메일 발송 + } catch (Exception es) { + es.printStackTrace(); + throw new BadRequestException("이메일 발송에 실패했습니다."); + } + } + + // 비밀번호 재설정 메일 발송 + @Override + public void sendResetPasswordMessage(String email, String newPassword) throws Exception { + MimeMessage message = createResetPasswordMessage(email, newPassword); + try { + javaMailSender.send(message); // 메일 발송 + } catch (MailException es) { + es.printStackTrace(); + throw new IllegalArgumentException(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/teaming/TeamingServer/Service/MemberServiceImpl.java b/src/main/java/com/teaming/TeamingServer/Service/MemberServiceImpl.java index 32c89a2..3df1d71 100644 --- a/src/main/java/com/teaming/TeamingServer/Service/MemberServiceImpl.java +++ b/src/main/java/com/teaming/TeamingServer/Service/MemberServiceImpl.java @@ -1,7 +1,7 @@ package com.teaming.TeamingServer.Service; import com.teaming.TeamingServer.Config.Jwt.JwtToken; -import com.teaming.TeamingServer.Config.Jwt.JwtTokenProvider; +import com.teaming.TeamingServer.Config.Jwt.JwtTokenProviderImpl; import com.teaming.TeamingServer.Domain.Dto.*; import com.teaming.TeamingServer.Domain.Dto.mainPageDto.Portfolio; import com.teaming.TeamingServer.Domain.Dto.mainPageDto.ProgressProject; @@ -31,7 +31,7 @@ public class MemberServiceImpl implements MemberService { private final MemberRepository memberRepository; private final AuthenticationManagerBuilder authenticationManagerBuilder; - private final JwtTokenProvider jwtTokenProvider; + private final JwtTokenProviderImpl jwtTokenProvider; private final ProjectRepository projectRepository; private final MemberProjectRepository memberProjectRepository; diff --git a/src/main/java/com/teaming/TeamingServer/common/BaseResponse.java b/src/main/java/com/teaming/TeamingServer/common/BaseResponse.java index 9ff6218..c05c12a 100644 --- a/src/main/java/com/teaming/TeamingServer/common/BaseResponse.java +++ b/src/main/java/com/teaming/TeamingServer/common/BaseResponse.java @@ -5,8 +5,6 @@ @Getter public class BaseResponse { - - private static final int status = HttpStatus.OK.value(); private String message; // 응답에 메시지가 포함 안될 수도 있으므로 final 뺌 private T data; // 응답에 데이터가 포함 안될 수도 있으므로 final 뺌 @@ -25,4 +23,8 @@ public BaseResponse(String message) { public BaseResponse(T data) { this.data = data; } + + public int getStatus() { + return HttpStatus.OK.value(); + } } diff --git a/src/test/java/com/teaming/TeamingServer/AuthControllerTests.java b/src/test/java/com/teaming/TeamingServer/AuthControllerTests.java new file mode 100644 index 0000000..8546fcc --- /dev/null +++ b/src/test/java/com/teaming/TeamingServer/AuthControllerTests.java @@ -0,0 +1,129 @@ +package com.teaming.TeamingServer; + +import com.teaming.TeamingServer.Controller.AuthController; +import com.teaming.TeamingServer.Domain.entity.Member; +import com.teaming.TeamingServer.Service.AuthService; +import com.teaming.TeamingServer.Service.AuthServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class AuthControllerTests { + FakeMemberRepository fakeMemberRepository; + SpyEmailService spyEmailService; + SpyJwtTokenProvider spyJwtTokenProvider; + AuthService authService; + MockMvc mockMvc; + + @BeforeEach + void setUp() { + fakeMemberRepository = new FakeMemberRepository(); + spyEmailService = new SpyEmailService(); + spyJwtTokenProvider = new SpyJwtTokenProvider(); + authService = new AuthServiceImpl(fakeMemberRepository, spyEmailService, spyJwtTokenProvider); + AuthController authController = new AuthController(authService); + mockMvc = MockMvcBuilders.standaloneSetup(authController).build(); + } + + @Test + void signup_returnsBadRequestStatus_whenNameIsMissing() throws Exception { + mockMvc.perform(post("/auth/signup") + .contentType(MediaType.APPLICATION_JSON) + .content("{\n" + + " \"email\": \"test@gmail.com\",\n" + + " \"password\": \"test1234\"\n" + + "}")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status", equalTo(400))) + .andExpect(jsonPath("$.message", equalTo("이름을 입력해주세요."))) + ; + } + + @Test + void signup_returnsBadRequestStatus_whenEmailIsMissing() throws Exception { + mockMvc.perform(post("/auth/signup") + .contentType(MediaType.APPLICATION_JSON) + .content("{\n" + + " \"name\": \"tester\",\n" + + " \"password\": \"test1234\"\n" + + "}")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status", equalTo(400))) + .andExpect(jsonPath("$.message", equalTo("이메일을 입력해주세요."))) + ; + } + + @Test + void signup_returnsBadRequestStatus_whenPasswordIsMissing() throws Exception { + mockMvc.perform(post("/auth/signup") + .contentType(MediaType.APPLICATION_JSON) + .content("{\n" + + " \"name\": \"tester\",\n" + + " \"email\": \"test@gmail.com\"\n" + + "}")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status", equalTo(400))) + .andExpect(jsonPath("$.message", equalTo("비밀번호를 입력해주세요."))) + ; + } + + @Test + void signup_returnsBadRequestStatus_whenEmailIsDuplicated() throws Exception { + fakeMemberRepository.save(Member.builder().email("test@gmail.com").build()); + mockMvc.perform(post("/auth/signup") + .contentType(MediaType.APPLICATION_JSON) + .content("{\n" + + " \"name\": \"tester\",\n" + + " \"email\": \"test@gmail.com\",\n" + + " \"password\": \"test1234\"\n" + + "}")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status", equalTo(400))) + .andExpect(jsonPath("$.message", equalTo("이미 회원가입된 이메일입니다."))) + ; + } + + @Test + void signup_returnsOkStatus() throws Exception { + mockMvc.perform(post("/auth/signup") + .contentType(MediaType.APPLICATION_JSON) + .content("{\n" + + " \"name\": \"tester\",\n" + + " \"email\": \"test@gmail.com\",\n" + + " \"password\": \"test1234\"\n" + + "}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status", equalTo(200))) + .andExpect(jsonPath("$.message", equalTo("회원가입이 완료되었습니다."))) + ; + } + + @Test + void signup_persistsMemberInfo() throws Exception { + mockMvc.perform(post("/auth/signup") + .contentType(MediaType.APPLICATION_JSON) + .content("{\n" + + " \"name\": \"tester\",\n" + + " \"email\": \"test@gmail.com\",\n" + + " \"password\": \"test1234\"\n" + + "}")); + + List result = fakeMemberRepository.findByEmail("test@gmail.com"); + Member expected = Member.builder() + .name("tester") + .email("test@gmail.com") + .password("test1234") + .build(); + assertThat(result).hasSize(1).contains(expected); + } +} diff --git a/src/test/java/com/teaming/TeamingServer/FakeMemberRepository.java b/src/test/java/com/teaming/TeamingServer/FakeMemberRepository.java new file mode 100644 index 0000000..ce0db7c --- /dev/null +++ b/src/test/java/com/teaming/TeamingServer/FakeMemberRepository.java @@ -0,0 +1,177 @@ +package com.teaming.TeamingServer; + +import com.teaming.TeamingServer.Domain.entity.Member; +import com.teaming.TeamingServer.Repository.MemberRepository; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.FluentQuery; + +import java.util.*; +import java.util.function.Function; + +public class FakeMemberRepository implements MemberRepository { + private Map fakeDBByEmail = new HashMap<>(); + @Override + public List findByEmail(String email) { + if(fakeDBByEmail.containsKey(email)) return List.of(fakeDBByEmail.get(email)); + return Collections.emptyList(); + } + + @Override + public Optional findByName(String name) { + return Optional.empty(); + } + + @Override + public void flush() { + + } + + @Override + public S saveAndFlush(S entity) { + return null; + } + + @Override + public List saveAllAndFlush(Iterable entities) { + return null; + } + + @Override + public void deleteAllInBatch(Iterable entities) { + + } + + @Override + public void deleteAllByIdInBatch(Iterable longs) { + + } + + @Override + public void deleteAllInBatch() { + + } + + @Override + public Member getOne(Long aLong) { + return null; + } + + @Override + public Member getById(Long aLong) { + return null; + } + + @Override + public Member getReferenceById(Long aLong) { + return null; + } + + @Override + public Optional findOne(Example example) { + return Optional.empty(); + } + + @Override + public List findAll(Example example) { + return null; + } + + @Override + public List findAll(Example example, Sort sort) { + return null; + } + + @Override + public Page findAll(Example example, Pageable pageable) { + return null; + } + + @Override + public long count(Example example) { + return 0; + } + + @Override + public boolean exists(Example example) { + return false; + } + + @Override + public R findBy(Example example, Function, R> queryFunction) { + return null; + } + + @Override + public S save(S entity) { + fakeDBByEmail.put(entity.getEmail(), entity); + return null; + } + + @Override + public List saveAll(Iterable entities) { + return null; + } + + @Override + public Optional findById(Long aLong) { + return Optional.empty(); + } + + @Override + public boolean existsById(Long aLong) { + return false; + } + + @Override + public List findAll() { + return null; + } + + @Override + public List findAllById(Iterable longs) { + return null; + } + + @Override + public long count() { + return 0; + } + + @Override + public void deleteById(Long aLong) { + + } + + @Override + public void delete(Member entity) { + + } + + @Override + public void deleteAllById(Iterable longs) { + + } + + @Override + public void deleteAll(Iterable entities) { + + } + + @Override + public void deleteAll() { + + } + + @Override + public List findAll(Sort sort) { + return null; + } + + @Override + public Page findAll(Pageable pageable) { + return null; + } +} diff --git a/src/test/java/com/teaming/TeamingServer/SpyEmailService.java b/src/test/java/com/teaming/TeamingServer/SpyEmailService.java new file mode 100644 index 0000000..120bad6 --- /dev/null +++ b/src/test/java/com/teaming/TeamingServer/SpyEmailService.java @@ -0,0 +1,15 @@ +package com.teaming.TeamingServer; + +import com.teaming.TeamingServer.Service.EmailService; + +public class SpyEmailService implements EmailService { + @Override + public void sendValidateEmailRequestMessage(String to, String verificationCode) { + + } + + @Override + public void sendResetPasswordMessage(String email, String newPassword) { + + } +} diff --git a/src/test/java/com/teaming/TeamingServer/SpyJwtTokenProvider.java b/src/test/java/com/teaming/TeamingServer/SpyJwtTokenProvider.java new file mode 100644 index 0000000..cc8010d --- /dev/null +++ b/src/test/java/com/teaming/TeamingServer/SpyJwtTokenProvider.java @@ -0,0 +1,34 @@ +package com.teaming.TeamingServer; + +import com.teaming.TeamingServer.Config.Jwt.JwtToken; +import com.teaming.TeamingServer.Config.Jwt.JwtTokenProvider; +import com.teaming.TeamingServer.Domain.entity.Member; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.security.core.Authentication; + +public class SpyJwtTokenProvider implements JwtTokenProvider { + @Override + public JwtToken generateToken(Authentication authentication) { + return null; + } + + @Override + public JwtToken generateToken(Member member) { + return null; + } + + @Override + public Authentication getAuthentication(String accessToken) { + return null; + } + + @Override + public boolean validateToken(String token) { + return false; + } + + @Override + public void checkMemberId(Authentication authentication, HttpServletRequest request) { + + } +}