diff --git a/build.gradle b/build.gradle index 0286ec2..6b30341 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,9 @@ dependencies { //gpt implementation 'com.theokanning.openai-gpt3-java:service:0.18.2' + //mail + implementation 'org.springframework.boot:spring-boot-starter-mail' + } tasks.named('test') { diff --git a/src/main/java/com/narae/fliwith/config/MailConfig.java b/src/main/java/com/narae/fliwith/config/MailConfig.java new file mode 100644 index 0000000..56d3b35 --- /dev/null +++ b/src/main/java/com/narae/fliwith/config/MailConfig.java @@ -0,0 +1,44 @@ +package com.narae.fliwith.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +public class MailConfig { + @Value("${spring.mail.username}") + private String id; + @Value("${spring.mail.password}") + private String password; + @Value("${spring.mail.host}") + private String host; + @Value("${spring.mail.port}") + private int port; + + @Bean + public JavaMailSender javaMailService() { + JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl(); + + javaMailSender.setHost(host); + javaMailSender.setUsername(id); + javaMailSender.setPassword(password); + javaMailSender.setPort(port); + javaMailSender.setJavaMailProperties(getMailProperties()); + javaMailSender.setDefaultEncoding("UTF-8"); + return javaMailSender; + } + + private Properties getMailProperties() { + Properties properties = new Properties(); + properties.setProperty("mail.transport.protocol", "smtp"); + properties.setProperty("mail.smtp.auth", "true"); + properties.setProperty("mail.smtp.starttls.enable", "true"); + properties.setProperty("mail.debug", "true"); + properties.setProperty("mail.smtp.ssl.trust","smtp.gmail.com"); + return properties; + } +} \ No newline at end of file diff --git a/src/main/java/com/narae/fliwith/config/security/util/JwtAccessDeniedHandler.java b/src/main/java/com/narae/fliwith/config/security/util/JwtAccessDeniedHandler.java index 8a2ccf9..fa958bb 100644 --- a/src/main/java/com/narae/fliwith/config/security/util/JwtAccessDeniedHandler.java +++ b/src/main/java/com/narae/fliwith/config/security/util/JwtAccessDeniedHandler.java @@ -4,6 +4,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.apache.http.HttpStatus; import org.json.JSONObject; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; @@ -22,7 +23,7 @@ public void handle(HttpServletRequest request, HttpServletResponse response, Acc private void setResponse(HttpServletResponse response, SecurityExceptionList exceptionCode) throws IOException { response.setContentType("application/json;charset=UTF-8"); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.setStatus(HttpStatus.SC_FORBIDDEN); JSONObject responseJson = new JSONObject(); responseJson.put("timestamp", LocalDateTime.now().withNano(0).toString()); diff --git a/src/main/java/com/narae/fliwith/config/security/util/JwtAuthenticationEntryPoint.java b/src/main/java/com/narae/fliwith/config/security/util/JwtAuthenticationEntryPoint.java index 4b0aea7..b36b7b1 100644 --- a/src/main/java/com/narae/fliwith/config/security/util/JwtAuthenticationEntryPoint.java +++ b/src/main/java/com/narae/fliwith/config/security/util/JwtAuthenticationEntryPoint.java @@ -17,12 +17,22 @@ public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { String exception = String.valueOf(request.getAttribute("exception")); - //TODO: 조건별 예외 처리 - if("S0004".equals(exception)){ + if(exception.equals(SecurityExceptionList.UNKNOWN_ERROR.getErrorCode())) + setResponse(response, SecurityExceptionList.UNKNOWN_ERROR); + + else if(exception.equals(SecurityExceptionList.MALFORMED_TOKEN_ERROR.getErrorCode())) + setResponse(response, SecurityExceptionList.MALFORMED_TOKEN_ERROR); + + else if(exception.equals(SecurityExceptionList.ILLEGAL_TOKEN_ERROR.getErrorCode())) + setResponse(response, SecurityExceptionList.ILLEGAL_TOKEN_ERROR); + + else if(exception.equals(SecurityExceptionList.EXPIRED_TOKEN_ERROR.getErrorCode())) setResponse(response, SecurityExceptionList.EXPIRED_TOKEN_ERROR); - } else{ - setResponse(response, SecurityExceptionList.ACCESS_DENIED); - } + + else if(exception.equals(SecurityExceptionList.UNSUPPORTED_TOKEN_ERROR.getErrorCode())) + setResponse(response, SecurityExceptionList.UNSUPPORTED_TOKEN_ERROR); + + else setResponse(response, SecurityExceptionList.ACCESS_DENIED); } diff --git a/src/main/java/com/narae/fliwith/config/security/util/SecurityConfig.java b/src/main/java/com/narae/fliwith/config/security/util/SecurityConfig.java index 82e4b05..0554e25 100644 --- a/src/main/java/com/narae/fliwith/config/security/util/SecurityConfig.java +++ b/src/main/java/com/narae/fliwith/config/security/util/SecurityConfig.java @@ -39,6 +39,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/user/**").permitAll() .requestMatchers("/admin/**").permitAll() .requestMatchers("/user/profile").authenticated() + .requestMatchers("/user/logout").authenticated() .anyRequest().authenticated() ) diff --git a/src/main/java/com/narae/fliwith/config/security/util/TokenUtil.java b/src/main/java/com/narae/fliwith/config/security/util/TokenUtil.java index f678824..2433a41 100644 --- a/src/main/java/com/narae/fliwith/config/security/util/TokenUtil.java +++ b/src/main/java/com/narae/fliwith/config/security/util/TokenUtil.java @@ -1,11 +1,16 @@ package com.narae.fliwith.config.security.util; +import static com.narae.fliwith.exception.security.constants.SecurityExceptionList.ACCESS_DENIED; +import static com.narae.fliwith.exception.security.constants.SecurityExceptionList.EXPIRED_TOKEN_ERROR; +import static com.narae.fliwith.exception.security.constants.SecurityExceptionList.ILLEGAL_TOKEN_ERROR; +import static com.narae.fliwith.exception.security.constants.SecurityExceptionList.MALFORMED_TOKEN_ERROR; +import static com.narae.fliwith.exception.security.constants.SecurityExceptionList.UNSUPPORTED_TOKEN_ERROR; + import com.narae.fliwith.config.security.dto.CustomUser; import com.narae.fliwith.config.security.dto.TokenRes; import com.narae.fliwith.domain.Token; import com.narae.fliwith.domain.User; -import com.narae.fliwith.exception.security.ExpiredTokenException; import com.narae.fliwith.exception.security.InvalidTokenException; import com.narae.fliwith.exception.user.NotFoundUserException; import com.narae.fliwith.repository.TokenRepository; @@ -15,6 +20,7 @@ import io.jsonwebtoken.security.Keys; import jakarta.servlet.ServletRequest; import java.util.List; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -30,6 +36,7 @@ import java.util.stream.Collectors; @Component +@Slf4j public class TokenUtil implements InitializingBean { private final UserRepository userRepository; private final TokenRepository tokenRepository; @@ -114,44 +121,50 @@ public boolean validateToken(String token, ServletRequest request){ Jws claims =Jwts.parser().setSigningKey(key).build().parseClaimsJws(token); return claims.getBody().getExpiration().after(new Date()); } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { - request.setAttribute("exception", "4043"); - System.out.println("4043"); + log.info("잘못된 JWT 서명입니다."); + request.setAttribute("exception", MALFORMED_TOKEN_ERROR.getErrorCode()); + } catch (ExpiredJwtException e) { - request.setAttribute("exception", "S0004"); - System.out.println("4044"); - throw new ExpiredTokenException(); + log.info("만료된 JWT 토큰입니다."); + request.setAttribute("exception", EXPIRED_TOKEN_ERROR.getErrorCode()); } catch (UnsupportedJwtException e) { - request.setAttribute("exception", "4045"); - System.out.println("4045"); + log.info("지원되지 않는 JWT 토큰입니다."); + request.setAttribute("exception", UNSUPPORTED_TOKEN_ERROR.getErrorCode()); } catch (IllegalArgumentException e) { - request.setAttribute("exception", "4046"); - System.out.println("4046"); + log.info("JWT 토큰이 잘못되었습니다."); + request.setAttribute("exception", ILLEGAL_TOKEN_ERROR.getErrorCode()); + + } catch (Exception e) { + log.info(e.getMessage()); + request.setAttribute("exception", ACCESS_DENIED.getErrorCode()); } return false; } public TokenRes reissue(String token){ - //token = refreshToken - - //accessToken으로 user를 찾고 + //사용자가 보낸 refreshToken으로 user를 찾고 String userEmail = getSubject(token); User user = userRepository.findByEmail(userEmail).orElseThrow(NotFoundUserException::new); - //찾은 user로 refreshToken을 가져오고 + //찾은 user로 저장되어있는 refreshToken을 가져오고 Token preToken = tokenRepository.findByUser(user).orElseThrow(InvalidTokenException::new); - tokenRepository.delete(preToken); - TokenRes tokenRes = token(user); - Token newToken = Token.builder() - .user(user) - .refreshToken(tokenRes.getRefreshToken()) - .build(); - tokenRepository.save(newToken); + if(preToken.getRefreshToken().equals(token)){ + tokenRepository.delete(preToken); + TokenRes tokenRes = token(user); + Token newToken = Token.builder() + .user(user) + .refreshToken(tokenRes.getRefreshToken()) + .build(); + tokenRepository.save(newToken); + + return tokenRes; + } - return tokenRes; + throw new InvalidTokenException(); } diff --git a/src/main/java/com/narae/fliwith/controller/UserController.java b/src/main/java/com/narae/fliwith/controller/UserController.java index 09ae737..7f66c91 100644 --- a/src/main/java/com/narae/fliwith/controller/UserController.java +++ b/src/main/java/com/narae/fliwith/controller/UserController.java @@ -6,6 +6,7 @@ import com.narae.fliwith.dto.UserReq.*; import com.narae.fliwith.dto.UserRes.ProfileRes; import com.narae.fliwith.dto.base.BaseRes; +import com.narae.fliwith.service.MailService; import com.narae.fliwith.service.UserService; import jakarta.servlet.ServletRequest; import lombok.RequiredArgsConstructor; @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @@ -25,6 +27,7 @@ public class UserController { private final UserService userService; + @PostMapping("/signup/email") public ResponseEntity> signUp(@RequestBody SignUpReq signUpReq){ userService.signUp(signUpReq); @@ -58,5 +61,16 @@ public ResponseEntity> reissue(@RequestHeader(value = "Refresh return ResponseEntity.ok(BaseRes.create(HttpStatus.OK.value(), "토큰 재발급에 성공했습니다.", userService.reissue(token, request))); } + @PostMapping("/logout") + public ResponseEntity> logout(@AuthenticationPrincipal CustomUser customUser) { + userService.logout(customUser.getEmail()); + return ResponseEntity.ok(BaseRes.create(HttpStatus.OK.value(), "로그아웃에 성공했습니다.")); + } + + @GetMapping("/authemail") + public ResponseEntity> authEmail(@RequestParam String auth) { + userService.updateSignupStatus(auth); + return ResponseEntity.ok(BaseRes.create(HttpStatus.OK.value(), "이메일 인증에 성공했습니다.")); + } } diff --git a/src/main/java/com/narae/fliwith/controller/openAPI/TourController.java b/src/main/java/com/narae/fliwith/controller/openAPI/TourController.java index 2cda774..0cb16ea 100644 --- a/src/main/java/com/narae/fliwith/controller/openAPI/TourController.java +++ b/src/main/java/com/narae/fliwith/controller/openAPI/TourController.java @@ -25,13 +25,15 @@ public class TourController { private final TourService tourService; @GetMapping("/tour") - public ResponseEntity>> getTourByType(@RequestParam String latitude, @RequestParam String longitude, @RequestParam String contentTypeId){ - return ResponseEntity.ok(BaseRes.create(HttpStatus.OK.value(), "관광지 목록 조회에 성공했습니다.", tourService.getTourByType(latitude, longitude, contentTypeId))); + public ResponseEntity>> getTourByType(@AuthenticationPrincipal CustomUser customUser, @RequestParam String latitude, @RequestParam String longitude, @RequestParam String contentTypeId){ + return ResponseEntity.ok(BaseRes.create(HttpStatus.OK.value(), "관광지 목록 조회에 성공했습니다.", tourService.getTourByType( + customUser.getEmail(), latitude, longitude, contentTypeId))); } @GetMapping("/tour/{contentTypeId}/{contentId}") - public ResponseEntity> getTour(@PathVariable(value = "contentTypeId")String contentTypeId, @PathVariable(value = "contentId") String contentId){ - return ResponseEntity.ok(BaseRes.create(HttpStatus.OK.value(), "관광지 상세 조회에 성공했습니다.", tourService.getTour(contentTypeId, contentId))); + public ResponseEntity> getTour(@AuthenticationPrincipal CustomUser customUser, @PathVariable(value = "contentTypeId")String contentTypeId, @PathVariable(value = "contentId") String contentId){ + return ResponseEntity.ok(BaseRes.create(HttpStatus.OK.value(), "관광지 상세 조회에 성공했습니다.", tourService.getTour( + customUser.getEmail(), contentTypeId, contentId))); } diff --git a/src/main/java/com/narae/fliwith/domain/SignupStatus.java b/src/main/java/com/narae/fliwith/domain/SignupStatus.java new file mode 100644 index 0000000..8ddf277 --- /dev/null +++ b/src/main/java/com/narae/fliwith/domain/SignupStatus.java @@ -0,0 +1,10 @@ +package com.narae.fliwith.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum SignupStatus { + COMPLETE, ING, FAIL +} diff --git a/src/main/java/com/narae/fliwith/domain/User.java b/src/main/java/com/narae/fliwith/domain/User.java index 0224024..f9eeffb 100644 --- a/src/main/java/com/narae/fliwith/domain/User.java +++ b/src/main/java/com/narae/fliwith/domain/User.java @@ -23,6 +23,13 @@ public class User { private Disability disability; @Enumerated(EnumType.STRING) private Role role; + @Enumerated(EnumType.STRING) + private SignupStatus signupStatus; + private String auth; //TODO: 탈퇴상태 추가 + public void completeSignup(){ + signupStatus = SignupStatus.COMPLETE; + } + } diff --git a/src/main/java/com/narae/fliwith/exception/constants/UserExceptionList.java b/src/main/java/com/narae/fliwith/exception/constants/UserExceptionList.java index 8aba5a3..2032f68 100644 --- a/src/main/java/com/narae/fliwith/exception/constants/UserExceptionList.java +++ b/src/main/java/com/narae/fliwith/exception/constants/UserExceptionList.java @@ -10,7 +10,11 @@ public enum UserExceptionList { DUPLICATE_USER_EMAIL("U0001", HttpStatus.CONFLICT, "이미 가입된 이메일입니다."), LOGIN_FAIL("U0002", HttpStatus.NOT_FOUND, "로그인에 실패했습니다."), DUPLICATE_USER_NICKNAME("U0003", HttpStatus.CONFLICT, "이미 존재하는 닉네임입니다."), - NOT_FOUND_USER_ERROR("U0004", HttpStatus.NOT_FOUND, "존재하지 않는 사용자입니다.") + NOT_FOUND_USER_ERROR("U0004", HttpStatus.NOT_FOUND, "존재하지 않는 사용자입니다."), + ALREADY_LOGOUT_ERROR("U0005", HttpStatus.NOT_FOUND, "이미 로그아웃한 사용자입니다."), + EMAIL_SEND_ERROR("U0006", HttpStatus.NOT_FOUND, "이메일 발송에 실패했습니다."), + EMAIL_AUTH_ERROR("U0007", HttpStatus.BAD_REQUEST, "유효하지 않은 인증 링크입니다."), + REQUIRE_EMAIL_AUTH("U0008", HttpStatus.UNAUTHORIZED, "이메일 인증이 필요한 사용자입니다.") ; diff --git a/src/main/java/com/narae/fliwith/exception/security/constants/SecurityExceptionList.java b/src/main/java/com/narae/fliwith/exception/security/constants/SecurityExceptionList.java index d27f07e..f97d328 100644 --- a/src/main/java/com/narae/fliwith/exception/security/constants/SecurityExceptionList.java +++ b/src/main/java/com/narae/fliwith/exception/security/constants/SecurityExceptionList.java @@ -8,10 +8,13 @@ @RequiredArgsConstructor public enum SecurityExceptionList { UNKNOWN_ERROR("S0001", HttpStatus.INTERNAL_SERVER_ERROR, "예상치 못한 오류가 발생했습니다."), - ACCESS_DENIED("S0002", HttpStatus.UNAUTHORIZED, "401 접근이 거부되었습니다."), - ACCESS_DENIED_03("S0003", HttpStatus.UNAUTHORIZED, "403 접근이 거부되었습니다."), + ACCESS_DENIED("S0002", HttpStatus.UNAUTHORIZED, "접근이 거부되었습니다."), + ACCESS_DENIED_03("S0003", HttpStatus.FORBIDDEN, "권한이 없는 사용자가 접근하려 했습니다."), EXPIRED_TOKEN_ERROR("S0004", HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), - INVALID_TOKEN_ERROR("S0005", HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다.") + INVALID_TOKEN_ERROR("S0005", HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."), + MALFORMED_TOKEN_ERROR("S0006", HttpStatus.UNAUTHORIZED, "잘못된 토큰 서명입니다."), + UNSUPPORTED_TOKEN_ERROR("S0007", HttpStatus.UNAUTHORIZED, "지원되지 않는 토큰입니다."), + ILLEGAL_TOKEN_ERROR("S0008", HttpStatus.UNAUTHORIZED, "토큰이 잘못되었습니다.") ; private final String errorCode; private final HttpStatus httpStatus; diff --git a/src/main/java/com/narae/fliwith/exception/user/AlreadyLogoutException.java b/src/main/java/com/narae/fliwith/exception/user/AlreadyLogoutException.java new file mode 100644 index 0000000..0f61bc3 --- /dev/null +++ b/src/main/java/com/narae/fliwith/exception/user/AlreadyLogoutException.java @@ -0,0 +1,11 @@ +package com.narae.fliwith.exception.user; + +import static com.narae.fliwith.exception.constants.UserExceptionList.ALREADY_LOGOUT_ERROR; + +public class AlreadyLogoutException extends UserException{ + public AlreadyLogoutException(){ + super(ALREADY_LOGOUT_ERROR.getErrorCode(), + ALREADY_LOGOUT_ERROR.getHttpStatus(), + ALREADY_LOGOUT_ERROR.getMessage()); + } +} diff --git a/src/main/java/com/narae/fliwith/exception/user/EmailAuthException.java b/src/main/java/com/narae/fliwith/exception/user/EmailAuthException.java new file mode 100644 index 0000000..77234d7 --- /dev/null +++ b/src/main/java/com/narae/fliwith/exception/user/EmailAuthException.java @@ -0,0 +1,12 @@ +package com.narae.fliwith.exception.user; + +import static com.narae.fliwith.exception.constants.UserExceptionList.EMAIL_AUTH_ERROR; + +public class EmailAuthException extends UserException{ + public EmailAuthException(){ + super(EMAIL_AUTH_ERROR.getErrorCode(), + EMAIL_AUTH_ERROR.getHttpStatus(), + EMAIL_AUTH_ERROR.getMessage()); + } + +} diff --git a/src/main/java/com/narae/fliwith/exception/user/EmailSendException.java b/src/main/java/com/narae/fliwith/exception/user/EmailSendException.java new file mode 100644 index 0000000..c9498c4 --- /dev/null +++ b/src/main/java/com/narae/fliwith/exception/user/EmailSendException.java @@ -0,0 +1,11 @@ +package com.narae.fliwith.exception.user; + +import static com.narae.fliwith.exception.constants.UserExceptionList.EMAIL_SEND_ERROR; + +public class EmailSendException extends UserException{ + public EmailSendException(){ + super(EMAIL_SEND_ERROR.getErrorCode(), + EMAIL_SEND_ERROR.getHttpStatus(), + EMAIL_SEND_ERROR.getMessage()); + } +} diff --git a/src/main/java/com/narae/fliwith/exception/user/RequireEmailAuthException.java b/src/main/java/com/narae/fliwith/exception/user/RequireEmailAuthException.java new file mode 100644 index 0000000..0ab9bf3 --- /dev/null +++ b/src/main/java/com/narae/fliwith/exception/user/RequireEmailAuthException.java @@ -0,0 +1,11 @@ +package com.narae.fliwith.exception.user; + +import static com.narae.fliwith.exception.constants.UserExceptionList.REQUIRE_EMAIL_AUTH; + +public class RequireEmailAuthException extends UserException { + public RequireEmailAuthException(){ + super(REQUIRE_EMAIL_AUTH.getErrorCode(), + REQUIRE_EMAIL_AUTH.getHttpStatus(), + REQUIRE_EMAIL_AUTH.getMessage()); + } +} diff --git a/src/main/java/com/narae/fliwith/repository/UserRepository.java b/src/main/java/com/narae/fliwith/repository/UserRepository.java index fe33291..b84e27b 100644 --- a/src/main/java/com/narae/fliwith/repository/UserRepository.java +++ b/src/main/java/com/narae/fliwith/repository/UserRepository.java @@ -13,4 +13,5 @@ public interface UserRepository extends JpaRepository { Optional findById(Long id); Optional findByEmail(String email); + Optional findByAuth(String uuid); } diff --git a/src/main/java/com/narae/fliwith/service/AuthService.java b/src/main/java/com/narae/fliwith/service/AuthService.java new file mode 100644 index 0000000..baca164 --- /dev/null +++ b/src/main/java/com/narae/fliwith/service/AuthService.java @@ -0,0 +1,25 @@ +package com.narae.fliwith.service; + +import com.narae.fliwith.domain.SignupStatus; +import com.narae.fliwith.domain.User; +import com.narae.fliwith.exception.user.LogInFailException; +import com.narae.fliwith.exception.user.RequireEmailAuthException; +import com.narae.fliwith.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Transactional +public class AuthService { + private final UserRepository userRepository; + + public User authUser(String email){ + User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + if(!user.getSignupStatus().equals(SignupStatus.COMPLETE)){ + throw new RequireEmailAuthException(); + } + return user; + } +} diff --git a/src/main/java/com/narae/fliwith/service/MailService.java b/src/main/java/com/narae/fliwith/service/MailService.java new file mode 100644 index 0000000..8d2248a --- /dev/null +++ b/src/main/java/com/narae/fliwith/service/MailService.java @@ -0,0 +1,58 @@ +package com.narae.fliwith.service; + +import com.narae.fliwith.domain.User; +import com.narae.fliwith.exception.user.EmailSendException; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import jakarta.transaction.Transactional; +import java.io.UnsupportedEncodingException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; +@Slf4j +@RequiredArgsConstructor +@Service +@Transactional +public class MailService { + private final JavaMailSender javaMailSender; + + + @Value("${spring.mail.username}") + private String email; + @Value("${email.server}") + private String server; + + private MimeMessage createMessage(User user) throws MessagingException, UnsupportedEncodingException { + String content = "
" + + "

안녕하세요. Fliwith 입니다

" + + "
" + + "

아래 링크를 클릭하면 이메일 인증이 완료됩니다.

" + + "인증 링크" + + "

"; + + + MimeMessage message = javaMailSender.createMimeMessage(); + message.addRecipients(MimeMessage.RecipientType.TO, user.getEmail()); + message.setSubject("Fliwith 회원가입 이메일 인증:"); //메일 제목 + message.setText(content, "utf-8", "html"); //내용, charset타입, subtype + message.setFrom(new InternetAddress(email,"Fliwith_Official")); //보내는 사람의 메일 주소, 보내는 사람 이름 + + log.info("message : " + message); + return message; + } + + //메일 발송 + public void sendMail(User user) { + try{ + MimeMessage mimeMessage = createMessage(user); + log.info("ok"); + javaMailSender.send(mimeMessage); + } catch (Exception e){ + e.printStackTrace(); + throw new EmailSendException(); + } + } +} diff --git a/src/main/java/com/narae/fliwith/service/ReviewService.java b/src/main/java/com/narae/fliwith/service/ReviewService.java index 2f8163e..3ef1757 100644 --- a/src/main/java/com/narae/fliwith/service/ReviewService.java +++ b/src/main/java/com/narae/fliwith/service/ReviewService.java @@ -37,13 +37,13 @@ @Transactional public class ReviewService { private final ReviewRepository reviewRepository; - private final UserRepository userRepository; private final SpotRepository spotRepository; private final LikeRepository likeRepository; + private final AuthService authService; public void writeReview(String email, ReviewReq.WriteReviewReq req) { - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); Spot spot = spotRepository.findById(req.getContentId()).orElseThrow(SpotFindFailException::new); Review review = Review.builder().likes(new ArrayList<>()).content(req.getContent()).user(user).spot(spot).images(new ArrayList<>()).build(); for(String url : req.getImages()){ @@ -54,7 +54,7 @@ public void writeReview(String email, ReviewReq.WriteReviewReq req) { } public ReviewRes.ReviewDetailRes getReviewDetail(String email, Long reviewId) { - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); Review review = reviewRepository.findById(reviewId).orElseThrow(ReviewFindFailException::new); @@ -73,7 +73,7 @@ public ReviewRes.ReviewDetailRes getReviewDetail(String email, Long reviewId) { } public void deleteReview(String email, Long reviewId) { - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); Review review = reviewRepository.findById(reviewId).orElseThrow(ReviewFindFailException::new); @@ -88,7 +88,7 @@ public void deleteReview(String email, Long reviewId) { public ReviewRes.ReviewDetailRes updateReview(String email, Long reviewId, ReviewReq.WriteReviewReq req) { - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); Review review = reviewRepository.findById(reviewId).orElseThrow(ReviewFindFailException::new); @@ -120,7 +120,7 @@ public ReviewRes.ReviewDetailRes updateReview(String email, Long reviewId, Revie public ReviewItemRes getReviewList(String email, int pageNo, String order) { - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); Pageable pageable = PageRequest.of(pageNo, 10); // 0은 페이지 번호, 10은 페이지 크기 @@ -160,7 +160,7 @@ public ReviewItemRes getReviewList(String email, int pageNo, String order) { } public List getSpotName(String email, String spotName) { - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); List spots = spotRepository.findAllByTitleContains(spotName); @@ -171,7 +171,7 @@ public List getSpotName(String email, String spotName) { } public LikeUnlikeRes likeUnlikeReview(String email, Long reviewId) { - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); AtomicBoolean like = new AtomicBoolean(true); Review review = reviewRepository.findById(reviewId).orElseThrow(ReviewFindFailException::new); @@ -189,7 +189,7 @@ public LikeUnlikeRes likeUnlikeReview(String email, Long reviewId) { public ReviewItemRes getReviewLikeList(String email, int pageNo) { - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); Pageable pageable = PageRequest.of(pageNo, 10); // 0은 페이지 번호, 10은 페이지 크기 @@ -207,7 +207,7 @@ public ReviewItemRes getReviewLikeList(String email, int pageNo) { } public ReviewItemRes getReviewWriteList(String email, int pageNo) { - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); Pageable pageable = PageRequest.of(pageNo, 10); // 0은 페이지 번호, 10은 페이지 크기 diff --git a/src/main/java/com/narae/fliwith/service/S3Service.java b/src/main/java/com/narae/fliwith/service/S3Service.java index 3c47e3e..e355630 100644 --- a/src/main/java/com/narae/fliwith/service/S3Service.java +++ b/src/main/java/com/narae/fliwith/service/S3Service.java @@ -33,11 +33,11 @@ public class S3Service { @Value("${image.folder}") private String folder; - private final UserRepository userRepository; + private final AuthService authService; public ImageRes.PresignedUrlRes issuePresignedUrl(String email, PresignedUrlReq presignedUrlReq){ - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); String imageName = folder+UUID.randomUUID()+"."+presignedUrlReq.getImageExtension().getUploadExtension(); diff --git a/src/main/java/com/narae/fliwith/service/UserService.java b/src/main/java/com/narae/fliwith/service/UserService.java index a037935..2f0097d 100644 --- a/src/main/java/com/narae/fliwith/service/UserService.java +++ b/src/main/java/com/narae/fliwith/service/UserService.java @@ -1,9 +1,9 @@ package com.narae.fliwith.service; -import com.narae.fliwith.config.security.dto.ReissueTokenRes; import com.narae.fliwith.config.security.dto.TokenRes; import com.narae.fliwith.config.security.util.TokenUtil; import com.narae.fliwith.domain.Role; +import com.narae.fliwith.domain.SignupStatus; import com.narae.fliwith.domain.Token; import com.narae.fliwith.domain.User; import com.narae.fliwith.dto.UserReq; @@ -11,15 +11,17 @@ import com.narae.fliwith.dto.UserReq.NicknameReq; import com.narae.fliwith.dto.UserRes.ProfileRes; import com.narae.fliwith.exception.security.InvalidTokenException; +import com.narae.fliwith.exception.user.AlreadyLogoutException; import com.narae.fliwith.exception.user.DuplicateUserEmailException; import com.narae.fliwith.exception.user.DuplicateUserNicknameException; +import com.narae.fliwith.exception.user.EmailAuthException; import com.narae.fliwith.exception.user.LogInFailException; import com.narae.fliwith.repository.TokenRepository; import com.narae.fliwith.repository.UserRepository; import jakarta.servlet.ServletRequest; import jakarta.transaction.Transactional; +import java.util.UUID; import lombok.RequiredArgsConstructor; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -29,9 +31,10 @@ public class UserService { private final UserRepository userRepository; private final TokenUtil tokenUtil; - private final AuthenticationManagerBuilder authenticationManagerBuilder; private final PasswordEncoder passwordEncoder; private final TokenRepository tokenRepository; + private final MailService mailService; + private final AuthService authService; public void signUp(UserReq.SignUpReq signUpReq) { if(userRepository.existsByEmail(signUpReq.getEmail())){ @@ -46,14 +49,17 @@ public void signUp(UserReq.SignUpReq signUpReq) { .role(Role.ROLE_USER) .nickname(signUpReq.getNickname()) .disability(signUpReq.getDisability()) + .signupStatus(SignupStatus.ING) + .auth(UUID.randomUUID().toString()) .build(); - userRepository.save(user); + user = userRepository.save(user); + mailService.sendMail(user); } public TokenRes logIn(UserReq.LogInReq logInReq) { - User user = userRepository.findByEmail(logInReq.getEmail()).orElseThrow(LogInFailException::new); + User user = authService.authUser(logInReq.getEmail()); if(passwordEncoder.matches(logInReq.getPassword(), user.getPw())){ tokenUtil.makeAuthentication(user); @@ -71,9 +77,6 @@ public TokenRes logIn(UserReq.LogInReq logInReq) { } } - //TODO: 로그아웃시 tokenRepository delete - - public void emailCheck(EmailReq emailReq) { if(userRepository.existsByEmail(emailReq.getEmail())){ @@ -90,7 +93,8 @@ public void nicknameCheck(NicknameReq nicknameReq) { } public ProfileRes getProfile(String email) { - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); + return ProfileRes.builder() .disability(user.getDisability()) .nickname(user.getNickname()) @@ -107,7 +111,14 @@ public TokenRes reissue(String token, ServletRequest request) { throw new InvalidTokenException(); } + public void logout(String email) { + User user = authService.authUser(email); + tokenRepository.findByUser(user).ifPresentOrElse(tokenRepository::delete, AlreadyLogoutException::new); + } - + public void updateSignupStatus(String auth){ + User user = userRepository.findByAuth(auth).orElseThrow(EmailAuthException::new); + user.completeSignup(); + } } diff --git a/src/main/java/com/narae/fliwith/service/openAPI/TourService.java b/src/main/java/com/narae/fliwith/service/openAPI/TourService.java index 9f8cf6b..b73780d 100644 --- a/src/main/java/com/narae/fliwith/service/openAPI/TourService.java +++ b/src/main/java/com/narae/fliwith/service/openAPI/TourService.java @@ -14,11 +14,10 @@ import com.narae.fliwith.dto.openAPI.*; import com.narae.fliwith.exception.spot.SpotFindFailException; import com.narae.fliwith.exception.tour.NotFoundAiTourException; -import com.narae.fliwith.exception.user.LogInFailException; import com.narae.fliwith.repository.LocationRepository; import com.narae.fliwith.repository.ReviewRepository; import com.narae.fliwith.repository.SpotRepository; -import com.narae.fliwith.repository.UserRepository; +import com.narae.fliwith.service.AuthService; import com.theokanning.openai.completion.chat.ChatCompletionRequest; import com.theokanning.openai.completion.chat.ChatMessage; import com.theokanning.openai.service.OpenAiService; @@ -58,7 +57,7 @@ public class TourService { private final SpotRepository spotRepository; private final LocationRepository locationRepository; private final ReviewRepository reviewRepository; - private final UserRepository userRepository; + private final AuthService authService; public Mono getDetailWithTour(String contentId) { return webClient.get() @@ -120,7 +119,8 @@ public Mono getDetailIntro(String contentId, String content .onErrorReturn(DecodingException.class, new DetailIntroRes.Item()); } - public List getTourByType(String latitude, String longitude, String contentTypeId) { + public List getTourByType(String email, String latitude, String longitude, String contentTypeId) { + User user = authService.authUser(email); return webClient.get() .uri(uriBuilder -> uriBuilder.path("/locationBasedList1") @@ -146,7 +146,9 @@ public List getTourByType(String latitude, String longitude, String co } - public TourDetailRes getTour(String contentTypeId, String contentId) { + public TourDetailRes getTour(String email, String contentTypeId, String contentId) { + User user = authService.authUser(email); + DetailWithTourRes.Item detailWithTour = getDetailWithTour(contentId).block(); DetailIntroRes.Item detailIntro = getDetailIntro(contentId, contentTypeId).block(); DetailCommonRes.Item detailCommon = getDetailCommon(contentId).block(); @@ -243,7 +245,7 @@ private AreaBasedListRes.Body getAreaBasedListWithAreaCode(String contentTypeId, } public TourDetailRes getAiTour(String email, AiTourReq aiTourReq) { - User user = userRepository.findByEmail(email).orElseThrow(LogInFailException::new); + User user = authService.authUser(email); AiTourParams params = AiTourParams.from(aiTourReq); List askList = new ArrayList<>(); @@ -294,7 +296,8 @@ public TourDetailRes getAiTour(String email, AiTourReq aiTourReq) { String gptRecommend = createGptRecommend(sb.toString(), params); // System.out.println(gptAnswer); TourAsk gptChoice = askList.stream().filter(tourAsk -> gptRecommend.contains(tourAsk.getName())).findFirst().get(); - return getTour(gptChoice.getContentTypeId(), gptChoice.getContentId()); + //TODO: auth 중복 로직 해결 + return getTour(email, gptChoice.getContentTypeId(), gptChoice.getContentId()); }