diff --git a/build.gradle b/build.gradle index e64376bb..ce35d2b3 100644 --- a/build.gradle +++ b/build.gradle @@ -54,6 +54,17 @@ dependencies { // Lombok annotationProcessor 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok' + + // Webflux + implementation 'org.springframework.boot:spring-boot-starter-webflux' + + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.boot:spring-boot-starter-cache' + implementation 'org.apache.commons:commons-pool2' + + // Http ~~ + implementation 'org.apache.httpcomponents:httpclient:4.5.13' } tasks.named('test') { diff --git a/src/main/java/com/project/likelion13thbe/domain/member/controller/MemberController.java b/src/main/java/com/project/likelion13thbe/domain/member/controller/MemberController.java index c3cfa023..0a255710 100644 --- a/src/main/java/com/project/likelion13thbe/domain/member/controller/MemberController.java +++ b/src/main/java/com/project/likelion13thbe/domain/member/controller/MemberController.java @@ -59,8 +59,8 @@ public ResponseEntity updateMyAccount( @AuthenticationPrincipal UserDetails userDetails, @RequestBody MemberReqDTO.MemberUpdateReqDTO dto ) { - String username = userDetails.getUsername(); - memberCommandService.updateMemberByUsername(username, dto); + String email = userDetails.getUsername(); + memberCommandService.updateMemberByEmail(email, dto); return ResponseEntity.ok("Member Updated Successfully."); } @@ -69,8 +69,8 @@ public ResponseEntity updateMyAccount( public ResponseEntity deleteMyAccount( @AuthenticationPrincipal UserDetails userDetails ) { - String username = userDetails.getUsername(); - memberCommandService.deleteMemberByUsername(username); + String email = userDetails.getUsername(); + memberCommandService.deleteMemberByEmail(email); return ResponseEntity.ok("Member Deleted Successfully."); } } diff --git a/src/main/java/com/project/likelion13thbe/domain/member/converter/MemberConverter.java b/src/main/java/com/project/likelion13thbe/domain/member/converter/MemberConverter.java index 36eeea3b..02de10e9 100644 --- a/src/main/java/com/project/likelion13thbe/domain/member/converter/MemberConverter.java +++ b/src/main/java/com/project/likelion13thbe/domain/member/converter/MemberConverter.java @@ -3,14 +3,16 @@ import com.project.likelion13thbe.domain.member.dto.request.MemberReqDTO; import com.project.likelion13thbe.domain.member.dto.response.MemberResDTO; import com.project.likelion13thbe.domain.member.entity.Member; +import org.springframework.security.crypto.password.PasswordEncoder; public class MemberConverter { - public static Member toMember(MemberReqDTO.MemberCreateReqDTO memberCreateReqDTO) { + public static Member toMember(MemberReqDTO.MemberCreateReqDTO dto, PasswordEncoder passwordEncoder) { return Member.builder() - .email(memberCreateReqDTO.getEmail()) - .password(memberCreateReqDTO.getPassword()) - .nickname(memberCreateReqDTO.getNickname()) + .email(dto.getEmail()) + .password(passwordEncoder.encode(dto.getPassword())) + .nickname(dto.getNickname()) + .role(dto.getRole()) .build(); } diff --git a/src/main/java/com/project/likelion13thbe/domain/member/dto/request/MemberReqDTO.java b/src/main/java/com/project/likelion13thbe/domain/member/dto/request/MemberReqDTO.java index 24d69b60..1c096464 100644 --- a/src/main/java/com/project/likelion13thbe/domain/member/dto/request/MemberReqDTO.java +++ b/src/main/java/com/project/likelion13thbe/domain/member/dto/request/MemberReqDTO.java @@ -10,6 +10,7 @@ public static class MemberCreateReqDTO { private String email; private String password; private String nickname; + private String role; } @Getter @@ -23,8 +24,8 @@ public static class MemberUpdateReqDTO { @Getter @NoArgsConstructor @AllArgsConstructor - public class LoginRequestDTO { - public String email; - public String password; + public static class LoginRequestDTO { + private String email; + private String password; } } diff --git a/src/main/java/com/project/likelion13thbe/domain/member/repository/MemberRepository.java b/src/main/java/com/project/likelion13thbe/domain/member/repository/MemberRepository.java index b99441c7..790368f2 100644 --- a/src/main/java/com/project/likelion13thbe/domain/member/repository/MemberRepository.java +++ b/src/main/java/com/project/likelion13thbe/domain/member/repository/MemberRepository.java @@ -10,5 +10,4 @@ public interface MemberRepository extends JpaRepository { Optional findById(Long id); Optional findByEmail(String email); - Optional findByUsername(String username); } diff --git a/src/main/java/com/project/likelion13thbe/domain/member/service/command/MemberCommandService.java b/src/main/java/com/project/likelion13thbe/domain/member/service/command/MemberCommandService.java index 5c8220d0..4b73cd07 100644 --- a/src/main/java/com/project/likelion13thbe/domain/member/service/command/MemberCommandService.java +++ b/src/main/java/com/project/likelion13thbe/domain/member/service/command/MemberCommandService.java @@ -9,6 +9,6 @@ public interface MemberCommandService { MemberResDTO.MemberCreateResDTO createMember(MemberReqDTO.MemberCreateReqDTO memberCreateReqDTO); void updateMember(Long id, MemberReqDTO.MemberUpdateReqDTO dto); void deleteMember(Long id); - void deleteMemberByUsername(String username); - void updateMemberByUsername(String username, MemberReqDTO.MemberUpdateReqDTO dto); + void deleteMemberByEmail(String email); + void updateMemberByEmail(String email, MemberReqDTO.MemberUpdateReqDTO dto); } diff --git a/src/main/java/com/project/likelion13thbe/domain/member/service/command/MemberCommandServiceImpl.java b/src/main/java/com/project/likelion13thbe/domain/member/service/command/MemberCommandServiceImpl.java index d904aea0..91142610 100644 --- a/src/main/java/com/project/likelion13thbe/domain/member/service/command/MemberCommandServiceImpl.java +++ b/src/main/java/com/project/likelion13thbe/domain/member/service/command/MemberCommandServiceImpl.java @@ -8,30 +8,32 @@ import jakarta.persistence.EntityNotFoundException; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class MemberCommandServiceImpl implements MemberCommandService { private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; public MemberResDTO.MemberCreateResDTO createMember(MemberReqDTO.MemberCreateReqDTO memberCreateReqDTO) { - Member member = MemberConverter.toMember(memberCreateReqDTO); + Member member = MemberConverter.toMember(memberCreateReqDTO, passwordEncoder); memberRepository.save(member); return MemberConverter.toMemberResDTO(member); } - public void deleteMemberByUsername(String username) { - Member member = memberRepository.findByUsername(username) + public void deleteMemberByEmail(String email) { + Member member = memberRepository.findByEmail(email) .orElseThrow(() -> new EntityNotFoundException("Member Not Found")); memberRepository.delete(member); } @Transactional - public void updateMemberByUsername(String username, MemberReqDTO.MemberUpdateReqDTO dto) { - Member member = memberRepository.findByUsername(username) + public void updateMemberByEmail(String email, MemberReqDTO.MemberUpdateReqDTO dto) { + Member member = memberRepository.findByEmail(email) .orElseThrow(() -> new EntityNotFoundException("Member Not Found")); if (dto.getEmail() != null) { diff --git a/src/main/java/com/project/likelion13thbe/domain/openapi/controller/OpenApiController.java b/src/main/java/com/project/likelion13thbe/domain/openapi/controller/OpenApiController.java new file mode 100644 index 00000000..81ed70ce --- /dev/null +++ b/src/main/java/com/project/likelion13thbe/domain/openapi/controller/OpenApiController.java @@ -0,0 +1,18 @@ +package com.project.likelion13thbe.domain.openapi.controller; + +import com.project.likelion13thbe.domain.openapi.service.OpenApiService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/openapi") +public class OpenApiController { + + @Autowired + private OpenApiService openApiService; + + @PostMapping("/ask") + public String askQuestion(@RequestBody String question) { + return openApiService.getOpenApiResponse(question); + } +} diff --git a/src/main/java/com/project/likelion13thbe/domain/openapi/service/OpenApiService.java b/src/main/java/com/project/likelion13thbe/domain/openapi/service/OpenApiService.java new file mode 100644 index 00000000..83ea51d8 --- /dev/null +++ b/src/main/java/com/project/likelion13thbe/domain/openapi/service/OpenApiService.java @@ -0,0 +1,5 @@ +package com.project.likelion13thbe.domain.openapi.service; + +public interface OpenApiService { + String getOpenApiResponse(String prompt); +} diff --git a/src/main/java/com/project/likelion13thbe/domain/openapi/service/OpenApiServiceImpl.java b/src/main/java/com/project/likelion13thbe/domain/openapi/service/OpenApiServiceImpl.java new file mode 100644 index 00000000..47e91090 --- /dev/null +++ b/src/main/java/com/project/likelion13thbe/domain/openapi/service/OpenApiServiceImpl.java @@ -0,0 +1,36 @@ +package com.project.likelion13thbe.domain.openapi.service; + + +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +@Service +public class OpenApiServiceImpl implements OpenApiService { + + private static final String API_KEY = "{$API_KEY}"; + private static final String API_URL = "https://api.deepseek.com/v1/chat/completions"; // 실제 API 엔드포인트 확인 필요 + + public String getOpenApiResponse(String prompt) { + RestTemplate restTemplate = new RestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + // JWT ? + headers.set("Authorization", "Bearer " + API_KEY); + + String requestBody = String.format("{\"model\": \"deepseek-chat\", \"messages\": [{\"role\": \"user\", \"content\": \"%s\"}]}", prompt); + + HttpEntity requestEntity = new HttpEntity<>(requestBody, headers); + + // API 호출 + ResponseEntity response = restTemplate.exchange( + API_URL, + HttpMethod.POST, + requestEntity, + String.class + ); + + return response.getBody(); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/likelion13thbe/domain/order/controller/OrderController.java b/src/main/java/com/project/likelion13thbe/domain/order/controller/OrderController.java index 2d2a4f5b..95f96878 100644 --- a/src/main/java/com/project/likelion13thbe/domain/order/controller/OrderController.java +++ b/src/main/java/com/project/likelion13thbe/domain/order/controller/OrderController.java @@ -1,9 +1,13 @@ package com.project.likelion13thbe.domain.order.controller; +import com.project.likelion13thbe.domain.member.entity.Member; +import com.project.likelion13thbe.domain.member.repository.MemberRepository; import com.project.likelion13thbe.domain.member.service.command.MemberCommandService; import com.project.likelion13thbe.domain.order.dto.request.OrderReqDTO; import com.project.likelion13thbe.domain.order.dto.response.OrderResDTO; import com.project.likelion13thbe.domain.order.service.command.OrderCommandService; +import com.project.likelion13thbe.global.apiPayload.code.GeneralErrorCode; +import com.project.likelion13thbe.global.apiPayload.exception.CustomException; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -19,6 +23,7 @@ public class OrderController { private final OrderCommandService orderCommandService; private final MemberCommandService memberCommandService; + private final MemberRepository memberRepository; @PostMapping public ResponseEntity createOrder( @@ -51,7 +56,12 @@ public ResponseEntity updateMyOrder( @RequestBody OrderReqDTO.OrderUpdateReqDTO dto ) { String username = userDetails.getUsername(); - orderCommandService.updateOrderByUsernameAndId(username, id, dto); + Member member = memberRepository.findByEmail(username) + .orElseThrow(() -> new CustomException(GeneralErrorCode.UNAUTHORIZED_401)); + Long memberId = member.getId(); + + + orderCommandService.updateOrderByMemberIdAndId(memberId, id, dto); return ResponseEntity.ok("Order Updated Successfully."); } @@ -61,7 +71,12 @@ public ResponseEntity deleteMyOrder( @AuthenticationPrincipal UserDetails userDetails ) { String username = userDetails.getUsername(); - orderCommandService.deleteOrderByUsername(username); + + Member member = memberRepository.findByEmail(username) + .orElseThrow(() -> new CustomException(GeneralErrorCode.UNAUTHORIZED_401)); + Long memberId = member.getId(); + + orderCommandService.deleteOrderByMemberId(memberId); return ResponseEntity.ok("Member Deleted Successfully."); } } diff --git a/src/main/java/com/project/likelion13thbe/domain/order/repository/OrderRepository.java b/src/main/java/com/project/likelion13thbe/domain/order/repository/OrderRepository.java index 29f7a0cd..047376fc 100644 --- a/src/main/java/com/project/likelion13thbe/domain/order/repository/OrderRepository.java +++ b/src/main/java/com/project/likelion13thbe/domain/order/repository/OrderRepository.java @@ -8,6 +8,6 @@ @Repository public interface OrderRepository extends JpaRepository { - Optional findByIdAndUsername(Long id, String username); - Optional findByUsername(String username); + Optional findByIdAndMemberId(Long id, Long memberId); + Optional findByMemberId(Long memberId); } diff --git a/src/main/java/com/project/likelion13thbe/domain/order/service/command/OrderCommandService.java b/src/main/java/com/project/likelion13thbe/domain/order/service/command/OrderCommandService.java index 24dc5fdc..2768ecaf 100644 --- a/src/main/java/com/project/likelion13thbe/domain/order/service/command/OrderCommandService.java +++ b/src/main/java/com/project/likelion13thbe/domain/order/service/command/OrderCommandService.java @@ -7,6 +7,6 @@ public interface OrderCommandService { OrderResDTO.OrderCreateResDTO createOrder(OrderReqDTO.OrderCreateReqDTO orderCreateReqDTO); void updateOrder(Long id, OrderReqDTO.OrderUpdateReqDTO dto); void deleteOrder(Long id); - void updateOrderByUsernameAndId(String username, Long id, OrderReqDTO.OrderUpdateReqDTO dto); - void deleteOrderByUsername(String username); + void updateOrderByMemberIdAndId(Long memberId, Long id, OrderReqDTO.OrderUpdateReqDTO dto); + void deleteOrderByMemberId(Long memberId); } diff --git a/src/main/java/com/project/likelion13thbe/domain/order/service/command/OrderCommandServiceImpl.java b/src/main/java/com/project/likelion13thbe/domain/order/service/command/OrderCommandServiceImpl.java index 4658b4bd..6e8008e2 100644 --- a/src/main/java/com/project/likelion13thbe/domain/order/service/command/OrderCommandServiceImpl.java +++ b/src/main/java/com/project/likelion13thbe/domain/order/service/command/OrderCommandServiceImpl.java @@ -61,8 +61,8 @@ public void deleteOrder(Long id) { } } - public void updateOrderByUsernameAndId(String username, Long id, OrderReqDTO.OrderUpdateReqDTO dto) { - Order order = orderRepository.findByIdAndUsername(id, username) + public void updateOrderByMemberIdAndId(Long memberId, Long id, OrderReqDTO.OrderUpdateReqDTO dto) { + Order order = orderRepository.findByIdAndMemberId(id, memberId) .orElseThrow(() -> new CustomException(GeneralErrorCode.UNAUTHORIZED_401)); if (dto.getQuantity() != null) { @@ -80,8 +80,8 @@ public void updateOrderByUsernameAndId(String username, Long id, OrderReqDTO.Ord orderRepository.save(order); } - public void deleteOrderByUsername(String username) { - Order order = orderRepository.findByUsername(username) + public void deleteOrderByMemberId(Long memberId) { + Order order = orderRepository.findByMemberId(memberId) .orElseThrow(() -> new CustomException(GeneralErrorCode.UNAUTHORIZED_401)); orderRepository.delete(order); } diff --git a/src/main/java/com/project/likelion13thbe/domain/review/controller/ReviewController.java b/src/main/java/com/project/likelion13thbe/domain/review/controller/ReviewController.java index 1a37798f..2c90a4a0 100644 --- a/src/main/java/com/project/likelion13thbe/domain/review/controller/ReviewController.java +++ b/src/main/java/com/project/likelion13thbe/domain/review/controller/ReviewController.java @@ -1,9 +1,13 @@ package com.project.likelion13thbe.domain.review.controller; +import com.project.likelion13thbe.domain.member.entity.Member; +import com.project.likelion13thbe.domain.member.repository.MemberRepository; import com.project.likelion13thbe.domain.order.dto.request.OrderReqDTO; import com.project.likelion13thbe.domain.review.dto.request.ReviewReqDTO; import com.project.likelion13thbe.domain.review.dto.response.ReviewResDTO; import com.project.likelion13thbe.domain.review.service.command.ReviewCommandService; +import com.project.likelion13thbe.global.apiPayload.code.GeneralErrorCode; +import com.project.likelion13thbe.global.apiPayload.exception.CustomException; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -19,6 +23,7 @@ @Tag(name="Review", description="Review 관련 API입니다.") public class ReviewController { private final ReviewCommandService reviewCommandService; + private final MemberRepository memberRepository; @PostMapping public ResponseEntity createReview( @@ -51,8 +56,11 @@ public ResponseEntity updateMyReview( @PathVariable Long id, @RequestBody ReviewReqDTO.ReviewUpdateReqDTO dto ) { - String username = userDetails.getUsername(); - reviewCommandService.updateReviewByUsernameAndId(username, id, dto); + String username = userDetails.getUsername(); + Member member = memberRepository.findByEmail(username) + .orElseThrow(() -> new CustomException(GeneralErrorCode.UNAUTHORIZED_401)); + Long memberId = member.getId(); + reviewCommandService.updateReviewByMemberIdAndId(memberId, id, dto); return ResponseEntity.ok("Review Updated Successfully."); } @@ -61,7 +69,10 @@ public ResponseEntity deleteMyReview( @AuthenticationPrincipal UserDetails userDetails ) { String username = userDetails.getUsername(); - reviewCommandService.deleteReviewByUsername(username); + Member member = memberRepository.findByEmail(username) + .orElseThrow(() -> new CustomException(GeneralErrorCode.UNAUTHORIZED_401)); + Long memberId = member.getId(); + reviewCommandService.deleteReviewByMemberId(memberId); return ResponseEntity.ok("Review Deleted Successfully."); } } diff --git a/src/main/java/com/project/likelion13thbe/domain/review/entity/Like.java b/src/main/java/com/project/likelion13thbe/domain/review/entity/ReviewLike.java similarity index 93% rename from src/main/java/com/project/likelion13thbe/domain/review/entity/Like.java rename to src/main/java/com/project/likelion13thbe/domain/review/entity/ReviewLike.java index 0d5df1da..c5fe6f37 100644 --- a/src/main/java/com/project/likelion13thbe/domain/review/entity/Like.java +++ b/src/main/java/com/project/likelion13thbe/domain/review/entity/ReviewLike.java @@ -13,7 +13,7 @@ @Builder @NoArgsConstructor @AllArgsConstructor -public class Like extends BaseEntity { +public class ReviewLike extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/com/project/likelion13thbe/domain/review/repository/ReviewRepository.java b/src/main/java/com/project/likelion13thbe/domain/review/repository/ReviewRepository.java index 3ca1f81e..cf8c03ce 100644 --- a/src/main/java/com/project/likelion13thbe/domain/review/repository/ReviewRepository.java +++ b/src/main/java/com/project/likelion13thbe/domain/review/repository/ReviewRepository.java @@ -8,7 +8,7 @@ public interface ReviewRepository extends JpaRepository { Optional findById(long id); - Optional findByUsername(String username); - Optional findByIdAndUsername(long id, String username); + Optional findByMemberId(Long memberId); + Optional findByIdAndMemberId(long id, Long memberId); } diff --git a/src/main/java/com/project/likelion13thbe/domain/review/service/command/ReviewCommandService.java b/src/main/java/com/project/likelion13thbe/domain/review/service/command/ReviewCommandService.java index f82f79b7..51147b90 100644 --- a/src/main/java/com/project/likelion13thbe/domain/review/service/command/ReviewCommandService.java +++ b/src/main/java/com/project/likelion13thbe/domain/review/service/command/ReviewCommandService.java @@ -7,4 +7,6 @@ public interface ReviewCommandService { ReviewResDTO.ReviewCreateResDTO createReview(ReviewReqDTO.ReviewCreateReqDTO dto); void deleteReview(Long id); void updateReview(Long id, ReviewReqDTO.ReviewUpdateReqDTO dto); + void updateReviewByMemberIdAndId(Long memberId, Long id, ReviewReqDTO.ReviewUpdateReqDTO dto); + void deleteReviewByMemberId(Long memberId); } diff --git a/src/main/java/com/project/likelion13thbe/domain/review/service/command/ReviewCommandServiceImpl.java b/src/main/java/com/project/likelion13thbe/domain/review/service/command/ReviewCommandServiceImpl.java index a59a3a66..e473ad39 100644 --- a/src/main/java/com/project/likelion13thbe/domain/review/service/command/ReviewCommandServiceImpl.java +++ b/src/main/java/com/project/likelion13thbe/domain/review/service/command/ReviewCommandServiceImpl.java @@ -69,8 +69,8 @@ public void deleteReview(Long id) { } @Transactional - public void updateReviewByUsernameAndId(String username, Long id, ReviewReqDTO.ReviewUpdateReqDTO dto) { - Review review = reviewRepository.findByIdAndUsername(id, username) + public void updateReviewByMemberIdAndId(Long memberId, Long id, ReviewReqDTO.ReviewUpdateReqDTO dto) { + Review review = reviewRepository.findByIdAndMemberId(id, memberId) .orElseThrow(() -> new EntityNotFoundException("Review not found")); if (dto.getTitle() != null) { @@ -94,8 +94,8 @@ public void updateReviewByUsernameAndId(String username, Long id, ReviewReqDTO.R reviewRepository.save(review); } - public void deleteReviewByUsername(String username) { - Review review = reviewRepository.findByUsername(username) + public void deleteReviewByMemberId(Long memberId) { + Review review = reviewRepository.findByMemberId(memberId) .orElseThrow(() -> new EntityNotFoundException("Review not found")); reviewRepository.delete(review); } diff --git a/src/main/java/com/project/likelion13thbe/global/config/SecurityConfig.java b/src/main/java/com/project/likelion13thbe/global/config/SecurityConfig.java index b1cccf92..6065e115 100644 --- a/src/main/java/com/project/likelion13thbe/global/config/SecurityConfig.java +++ b/src/main/java/com/project/likelion13thbe/global/config/SecurityConfig.java @@ -36,19 +36,19 @@ public class SecurityConfig { "api/usage", "/swagger-ui/**", // swagger 관련 URL "/v3/api-docs/**", + "/api/v1/members/**", + "/api/v1/openapi/**" + }; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ - CustomLoginFilter loginFilter = new CustomLoginFilter(authenticationManager(authenticationConfiguration), jwtUtil); - loginFilter.setFilterProcessesUrl("/api/v1/login"); http .authorizeHttpRequests(request -> request .requestMatchers(allowUrl).permitAll() .anyRequest().authenticated()) .addFilterBefore(new JwtAuthorizationFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class) - .addFilterAt(loginFilter, UsernamePasswordAuthenticationFilter.class) .formLogin(AbstractHttpConfigurer::disable) .httpBasic(HttpBasicConfigurer::disable) .csrf(AbstractHttpConfigurer::disable) diff --git a/src/main/java/com/project/likelion13thbe/global/oauth/controller/OauthController.java b/src/main/java/com/project/likelion13thbe/global/oauth/controller/OauthController.java new file mode 100644 index 00000000..0853efa3 --- /dev/null +++ b/src/main/java/com/project/likelion13thbe/global/oauth/controller/OauthController.java @@ -0,0 +1,67 @@ +//package com.project.likelion13thbe.global.oauth.controller; +// +//import com.project.likelion13thbe.domain.member.entity.Member; +//import com.project.likelion13thbe.domain.member.repository.MemberRepository; +//import com.project.likelion13thbe.global.apiPayload.CustomResponse; +//import com.project.likelion13thbe.global.oauth.dto.KakaoUserInfoResponseDTO; +//import com.project.likelion13thbe.global.oauth.service.KakaoService; +//import com.project.likelion13thbe.global.security.CustomUserDetails; +//import com.project.likelion13thbe.global.security.JwtUtil; +//import com.project.likelion13thbe.global.token.dto.TokenDTO; +//import io.jsonwebtoken.Jwts; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.web.bind.annotation.GetMapping; +//import org.springframework.web.bind.annotation.RequestMapping; +//import org.springframework.web.bind.annotation.RequestParam; +//import org.springframework.web.bind.annotation.RestController; +// +//@Slf4j +//@RestController +//@RequiredArgsConstructor +//@RequestMapping +//public class OauthController { +// +// private final KakaoService kakaoService; +// private final MemberRepository memberRepository; +// private final JwtUtil jwtUtil; +// +// @GetMapping("/callback/kakao") +// public CustomResponse callback(@RequestParam("code") String code) { +// +// // 1. 카카오 인증서버에서 토큰을 발급받는다. +// // 인가code와 Redirect URL을 파라미터로 전달하여 카카오 인증서버에 요청. +// String accessToken = kakaoService.getAccessTokenFromKakao(code); +// +// +// // 2. 1번에서 받은 토큰으로 카카오 리소스 서버에 사용자 정보 요청. +// KakaoUserInfoResponseDTO userInfo = kakaoService.getUserInfo(accessToken); +// +// // 3. 회원가입 & 로그인 처리 +// // 여기에 서버 사용자 로그인(인증) 또는 회원가입 로직 추가 +// String email = userInfo.getKakaoAccount().email; +// +// Member member = memberRepository.findByEmail(email) +// .orElseGet(() -> { +// Member newMember = Member.builder() +// .email(email) +// .nickname(userInfo.getKakaoAccount().getName()) +// .password("kakao_oauth") +// .build(); +// return memberRepository.save(newMember); +// }); +// +// CustomUserDetails userDetails = new CustomUserDetails(email, null, "USER"); +// +// String jwtAccessToken = jwtUtil.createJwtAccessToken(userDetails); +// String jwtRefreshToken = jwtUtil.createJwtRefreshToken(userDetails); +// +// TokenDTO tokenDTO = TokenDTO.builder() +// .accessToken(jwtAccessToken) +// .refreshToken(jwtRefreshToken) +// .build(); +// +// return CustomResponse.onSuccess(null); +// } +//} +// diff --git a/src/main/java/com/project/likelion13thbe/global/oauth/dto/KakaoTokenResponseDTO.java b/src/main/java/com/project/likelion13thbe/global/oauth/dto/KakaoTokenResponseDTO.java new file mode 100644 index 00000000..30b15076 --- /dev/null +++ b/src/main/java/com/project/likelion13thbe/global/oauth/dto/KakaoTokenResponseDTO.java @@ -0,0 +1,28 @@ +//package com.project.likelion13thbe.global.oauth.dto; +// +//import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +//import com.fasterxml.jackson.annotation.JsonProperty; +//import lombok.Getter; +//import lombok.NoArgsConstructor; +// +//@Getter +//@NoArgsConstructor //역직렬화를 위한 기본 생성자 +//@JsonIgnoreProperties(ignoreUnknown = true) +//public class KakaoTokenResponseDTO { +// +// @JsonProperty("token_type") +// public String tokenType; +// @JsonProperty("access_token") +// public String accessToken; +// @JsonProperty("id_token") +// public String idToken; +// @JsonProperty("expires_in") +// public Integer expiresIn; +// @JsonProperty("refresh_token") +// public String refreshToken; +// @JsonProperty("refresh_token_expires_in") +// public Integer refreshTokenExpiresIn; +// @JsonProperty("scope") +// public String scope; +//} +// diff --git a/src/main/java/com/project/likelion13thbe/global/oauth/dto/KakaoUserInfoResponseDTO.java b/src/main/java/com/project/likelion13thbe/global/oauth/dto/KakaoUserInfoResponseDTO.java new file mode 100644 index 00000000..f10fb9f2 --- /dev/null +++ b/src/main/java/com/project/likelion13thbe/global/oauth/dto/KakaoUserInfoResponseDTO.java @@ -0,0 +1,191 @@ +//package com.project.likelion13thbe.global.oauth.dto; +// +// +//import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +//import com.fasterxml.jackson.annotation.JsonProperty; +//import lombok.Getter; +//import lombok.NoArgsConstructor; +// +//import java.util.Date; +//import java.util.HashMap; +// +//@Getter +//@NoArgsConstructor //역직렬화를 위한 기본 생성자 +//@JsonIgnoreProperties(ignoreUnknown = true) +//public class KakaoUserInfoResponseDTO { +// +// //회원 번호 +// @JsonProperty("id") +// public Long id; +// +// //자동 연결 설정을 비활성화한 경우만 존재. +// //true : 연결 상태, false : 연결 대기 상태 +// @JsonProperty("has_signed_up") +// public Boolean hasSignedUp; +// +// //서비스에 연결 완료된 시각. UTC +// @JsonProperty("connected_at") +// public Date connectedAt; +// +// //카카오싱크 간편가입을 통해 로그인한 시각. UTC +// @JsonProperty("synched_at") +// public Date synchedAt; +// +// //사용자 프로퍼티 +// @JsonProperty("properties") +// public HashMap properties; +// +// //카카오 계정 정보 +// @JsonProperty("kakao_account") +// public KakaoAccount kakaoAccount; +// +// //uuid 등 추가 정보 +// @JsonProperty("for_partner") +// public Partner partner; +// +// @Getter +// @NoArgsConstructor +// @JsonIgnoreProperties(ignoreUnknown = true) +// public class KakaoAccount { +// +// //프로필 정보 제공 동의 여부 +// @JsonProperty("profile_needs_agreement") +// public Boolean isProfileAgree; +// +// //닉네임 제공 동의 여부 +// @JsonProperty("profile_nickname_needs_agreement") +// public Boolean isNickNameAgree; +// +// //프로필 사진 제공 동의 여부 +// @JsonProperty("profile_image_needs_agreement") +// public Boolean isProfileImageAgree; +// +// //사용자 프로필 정보 +// @JsonProperty("profile") +// public Profile profile; +// +// //이름 제공 동의 여부 +// @JsonProperty("name_needs_agreement") +// public Boolean isNameAgree; +// +// //카카오계정 이름 +// @JsonProperty("name") +// public String name; +// +// //이메일 제공 동의 여부 +// @JsonProperty("email_needs_agreement") +// public Boolean isEmailAgree; +// +// //이메일이 유효 여부 +// // true : 유효한 이메일, false : 이메일이 다른 카카오 계정에 사용돼 만료 +// @JsonProperty("is_email_valid") +// public Boolean isEmailValid; +// +// //이메일이 인증 여부 +// //true : 인증된 이메일, false : 인증되지 않은 이메일 +// @JsonProperty("is_email_verified") +// public Boolean isEmailVerified; +// +// //카카오계정 대표 이메일 +// @JsonProperty("email") +// public String email; +// +// //연령대 제공 동의 여부 +// @JsonProperty("age_range_needs_agreement") +// public Boolean isAgeAgree; +// +// //연령대 +// //참고 https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info +// @JsonProperty("age_range") +// public String ageRange; +// +// //출생 연도 제공 동의 여부 +// @JsonProperty("birthyear_needs_agreement") +// public Boolean isBirthYearAgree; +// +// //출생 연도 (YYYY 형식) +// @JsonProperty("birthyear") +// public String birthYear; +// +// //생일 제공 동의 여부 +// @JsonProperty("birthday_needs_agreement") +// public Boolean isBirthDayAgree; +// +// //생일 (MMDD 형식) +// @JsonProperty("birthday") +// public String birthDay; +// +// //생일 타입 +// // SOLAR(양력) 혹은 LUNAR(음력) +// @JsonProperty("birthday_type") +// public String birthDayType; +// +// //성별 제공 동의 여부 +// @JsonProperty("gender_needs_agreement") +// public Boolean isGenderAgree; +// +// //성별 +// @JsonProperty("gender") +// public String gender; +// +// //전화번호 제공 동의 여부 +// @JsonProperty("phone_number_needs_agreement") +// public Boolean isPhoneNumberAgree; +// +// //전화번호 +// //국내 번호인 경우 +82 00-0000-0000 형식 +// @JsonProperty("phone_number") +// public String phoneNumber; +// +// //CI 동의 여부 +// @JsonProperty("ci_needs_agreement") +// public Boolean isCIAgree; +// +// //CI, 연계 정보 +// @JsonProperty("ci") +// public String ci; +// +// //CI 발급 시각, UTC +// @JsonProperty("ci_authenticated_at") +// public Date ciCreatedAt; +// +// @Getter +// @NoArgsConstructor +// @JsonIgnoreProperties(ignoreUnknown = true) +// public class Profile { +// +// //닉네임 +// @JsonProperty("nickname") +// public String nickName; +// +// //프로필 미리보기 이미지 URL +// @JsonProperty("thumbnail_image_url") +// public String thumbnailImageUrl; +// +// //프로필 사진 URL +// @JsonProperty("profile_image_url") +// public String profileImageUrl; +// +// //프로필 사진 URL 기본 프로필인지 여부 +// //true : 기본 프로필, false : 사용자 등록 +// @JsonProperty("is_default_image") +// public String isDefaultImage; +// +// //닉네임이 기본 닉네임인지 여부 +// //true : 기본 닉네임, false : 사용자 등록 +// @JsonProperty("is_default_nickname") +// public Boolean isDefaultNickName; +// +// } +// } +// +// @Getter +// @NoArgsConstructor +// @JsonIgnoreProperties(ignoreUnknown = true) +// public class Partner { +// //고유 ID +// @JsonProperty("uuid") +// public String uuid; +// } +// +//} diff --git a/src/main/java/com/project/likelion13thbe/global/oauth/service/KakaoService.java b/src/main/java/com/project/likelion13thbe/global/oauth/service/KakaoService.java new file mode 100644 index 00000000..1194ff9a --- /dev/null +++ b/src/main/java/com/project/likelion13thbe/global/oauth/service/KakaoService.java @@ -0,0 +1,96 @@ +//package com.project.likelion13thbe.global.oauth.service; +// +// +//import com.project.likelion13thbe.domain.member.repository.MemberRepository; +//import com.project.likelion13thbe.global.oauth.dto.KakaoTokenResponseDTO; +//import com.project.likelion13thbe.global.oauth.dto.KakaoUserInfoResponseDTO; +//import com.project.likelion13thbe.global.security.JwtUtil; +//import io.netty.handler.codec.http.HttpHeaderValues; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.http.HttpHeaders; +//import org.springframework.http.HttpStatusCode; +//import org.springframework.stereotype.Service; +//import org.springframework.web.reactive.function.client.WebClient; +//import reactor.core.publisher.Mono; +// +//@Slf4j +//@Service +//public class KakaoService { +// +// +// private final String clientId; // API Key +// private final String tokenURI; // 카카오 인증 서버 +// private final String userInfoURI; // 카카오 리소스 서버 +// private final String redirectURI; // redirect URI +// private final MemberRepository memberRepository; +// private final JwtUtil jwtUtil; +// +// @Autowired +// public KakaoService(@Value("${spring.security.oauth2.client.registration.kakao.client-id}") String clientId, +// @Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}") String redirectURI, +// @Value("${spring.security.oauth2.client.provider.kakao.user-info-uri}") String userInfoURI, +// @Value("${spring.security.oauth2.client.provider.kakao.token-uri}") String tokenURI, MemberRepository memberRepository, JwtUtil jwtUtil) { +// this.clientId = clientId; +// this.tokenURI = tokenURI; +// this.userInfoURI = userInfoURI; +// this.redirectURI = redirectURI; +// this.memberRepository = memberRepository; +// this.jwtUtil = jwtUtil; +// } +// +// public String getAccessTokenFromKakao(String code) { +// +// KakaoTokenResponseDTO kakaoTokenResponseDto = WebClient.create(tokenURI) +// .post() +// .uri(uriBuilder -> uriBuilder +// .scheme("https") +// .queryParam("grant_type", "authorization_code") +// .queryParam("client_id", clientId) +// .queryParam("redirect_uri", redirectURI) +// .queryParam("code", code) +// .build(true)) +// .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString()) +// .retrieve() +// //TODO : Custom Exception +// .onStatus(HttpStatusCode::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("Invalid Parameter"))) +// .onStatus(HttpStatusCode::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("Internal Server Error"))) +// .bodyToMono(KakaoTokenResponseDTO.class) +// .block(); +// +// +// log.info(" [Kakao Service] Access Token ------> {}", kakaoTokenResponseDto.getAccessToken()); +// log.info(" [Kakao Service] Refresh Token ------> {}", kakaoTokenResponseDto.getRefreshToken()); +// //제공 조건: OpenID Connect가 활성화 된 앱의 토큰 발급 요청인 경우 또는 scope에 openid를 포함한 추가 항목 동의 받기 요청을 거친 토큰 발급 요청인 경우 +// log.info(" [Kakao Service] Id Token ------> {}", kakaoTokenResponseDto.getIdToken()); +// log.info(" [Kakao Service] Scope ------> {}", kakaoTokenResponseDto.getScope()); +// +// return kakaoTokenResponseDto.getAccessToken(); +// } +// +// // 이메일을 통해서 만약 이메일이 없으면 회원가입 .. 있으면 로그인 +// public KakaoUserInfoResponseDTO getUserInfo(String accessToken) { +// +// KakaoUserInfoResponseDTO userInfo = WebClient.create(userInfoURI) +// .get() +// .uri(uriBuilder -> uriBuilder +// .scheme("https") +// .build(true)) +// .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) // access token 인가 +// .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString()) +// .retrieve() +// //TODO : Custom Exception +// .onStatus(HttpStatusCode::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("Invalid Parameter"))) +// .onStatus(HttpStatusCode::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("Internal Server Error"))) +// .bodyToMono(KakaoUserInfoResponseDTO.class) +// .block(); +// +// log.info("[ Kakao Service ] Auth ID ---> {} ", userInfo.getId()); +// log.info("[ Kakao Service ] NickName ---> {} ", userInfo.getKakaoAccount().getProfile().getNickName()); +// log.info("[ Kakao Service ] ProfileImageUrl ---> {} ", userInfo.getKakaoAccount().getProfile().getProfileImageUrl()); +// +// return userInfo; +// } +// +//} diff --git a/src/main/java/com/project/likelion13thbe/global/security/AuthController.java b/src/main/java/com/project/likelion13thbe/global/security/AuthController.java index 16f18e3d..098a6ada 100644 --- a/src/main/java/com/project/likelion13thbe/global/security/AuthController.java +++ b/src/main/java/com/project/likelion13thbe/global/security/AuthController.java @@ -1,16 +1,23 @@ package com.project.likelion13thbe.global.security; +import com.project.likelion13thbe.domain.member.dto.request.MemberReqDTO; import com.project.likelion13thbe.global.apiPayload.CustomResponse; +import com.project.likelion13thbe.global.token.dto.JwtResDTO; +import com.project.likelion13thbe.global.token.dto.LoginReqDTO; import com.project.likelion13thbe.global.token.dto.TokenDTO; +import com.project.likelion13thbe.global.token.entity.Token; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.security.auth.message.AuthException; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; @Slf4j @RestController @@ -20,6 +27,8 @@ public class AuthController { private final AuthService authService; + private final AuthenticationManager authenticationManager; + private final JwtUtil jwtUtil; //토큰 재발급 API @Operation(method = "POST", summary = "토큰 재발급", description = "토큰 재발급. accessToken과 refreshToken을 body에 담아서 전송합니다.") @@ -30,4 +39,19 @@ public CustomResponse reissue(@RequestBody TokenDTO jwtDto) throws AuthExcept return CustomResponse.onSuccess(authService.reissueToken(jwtDto)); } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginReqDTO dto) { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(dto.getEmail(), dto.getPassword()); + Authentication authentication = authenticationManager.authenticate(authenticationToken); + + CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); + + String accessToken = jwtUtil.createJwtAccessToken(userDetails); + String refreshToken = jwtUtil.createJwtRefreshToken(userDetails); + + return ResponseEntity.ok(new TokenDTO(accessToken, refreshToken)); + + } + } diff --git a/src/main/java/com/project/likelion13thbe/global/security/JwtUtil.java b/src/main/java/com/project/likelion13thbe/global/security/JwtUtil.java index 39a05f69..5df05254 100644 --- a/src/main/java/com/project/likelion13thbe/global/security/JwtUtil.java +++ b/src/main/java/com/project/likelion13thbe/global/security/JwtUtil.java @@ -11,12 +11,14 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.time.Instant; import java.util.Date; import java.util.stream.Collectors; @@ -28,20 +30,20 @@ public class JwtUtil { private final SecretKey secretKey; private final Long accessExpMs; private final Long refreshExpMs; - private final TokenRepository tokenRepository; + private final RedisTemplate redisTemplate; public JwtUtil( - @Value("${spring.jwt.secret}") String secret, + @Value("ThisIsASecretKeyForJwtSigningAndMustBeLongEnough123") String secret, @Value("${spring.jwt.token.access-expiration-time}") Long access, @Value("${spring.jwt.token.refresh-expiration-time}") Long refresh, - TokenRepository tokenRepo + RedisTemplate redisTemplate ) { secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm()); - accessExpMs = access; - refreshExpMs = refresh; - tokenRepository = tokenRepo; + this.accessExpMs = access; + this.redisTemplate = redisTemplate; + this.refreshExpMs = refresh; } // JWT 토큰을 입력으로 받아 토큰의 subject 로부터 사용자 Email 추출하는 메서드 @@ -101,11 +103,10 @@ public String createJwtRefreshToken(CustomUserDetails customUserDetails) { Instant expiration = Instant.now().plusMillis(refreshExpMs); String refreshToken = tokenProvider(customUserDetails, expiration); - // DB에 Refresh Token 저장 - tokenRepository.save(Token.builder() - .email(customUserDetails.getUsername()) - .token(refreshToken) - .build() + redisTemplate.opsForValue().set( + customUserDetails.getUsername(), // email + refreshToken, + Duration.ofMillis(refreshExpMs) ); @@ -115,6 +116,12 @@ public String createJwtRefreshToken(CustomUserDetails customUserDetails) { // 제공된 리프레시 토큰을 기반으로 JwtDto 쌍을 다시 발급 public TokenDTO reissueToken(String refreshToken) throws SignatureException { + String email = getEmail(refreshToken); + String savedRefreshToken = redisTemplate.opsForValue().get(email); + if(savedRefreshToken != null || !savedRefreshToken.equals(refreshToken)) { + throw new SecurityException("Unauthorized."); + } + // refreshToken 에서 user 정보를 가져와서 새로운 토큰을 발급 (발급 시간, 유효 시간(reset)만 새로 적용) CustomUserDetails userDetails = new CustomUserDetails( getEmail(refreshToken), diff --git a/src/main/java/com/project/likelion13thbe/global/token/controller/TokenController.java b/src/main/java/com/project/likelion13thbe/global/token/controller/TokenController.java new file mode 100644 index 00000000..f309a662 --- /dev/null +++ b/src/main/java/com/project/likelion13thbe/global/token/controller/TokenController.java @@ -0,0 +1,4 @@ +package com.project.likelion13thbe.global.token.controller; + +public class TokenController { +} diff --git a/src/main/java/com/project/likelion13thbe/global/token/dto/JwtResDTO.java b/src/main/java/com/project/likelion13thbe/global/token/dto/JwtResDTO.java new file mode 100644 index 00000000..aa8b4078 --- /dev/null +++ b/src/main/java/com/project/likelion13thbe/global/token/dto/JwtResDTO.java @@ -0,0 +1,9 @@ +package com.project.likelion13thbe.global.token.dto; + +public class JwtResDTO { + private String token; + + public JwtResDTO(String token) { + this.token = token; + } +} diff --git a/src/main/java/com/project/likelion13thbe/global/token/dto/TokenReqDTO.java b/src/main/java/com/project/likelion13thbe/global/token/dto/LoginReqDTO.java similarity index 53% rename from src/main/java/com/project/likelion13thbe/global/token/dto/TokenReqDTO.java rename to src/main/java/com/project/likelion13thbe/global/token/dto/LoginReqDTO.java index c65d4b6f..df3417bf 100644 --- a/src/main/java/com/project/likelion13thbe/global/token/dto/TokenReqDTO.java +++ b/src/main/java/com/project/likelion13thbe/global/token/dto/LoginReqDTO.java @@ -4,7 +4,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; -public class TokenReqDTO { - - +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class LoginReqDTO { + private String email; + private String password; } diff --git a/src/main/java/com/project/likelion13thbe/global/token/entity/Token.java b/src/main/java/com/project/likelion13thbe/global/token/entity/Token.java index ed7f19c3..bff6709b 100644 --- a/src/main/java/com/project/likelion13thbe/global/token/entity/Token.java +++ b/src/main/java/com/project/likelion13thbe/global/token/entity/Token.java @@ -1,21 +1,21 @@ -package com.project.likelion13thbe.global.token.entity; + package com.project.likelion13thbe.global.token.entity; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; + import jakarta.persistence.Entity; + import jakarta.persistence.Id; + import jakarta.persistence.Table; + import lombok.AllArgsConstructor; + import lombok.Builder; + import lombok.Getter; + import lombok.NoArgsConstructor; -@Entity -@Getter -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Table(name="token") -public class Token { - @Id - private String email; - private String token; -} + @Entity + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + @Table(name="token") + public class Token { + @Id + private String email; + private String token; + }