From 3d2d014244da2619ec3a7bf06eeb2f5d1d12f3a5 Mon Sep 17 00:00:00 2001 From: Kohseoyoung Date: Thu, 28 May 2026 23:03:05 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=208=EC=A3=BC=EC=B0=A8=20SecurityConfig?= =?UTF-8?q?=20=EC=88=98=EC=A0=95,=20=EB=AF=B8=EC=85=98=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 61 +++++----- .../member/converter/MemberConverter.java | 80 ++++++------- .../umc/domain/member/dto/MemberReqDTO.java | 54 +++++++-- .../umc/domain/member/dto/MemberResDTO.java | 30 +++-- .../java/umc/domain/member/entity/Member.java | 10 +- .../umc/domain/member/entity/MemberFood.java | 31 ++++- .../domain/member/entity/MemberMission.java | 3 +- .../umc/domain/member/entity/MemberTerm.java | 41 ++++++- .../enums/{Org_cd.java => Social_Type.java} | 4 +- .../exception/code/MemberErrorCode.java | 7 +- .../exception/code/MemberSuccessCode.java | 8 +- .../repository/MemberFoodRepository.java | 4 + .../repository/MemberMissionRepository.java | 3 +- .../member/repository/MemberRepository.java | 1 + .../repository/MemberTermRepository.java | 4 + .../domain/member/service/MemberService.java | 110 +++++++----------- .../mission/controller/MissionController.java | 26 ++--- .../mission/converter/MissionConverter.java | 26 +++-- .../umc/domain/mission/dto/MissionReqDTO.java | 9 +- .../umc/domain/mission/dto/MissionResDTO.java | 14 ++- .../mission/service/MissionService.java | 29 ++--- .../store/controller/StoreController.java | 56 +++++---- .../store/converter/StoreConverter.java | 55 +++++++-- .../umc/domain/store/dto/StoreReqDTO.java | 30 +++-- .../umc/domain/store/dto/StoreResDTO.java | 29 +++-- .../domain/store/enums/ReviewSuccessCode.java | 17 --- .../domain/store/enums/StoreSuccessCode.java | 17 --- .../exception/code/StoreSuccessCode.java | 10 +- .../domain/store/service/StoreService.java | 54 ++++----- .../umc/global/config/SecurityConfig.java | 25 +++- .../security/handler/CustomAccessDenied.java | 37 ++++++ .../security/handler/CustomEntryPoint.java | 37 ++++++ 32 files changed, 575 insertions(+), 347 deletions(-) rename src/main/java/umc/domain/member/enums/{Org_cd.java => Social_Type.java} (62%) create mode 100644 src/main/java/umc/domain/member/repository/MemberFoodRepository.java create mode 100644 src/main/java/umc/domain/member/repository/MemberTermRepository.java delete mode 100644 src/main/java/umc/domain/store/enums/ReviewSuccessCode.java delete mode 100644 src/main/java/umc/domain/store/enums/StoreSuccessCode.java create mode 100644 src/main/java/umc/global/security/handler/CustomAccessDenied.java create mode 100644 src/main/java/umc/global/security/handler/CustomEntryPoint.java diff --git a/src/main/java/umc/domain/member/controller/MemberController.java b/src/main/java/umc/domain/member/controller/MemberController.java index 82dd2dc7..c1f495e7 100644 --- a/src/main/java/umc/domain/member/controller/MemberController.java +++ b/src/main/java/umc/domain/member/controller/MemberController.java @@ -1,11 +1,13 @@ package umc.domain.member.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import umc.domain.member.dto.MemberReqDTO; import umc.domain.member.dto.MemberResDTO; import umc.domain.member.exception.code.MemberSuccessCode; import umc.domain.member.service.MemberService; +import umc.domain.mission.dto.MissionResDTO; import umc.global.apiPayload.ApiResponse; import umc.global.apiPayload.code.BaseSuccessCode; @@ -13,52 +15,47 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api") +@RequestMapping("/api/members") public class MemberController { private final MemberService memberService; - // 멤버 생성 - @PostMapping("/v1/member/me") - public ApiResponse createMember( - @RequestBody MemberReqDTO.CreateMember dto // ✅ @PathVariable → @RequestBody + // Member 조회 + @PostMapping("/me") + public ApiResponse getMember( + @RequestBody MemberReqDTO.GetMemberDTO dto ) { - BaseSuccessCode code = MemberSuccessCode.CREATED; - return ApiResponse.onSuccess(code, memberService.createMember(dto)); + return ApiResponse.onSuccess(MemberSuccessCode.MEMBER_OK, + memberService.getMember(dto.id())); } - // 멤버 조회 - @GetMapping("/v1/member/me") // ✅ @PostMapping → @GetMapping - public ApiResponse> getMembers( - @RequestParam Integer pageSize, // ✅ @PathVariable → @RequestParam - @RequestParam Integer pageNumber, - @RequestParam(required = false) String sort - ){ - BaseSuccessCode code = MemberSuccessCode.OK; // ✅ MissionSuccessCode → MemberSuccessCode - return ApiResponse.onSuccess(code, memberService.getMembers(pageSize, pageNumber, sort)); + // 회원가입 + @PostMapping("/signup") + public ApiResponse signUp( + @RequestBody @Valid MemberReqDTO.SignUpDTO requestDto + ) { + return ApiResponse.onSuccess(MemberSuccessCode.MEMBER_CREATED, memberService.signUp(requestDto)); } - // 멤버 미션 생성 - @PostMapping("/v1/member/missions") - public ApiResponse createMemberMissions( - @RequestParam Long memberId, // ✅ 추가 - @RequestParam Long missionId, - @RequestBody MemberReqDTO.CreateMemberMission dto // ✅ @PathVariable → @RequestBody + // 내 미션 생성 + @PostMapping("/me/missions") + public ApiResponse createMyMission( + @RequestParam Long memberId, + @RequestParam Long missionId ) { - BaseSuccessCode code = MemberSuccessCode.MISSIONCREATED; - return ApiResponse.onSuccess(code, memberService.createMemberMission(memberId, missionId, dto)); + return ApiResponse.onSuccess(MemberSuccessCode.MISSION_CREATED, + memberService.createMyMission(memberId, missionId)); // ← 메서드명 수정 } - // 멤버 미션 조회 - @GetMapping("/v1/member/missions") // ✅ @PostMapping → @GetMapping - public ApiResponse> getMemberMissions( - @RequestParam Long memberId, // ✅ 추가 - @RequestParam Long missionId, - @RequestParam Integer pageSize, // ✅ @PathVariable → @RequestParam + // 내 미션 조회 + @GetMapping("/me/missions") + public ApiResponse> getMyMissions( + @RequestParam Long memberId, + @RequestParam Integer pageSize, // ← missionId 제거 @RequestParam Integer pageNumber, @RequestParam(required = false) String sort ){ - BaseSuccessCode code = MemberSuccessCode.MISSIONOK; // ✅ MissionSuccessCode → MemberSuccessCode - return ApiResponse.onSuccess(code, memberService.getMemberMissions(memberId, missionId, pageSize, pageNumber, sort)); + return ApiResponse.onSuccess(MemberSuccessCode.MEMBER_MISSION_OK, + memberService.getMyMissions(memberId, pageSize, pageNumber, sort)); // ← 메서드명 수정 } } \ No newline at end of file diff --git a/src/main/java/umc/domain/member/converter/MemberConverter.java b/src/main/java/umc/domain/member/converter/MemberConverter.java index 4e4260d5..52f8cfdd 100644 --- a/src/main/java/umc/domain/member/converter/MemberConverter.java +++ b/src/main/java/umc/domain/member/converter/MemberConverter.java @@ -4,17 +4,32 @@ import umc.domain.member.dto.MemberReqDTO; import umc.domain.member.entity.Member; import umc.domain.member.entity.MemberMission; +import umc.domain.member.enums.Social_Type; +import umc.domain.member.enums.Status; +import umc.domain.mission.dto.MissionResDTO; import umc.domain.mission.entity.Mission; public class MemberConverter { - // 멤버 생성 — 암호화된 비밀번호를 인자로 받음 - public static Member toMember( - MemberReqDTO.CreateMember dto, - String encodedPassword - ) { + // 멤버 조회 + public static MemberResDTO.GetMemberDTO toGetMember(Member member) { + return MemberResDTO.GetMemberDTO.builder() + .member_id(member.getId()) + .email(member.getEmail()) + .name(member.getName()) + .gender(member.getGender()) + .birth(member.getBirth()) + .phone(member.getPhone()) + .point(member.getPoint()) + .status(member.getStatus()) + .build(); + } + + // 회원가입 생성 — 암호화된 비밀번호를 인자로 받음 + public static Member toPutMember( + MemberReqDTO.SignUpDTO dto, String encodedPassword + ) { return Member.builder() - .log_id(dto.log_id()) .email(dto.email()) .password(encodedPassword) // ★ 암호화된 비밀번호 사용 .name(dto.name()) @@ -24,61 +39,46 @@ public static Member toMember( .add1(dto.add1()) .add2(dto.add2()) .phone(dto.phone()) - .point(dto.point()) - .status(dto.status()) - .org_cd(dto.org_cd()) + .point(0) + .status(Status.ACTIVE) + .social_provider(Social_Type.NONE) .build(); } - // 멤버 조회 - public static MemberResDTO.GetMember toGetMember( - Member member - ) { - return MemberResDTO.GetMember.builder() - .member_id(member.getId()) - .log_id(member.getLog_id()) - .email(member.getEmail()) - .password(member.getPassword()) - .name(member.getName()) - .gender(member.getGender()) - .birth(member.getBirth()) - .post(member.getPost()) - .add1(member.getAdd1()) - .add2(member.getAdd2()) - .phone(member.getPhone()) - .point(member.getPoint()) - .status(member.getStatus()) - .org_cd(member.getOrg_cd()) - .build(); - + // 회원가입 조회 + public static MemberResDTO.GetSignUpDTO toGetSignUp(Member member) { + return new MemberResDTO.GetSignUpDTO( + member.getId(), + member.getCreatedAt() + ); } // 멤버 미션 생성 - public static MemberMission toMemberMission( - Mission mission, - Member member, - MemberReqDTO.CreateMemberMission dto + public static MemberMission toPutMemberMission( + Mission mission, Member member ) { return MemberMission.builder() .mission(mission) .member(member) - .succ_yn(dto.succ_yn()) - .user_start_dt(dto.user_start_dt()) + .succ_yn("N") // 기본값 미완료 + .user_start_dt(java.time.LocalDate.now()) .build(); } // 멤버 미션 조회 - public static MemberResDTO.GetMemberMission toGetMemberMission( + public static MemberResDTO.GetMemberMissionDTO toGetMemberMission( MemberMission memberMission - ){ - return MemberResDTO.GetMemberMission.builder() + ) { + return MemberResDTO.GetMemberMissionDTO.builder() .member_id(memberMission.getMember().getId()) .mission_id(memberMission.getMission().getId()) .succ_yn(memberMission.getSucc_yn()) .user_start_dt(memberMission.getUser_start_dt()) .build(); - } + // 홈 조회, 마이페이지 조회 추가 + + } \ No newline at end of file diff --git a/src/main/java/umc/domain/member/dto/MemberReqDTO.java b/src/main/java/umc/domain/member/dto/MemberReqDTO.java index 96017374..3a6b6ee9 100644 --- a/src/main/java/umc/domain/member/dto/MemberReqDTO.java +++ b/src/main/java/umc/domain/member/dto/MemberReqDTO.java @@ -1,30 +1,60 @@ package umc.domain.member.dto; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import umc.domain.member.enums.Gender; -import umc.domain.member.enums.Org_cd; +import umc.domain.member.enums.Social_Type; import umc.domain.member.enums.Status; import java.time.LocalDate; +import java.util.List; public class MemberReqDTO { - public record CreateMember( - String log_id, + + // Member 조회 + public record GetMemberDTO( + Long id + ){} + + // 회원가입 + public record SignUpDTO( + @NotBlank @Email String email, + @NotBlank String password, + @NotBlank String name, + @NotNull Gender gender, + @NotBlank String birth, + @NotBlank String post, - String add1, - String add2, + @NotBlank + String add1, // 시, 구 + @NotBlank + String add2, // 상세주소 동, 호 + @NotBlank String phone, - Integer point, - Status status, - Org_cd org_cd - ){} - public record CreateMemberMission( - String succ_yn, - LocalDate user_start_dt + List foodId, + @NotNull + @NotEmpty + @Valid + List terms ){} + + // 약관 + public record TermDTO( + @NotNull + Long termId, + @NotNull + Boolean isAgreed + ) {} + + + } diff --git a/src/main/java/umc/domain/member/dto/MemberResDTO.java b/src/main/java/umc/domain/member/dto/MemberResDTO.java index 3fc4e63c..24434892 100644 --- a/src/main/java/umc/domain/member/dto/MemberResDTO.java +++ b/src/main/java/umc/domain/member/dto/MemberResDTO.java @@ -2,37 +2,43 @@ import lombok.Builder; import umc.domain.member.enums.Gender; -import umc.domain.member.enums.Org_cd; +import umc.domain.member.enums.Social_Type; import umc.domain.member.enums.Status; import java.time.LocalDate; +import java.time.LocalDateTime; public class MemberResDTO { + // Member 조회 @Builder - public record GetMember( + public record GetMemberDTO( Long member_id, - String log_id, String email, - String password, String name, Gender gender, String birth, - String post, - String add1, - String add2, String phone, Integer point, - Status status, - Org_cd org_cd - + Status status ){} + // 회원가입 + @Builder + public record GetSignUpDTO( + Long member_id, + LocalDateTime createdAt + ) { + } + + // 내 미션 조회 @Builder - public record GetMemberMission( + public record GetMemberMissionDTO( Long member_id, Long mission_id, String succ_yn, LocalDate user_start_dt ){} -} + + +} \ No newline at end of file diff --git a/src/main/java/umc/domain/member/entity/Member.java b/src/main/java/umc/domain/member/entity/Member.java index b5a1ba53..a9987808 100644 --- a/src/main/java/umc/domain/member/entity/Member.java +++ b/src/main/java/umc/domain/member/entity/Member.java @@ -7,7 +7,7 @@ import lombok.NoArgsConstructor; import umc.domain.common.BaseEntity; import umc.domain.member.enums.Gender; -import umc.domain.member.enums.Org_cd; +import umc.domain.member.enums.Social_Type; import umc.domain.member.enums.Status; @@ -24,7 +24,7 @@ public class Member extends BaseEntity { @Column(name = "member_id", nullable = false) private Long id; - @Column(name = "log_id", nullable = false, length = 50) + @Column(name = "log_id", nullable = true, length = 50) private String log_id; @Column(name = "email", nullable = false, length = 50) @@ -62,8 +62,10 @@ public class Member extends BaseEntity { @Enumerated(EnumType.STRING) private Status status; - @Column(name = "org_cd", nullable = false, length = 10) + @Column(name = "social_provider", nullable = false, length = 10) @Enumerated(EnumType.STRING) - private Org_cd org_cd; + private Social_Type social_provider; + @Column(name = "social_uid", length = 20) + private String social_uid; } \ No newline at end of file diff --git a/src/main/java/umc/domain/member/entity/MemberFood.java b/src/main/java/umc/domain/member/entity/MemberFood.java index 87d8f537..6724706d 100644 --- a/src/main/java/umc/domain/member/entity/MemberFood.java +++ b/src/main/java/umc/domain/member/entity/MemberFood.java @@ -1,4 +1,33 @@ package umc.domain.member.entity; -public class MemberFood { +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import umc.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name="memberFood") +public class MemberFood extends BaseEntity{ + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "prefer_id", nullable = false) + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + /* + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "food_id", nullable = false) + private Food food; + */ + } diff --git a/src/main/java/umc/domain/member/entity/MemberMission.java b/src/main/java/umc/domain/member/entity/MemberMission.java index fef57474..cca3ba26 100644 --- a/src/main/java/umc/domain/member/entity/MemberMission.java +++ b/src/main/java/umc/domain/member/entity/MemberMission.java @@ -7,7 +7,6 @@ import lombok.NoArgsConstructor; import umc.domain.common.BaseEntity; import umc.domain.mission.entity.Mission; -import umc.domain.store.entity.Store; import java.time.LocalDate; @@ -21,7 +20,7 @@ public class MemberMission extends BaseEntity{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "mebmer_mission_id", nullable = false) + @Column(name = "member_mission_id", nullable = false) private Long id; @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/umc/domain/member/entity/MemberTerm.java b/src/main/java/umc/domain/member/entity/MemberTerm.java index 2363b421..ea98f013 100644 --- a/src/main/java/umc/domain/member/entity/MemberTerm.java +++ b/src/main/java/umc/domain/member/entity/MemberTerm.java @@ -1,4 +1,41 @@ package umc.domain.member.entity; -public class MemberTerm { -} +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import umc.domain.common.BaseEntity; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name="term") +public class MemberTerm extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "term_id", nullable = false) + private Long id; + + @Column(name = "term1_yn", nullable = false, length = 1) + private Boolean term1_yn; + + @Column(name = "term2_yn", nullable = false, length = 1) + private Boolean term2_yn; + + @Column(name = "term3_yn", nullable = false, length = 1) + private Boolean term3_yn; + + @Column(name = "term4_yn", nullable = false, length = 1) + private Boolean term4_yn; + + @Column(name = "term5_yn", nullable = false, length = 1) + private Boolean term5_yn; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; +} \ No newline at end of file diff --git a/src/main/java/umc/domain/member/enums/Org_cd.java b/src/main/java/umc/domain/member/enums/Social_Type.java similarity index 62% rename from src/main/java/umc/domain/member/enums/Org_cd.java rename to src/main/java/umc/domain/member/enums/Social_Type.java index 29bdaf3c..d4602e0c 100644 --- a/src/main/java/umc/domain/member/enums/Org_cd.java +++ b/src/main/java/umc/domain/member/enums/Social_Type.java @@ -1,8 +1,8 @@ package umc.domain.member.enums; -public enum Org_cd { +public enum Social_Type { NAVER, GOOGLE, KAKAO, - APPLE + NONE, APPLE } diff --git a/src/main/java/umc/domain/member/exception/code/MemberErrorCode.java b/src/main/java/umc/domain/member/exception/code/MemberErrorCode.java index bbc2058a..877fc9f7 100644 --- a/src/main/java/umc/domain/member/exception/code/MemberErrorCode.java +++ b/src/main/java/umc/domain/member/exception/code/MemberErrorCode.java @@ -9,7 +9,12 @@ @RequiredArgsConstructor public enum MemberErrorCode implements BaseErrorCode { MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER404_1", "해당 사용자를 찾을 수 없습니다."), - MEMBER_MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBERMISSION404_1", "해당 미션을 찾을 수 없습니다."); + + MEMBER_MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBERMISSION404_1", "해당 미션을 찾을 수 없습니다."), + + DUPLICATE_EMAIL(HttpStatus.CONFLICT, "MEMBER409_1", "이미 사용 중인 이메일입니다."), + + ; private final HttpStatus status; private final String code; diff --git a/src/main/java/umc/domain/member/exception/code/MemberSuccessCode.java b/src/main/java/umc/domain/member/exception/code/MemberSuccessCode.java index 16d973c8..0def7fde 100644 --- a/src/main/java/umc/domain/member/exception/code/MemberSuccessCode.java +++ b/src/main/java/umc/domain/member/exception/code/MemberSuccessCode.java @@ -9,11 +9,11 @@ @RequiredArgsConstructor public enum MemberSuccessCode implements BaseSuccessCode { - CREATED(HttpStatus.CREATED, "MEMBER200_1", "성공적으로 유저를 생성했습니다."), - OK(HttpStatus.OK, "MEMBER200_2", "성공적으로 유저를 조회했습니다."), + MEMBER_CREATED(HttpStatus.CREATED, "MEMBER200_1", "성공적으로 유저를 생성했습니다."), + MEMBER_OK(HttpStatus.OK, "MEMBER200_2", "성공적으로 유저를 조회했습니다."), - MISSIONCREATED(HttpStatus.CREATED, "MEMBERMISSION200_1", "성공적으로 멤버미션을 생성했습니다."), - MISSIONOK(HttpStatus.OK, "MEMBERMISSION200_2", "성공적으로 멤버미션을 조회했습니다."); + MISSION_CREATED(HttpStatus.CREATED, "MEMBERMISSION200_1", "성공적으로 멤버 미션을 생성했습니다."), + MEMBER_MISSION_OK(HttpStatus.OK, "MEMBERMISSION200_2", "성공적으로 멤버미션을 조회했습니다."); private final HttpStatus status; private final String code; diff --git a/src/main/java/umc/domain/member/repository/MemberFoodRepository.java b/src/main/java/umc/domain/member/repository/MemberFoodRepository.java new file mode 100644 index 00000000..596f9d9b --- /dev/null +++ b/src/main/java/umc/domain/member/repository/MemberFoodRepository.java @@ -0,0 +1,4 @@ +package umc.domain.member.repository; + +public interface MemberFoodRepository { +} diff --git a/src/main/java/umc/domain/member/repository/MemberMissionRepository.java b/src/main/java/umc/domain/member/repository/MemberMissionRepository.java index f5a58049..f3e989fa 100644 --- a/src/main/java/umc/domain/member/repository/MemberMissionRepository.java +++ b/src/main/java/umc/domain/member/repository/MemberMissionRepository.java @@ -8,5 +8,6 @@ import umc.domain.mission.entity.Mission; public interface MemberMissionRepository extends JpaRepository { - Page findAllByMember_IdAndMission_Id(Long memberId, Long missionId, Pageable pageable); + // 내 미션 전체 조회 + Page findAllByMember_Id(Long memberId, Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/umc/domain/member/repository/MemberRepository.java b/src/main/java/umc/domain/member/repository/MemberRepository.java index 0ee33da4..5505194a 100644 --- a/src/main/java/umc/domain/member/repository/MemberRepository.java +++ b/src/main/java/umc/domain/member/repository/MemberRepository.java @@ -13,4 +13,5 @@ public interface MemberRepository extends JpaRepository { // findById는 JPA가 기본 제공! Optional findByEmail(String email); + boolean existsByEmail(String email); } \ No newline at end of file diff --git a/src/main/java/umc/domain/member/repository/MemberTermRepository.java b/src/main/java/umc/domain/member/repository/MemberTermRepository.java new file mode 100644 index 00000000..6d8c2dc7 --- /dev/null +++ b/src/main/java/umc/domain/member/repository/MemberTermRepository.java @@ -0,0 +1,4 @@ +package umc.domain.member.repository; + +public interface MemberTermRepository { +} diff --git a/src/main/java/umc/domain/member/service/MemberService.java b/src/main/java/umc/domain/member/service/MemberService.java index 30050a2d..2a5953ca 100644 --- a/src/main/java/umc/domain/member/service/MemberService.java +++ b/src/main/java/umc/domain/member/service/MemberService.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -16,10 +17,11 @@ import umc.domain.member.exception.code.MemberErrorCode; import umc.domain.member.repository.MemberMissionRepository; import umc.domain.member.repository.MemberRepository; +import umc.domain.mission.dto.MissionResDTO; import umc.domain.mission.entity.Mission; import umc.domain.mission.repository.MissionRepository; - +import javax.swing.plaf.synth.Region; import java.util.List; @Service @@ -32,87 +34,60 @@ public class MemberService { private final PasswordEncoder passwordEncoder; - // 멤버 생성 + // Member 조회 + public MemberResDTO.GetMemberDTO getMember(Long id) { + Member member = memberRepository.findById(id) + .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); + return MemberConverter.toGetMember(member); + } + + // 회원가입 @Transactional - public Void createMember( - MemberReqDTO.CreateMember dto - ){ + public MemberResDTO.GetSignUpDTO signUp(MemberReqDTO.SignUpDTO dto) { + validateEmailNotDuplicate(dto.email()); + Member member = createMember(dto); + // 선호 음식 생성 savePreferFoods(member, dto.foodIds()); + // 약관 생성 saveTermAgreements(member, dto.terms()); + return MemberConverter.toGetSignUp(member); + } + + private void validateEmailNotDuplicate(String email) { + if (memberRepository.existsByEmail(email)) { + throw new MemberException(MemberErrorCode.DUPLICATE_EMAIL); + } + } + + // 멤버 생성 + private Member createMember(MemberReqDTO.SignUpDTO dto) { // ★ 비밀번호 암호화 String encodedPassword = passwordEncoder.encode(dto.password()); - // 멤버 생성 - Member member = MemberConverter.toMember(dto, encodedPassword); - + Member member = MemberConverter.toPutMember(dto, encodedPassword); // 멤버 DB 저장 - memberRepository.save(member); - - return null; + return memberRepository.save(member); } - // 멤버 호출 - public List getMembers( - Integer pageSize, - Integer pageNumber, - String sort - ){ - // 정렬 정보 생성 - Sort sortInfo; - if(sort != null){ - if(sort.equalsIgnoreCase("asc")){ - sortInfo = Sort.by("id").ascending(); - } else if(sort.equalsIgnoreCase("desc")){ - sortInfo = Sort.by("id").descending(); - } else { - sortInfo = Sort.by(sort); // 컬럼명으로 정렬 - } - } else { - sortInfo = Sort.by("id").descending(); - } - - // 페이지 정보들을 PageRequest로 만들기 - PageRequest pageRequest = PageRequest.of(pageNumber, pageSize, sortInfo); + // 선호음식 + 약관 추가 - // 가게 내 아이디 조회 - Page memberList = memberRepository.findAll(pageRequest); - - - // 미션들 응답 DTO로 포장하기 - return memberList.map(MemberConverter::toGetMember).getContent(); - } - - // 멤버 미션 생성 + // 내 미션 생성 @Transactional - public Void createMemberMission( - Long memberId, - Long missionId, - MemberReqDTO.CreateMemberMission dto - ){ - // 멤버 찾기 + public Void createMyMission(Long memberId, Long missionId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); - - // 미션 찾기 Mission mission = missionRepository.findById(missionId) - .orElseThrow(() ->new MemberException(MemberErrorCode.MEMBER_MISSION_NOT_FOUND)); - - // 미션 생성 - MemberMission memberMission = MemberConverter.toMemberMission(mission, member, dto); - - // 미션 DB 저장 + .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_MISSION_NOT_FOUND)); + MemberMission memberMission = MemberConverter.toPutMemberMission(mission, member); memberMissionRepository.save(memberMission); - return null; } - // 멤버 미션 조회 - public List getMemberMissions( + // 내 미션 조회 + public List getMyMissions( Long memberId, - Long missionId, Integer pageSize, Integer pageNumber, String sort ){ - // 정렬 정보 생성 Sort sortInfo; if(sort != null){ if(sort.equalsIgnoreCase("asc")){ @@ -120,21 +95,14 @@ public List getMemberMissions( } else if(sort.equalsIgnoreCase("desc")){ sortInfo = Sort.by("id").descending(); } else { - sortInfo = Sort.by(sort); // 컬럼명으로 정렬 + sortInfo = Sort.by(sort); } } else { sortInfo = Sort.by("id").descending(); } - - // 페이지 정보들을 PageRequest로 만들기 PageRequest pageRequest = PageRequest.of(pageNumber, pageSize, sortInfo); - - // 멤버 미션 아이디 조회 - Page memberMissionList = memberMissionRepository.findAllByMember_IdAndMission_Id(memberId, missionId, pageRequest); - - - // 미션들 응답 DTO로 포장하기 + Page memberMissionList = memberMissionRepository.findAllByMember_Id(memberId, pageRequest); return memberMissionList.map(MemberConverter::toGetMemberMission).getContent(); } -} \ No newline at end of file +} diff --git a/src/main/java/umc/domain/mission/controller/MissionController.java b/src/main/java/umc/domain/mission/controller/MissionController.java index 78b03d1a..72588957 100644 --- a/src/main/java/umc/domain/mission/controller/MissionController.java +++ b/src/main/java/umc/domain/mission/controller/MissionController.java @@ -1,5 +1,6 @@ package umc.domain.mission.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import umc.domain.mission.dto.MissionReqDTO; @@ -7,36 +8,35 @@ import umc.domain.mission.exception.code.MissionSuccessCode; import umc.domain.mission.service.MissionService; import umc.global.apiPayload.ApiResponse; -import umc.global.apiPayload.code.BaseSuccessCode; import java.util.List; @RestController @RequiredArgsConstructor -@RequestMapping("/api") +@RequestMapping("/api/stores") public class MissionController { private final MissionService missionService; // 가게 미션 생성 - @PostMapping("/v1/stores/{storeId}/missions") - public ApiResponse createMission( + @PostMapping("/{storeId}/missions") + public ApiResponse createMission( @PathVariable Long storeId, - @RequestBody MissionReqDTO.CreateMission dto + @RequestBody @Valid MissionReqDTO.CreateMissionDTO dto ){ - BaseSuccessCode code = MissionSuccessCode.CREATED; - return ApiResponse.onSuccess(code, missionService.createMission(storeId, dto)); + return ApiResponse.onSuccess(MissionSuccessCode.CREATED, + missionService.createMission(storeId, dto)); } - // 가게 내 미션들 조회 - @GetMapping("/v1/stores/{storeId}/missions") - public ApiResponse> getMissions( + // 가게 미션 조회 + @GetMapping("/{storeId}/missions") + public ApiResponse> getMissions( @PathVariable Long storeId, @RequestParam Integer pageSize, @RequestParam Integer pageNumber, @RequestParam(required = false) String sort ){ - BaseSuccessCode code = MissionSuccessCode.OK; - return ApiResponse.onSuccess(code, missionService.getMissions(storeId, pageSize, pageNumber, sort)); + return ApiResponse.onSuccess(MissionSuccessCode.OK, + missionService.getMissions(storeId, pageSize, pageNumber, sort)); } -} +} \ No newline at end of file diff --git a/src/main/java/umc/domain/mission/converter/MissionConverter.java b/src/main/java/umc/domain/mission/converter/MissionConverter.java index 140c501b..bb5f2c06 100644 --- a/src/main/java/umc/domain/mission/converter/MissionConverter.java +++ b/src/main/java/umc/domain/mission/converter/MissionConverter.java @@ -6,9 +6,11 @@ import umc.domain.store.entity.Store; public class MissionConverter { - public static Mission toMission( + + // 미션 생성 (ReqDTO → Entity) + public static Mission toPutMission( Store store, - MissionReqDTO.CreateMission dto + MissionReqDTO.CreateMissionDTO dto ){ return Mission.builder() .store(store) @@ -19,18 +21,22 @@ public static Mission toMission( .build(); } - // 가게 내 미션 조회 - public static MissionResDTO.GetMission toGetMission( - Mission mission - ){ - return MissionResDTO.GetMission.builder() + // 미션 생성 조회 (Entity → ResDTO) + public static MissionResDTO.GetCreateMissionDTO toGetCreateMission(Mission mission) { + return new MissionResDTO.GetCreateMissionDTO( + mission.getId(), + mission.getCreatedAt() + ); + } + + // 미션 조회 (Entity → ResDTO) + public static MissionResDTO.GetMissionDTO toGetMission(Mission mission) { + return MissionResDTO.GetMissionDTO.builder() .missionId(mission.getId()) .conditional(mission.getConditional()) .reward_point(mission.getReward_point()) .start_dt(mission.getStart_dt()) .end_dt(mission.getEnd_dt()) .build(); - } - -} +} \ No newline at end of file diff --git a/src/main/java/umc/domain/mission/dto/MissionReqDTO.java b/src/main/java/umc/domain/mission/dto/MissionReqDTO.java index f365d71c..1e1cc24a 100644 --- a/src/main/java/umc/domain/mission/dto/MissionReqDTO.java +++ b/src/main/java/umc/domain/mission/dto/MissionReqDTO.java @@ -1,14 +1,21 @@ package umc.domain.mission.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + import java.time.LocalDate; public class MissionReqDTO { // 가게 미션 생성 - public record CreateMission( + public record CreateMissionDTO( + @NotNull Integer reward_point, + @NotBlank String conditional, + @NotNull LocalDate start_dt, + @NotNull LocalDate end_dt ){} diff --git a/src/main/java/umc/domain/mission/dto/MissionResDTO.java b/src/main/java/umc/domain/mission/dto/MissionResDTO.java index b720f61d..fc71d0b7 100644 --- a/src/main/java/umc/domain/mission/dto/MissionResDTO.java +++ b/src/main/java/umc/domain/mission/dto/MissionResDTO.java @@ -3,16 +3,24 @@ import lombok.Builder; import java.time.LocalDate; +import java.time.LocalDateTime; public class MissionResDTO { - // 가게 내 미션 조회 + // 미션 생성 응답 @Builder - public record GetMission( + public record GetCreateMissionDTO( + Long mission_id, + LocalDateTime createdAt + ){} + + // 미션 조회 응답 + @Builder + public record GetMissionDTO( Long missionId, Integer reward_point, String conditional, LocalDate start_dt, LocalDate end_dt ){} -} +} \ No newline at end of file diff --git a/src/main/java/umc/domain/mission/service/MissionService.java b/src/main/java/umc/domain/mission/service/MissionService.java index 83ce7c1e..56021918 100644 --- a/src/main/java/umc/domain/mission/service/MissionService.java +++ b/src/main/java/umc/domain/mission/service/MissionService.java @@ -27,30 +27,23 @@ public class MissionService { // 가게 미션 생성 @Transactional - public Void createMission( + public MissionResDTO.GetCreateMissionDTO createMission( Long storeId, - MissionReqDTO.CreateMission dto + MissionReqDTO.CreateMissionDTO dto ){ - // 가게 찾기 Store store = storeRepository.findById(storeId) .orElseThrow(() -> new StoreException(StoreErrorCode.STORE_NOT_FOUND)); - - // 미션 생성 - Mission mission = MissionConverter.toMission(store, dto); - - // 미션 DB 저장 - missionRepository.save(mission); - - return null; + Mission mission = MissionConverter.toPutMission(store, dto); + return MissionConverter.toGetCreateMission(missionRepository.save(mission)); } - public List getMissions( + // 가게 미션 조회 + public List getMissions( Long storeId, Integer pageSize, Integer pageNumber, String sort ){ - // 정렬 정보 생성 Sort sortInfo; if(sort != null){ if(sort.equalsIgnoreCase("asc")){ @@ -58,20 +51,14 @@ public List getMissions( } else if(sort.equalsIgnoreCase("desc")){ sortInfo = Sort.by("id").descending(); } else { - sortInfo = Sort.by(sort); // 컬럼명으로 정렬 + sortInfo = Sort.by(sort); } } else { sortInfo = Sort.by("id").descending(); } - // 페이지 정보들을 PageRequest로 만들기 PageRequest pageRequest = PageRequest.of(pageNumber, pageSize, sortInfo); - - // 가게 내 미션들 조회 Page missionList = missionRepository.findAllByStoreId(storeId, pageRequest); - - - // 미션들 응답 DTO로 포장하기 return missionList.map(MissionConverter::toGetMission).getContent(); } -} +} \ No newline at end of file diff --git a/src/main/java/umc/domain/store/controller/StoreController.java b/src/main/java/umc/domain/store/controller/StoreController.java index 3e3b81cd..f3b70f82 100644 --- a/src/main/java/umc/domain/store/controller/StoreController.java +++ b/src/main/java/umc/domain/store/controller/StoreController.java @@ -1,49 +1,57 @@ package umc.domain.store.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; -import umc.domain.store.enums.ReviewSuccessCode; -import umc.domain.store.enums.StoreSuccessCode; import umc.domain.store.dto.StoreReqDTO; import umc.domain.store.dto.StoreResDTO; +import umc.domain.store.exception.code.StoreSuccessCode; import umc.domain.store.service.StoreService; import umc.global.apiPayload.ApiResponse; -import umc.global.apiPayload.code.BaseSuccessCode; @RestController @RequiredArgsConstructor -@RequestMapping("/api") +@RequestMapping("/api/stores") public class StoreController { private final StoreService storeService; - @PostMapping("/v1/stores/{storeId}") - public ApiResponse getStoreInfo( - @RequestBody StoreReqDTO.GetStoreInfo dto + // 가게 조회 + @GetMapping("/{storeId}") + public ApiResponse getStoreInfo( + @PathVariable Long storeId ){ - BaseSuccessCode code = StoreSuccessCode.OK; - return ApiResponse.onSuccess(code, storeService.getStoreInfo(dto)); + return ApiResponse.onSuccess(StoreSuccessCode.STORE_SUCCESS_CODE, + storeService.getStoreInfo(storeId)); } - @PostMapping("/v1/stores/{storeId}/reviews") - public ApiResponse getReviewInfo( - @RequestBody StoreReqDTO.GetReviewInfo dto + // 리뷰 조회 + @GetMapping("/{storeId}/reviews") + public ApiResponse getReviewInfo( + @PathVariable Long storeId, + @RequestParam Long memberId ){ - BaseSuccessCode code = ReviewSuccessCode.OK; - return ApiResponse.onSuccess(code, storeService.getReviewInfo(dto)); + return ApiResponse.onSuccess(StoreSuccessCode.REVIEW_SUCCESS_CODE, + storeService.getReviewInfo(memberId, storeId)); } - @PostMapping("/v1/stores/create") - public ApiResponse createStore() { - storeService.createStore(); - return ApiResponse.onSuccess(StoreSuccessCode.OK, "저장 완료"); + // 가게 생성 + @PostMapping + public ApiResponse createStore( + @RequestBody @Valid StoreReqDTO.CreateStoreDTO dto + ){ + return ApiResponse.onSuccess(StoreSuccessCode.STORE_CREATED, + storeService.createStore(dto)); } - @PostMapping("/v1/stores/reviews/create") - public ApiResponse createReview() { - storeService.createReview(); - return ApiResponse.onSuccess(ReviewSuccessCode.OK, "저장 완료"); + // 리뷰 생성 + @PostMapping("/{storeId}/reviews") + public ApiResponse createReview( + @PathVariable Long storeId, + @RequestParam Long memberId, + @RequestBody @Valid StoreReqDTO.CreateReviewDTO dto + ){ + return ApiResponse.onSuccess(StoreSuccessCode.REVIEW_CREATED, + storeService.createReview(memberId, storeId, dto)); } - - } \ No newline at end of file diff --git a/src/main/java/umc/domain/store/converter/StoreConverter.java b/src/main/java/umc/domain/store/converter/StoreConverter.java index 546f4326..325f57e6 100644 --- a/src/main/java/umc/domain/store/converter/StoreConverter.java +++ b/src/main/java/umc/domain/store/converter/StoreConverter.java @@ -1,15 +1,55 @@ package umc.domain.store.converter; import umc.domain.member.entity.Member; +import umc.domain.store.dto.StoreReqDTO; import umc.domain.store.dto.StoreResDTO; import umc.domain.store.entity.Review; import umc.domain.store.entity.Store; public class StoreConverter { - public static StoreResDTO.GetStoreInfo toGetStoreInfo( - Store store + + // 가게 생성 (ReqDTO → Entity) + public static Store toPutStore(StoreReqDTO.CreateStoreDTO dto) { + return Store.builder() + .store_nm(dto.store_nm()) + .region_nm(dto.region_nm()) + .open_dt(dto.open_dt()) + .close_dt(dto.close_dt()) + .build(); + } + + // 가게 생성 조회 (Entity → ResDTO) + public static StoreResDTO.GetCreateStoreDTO toGetStore(Store store) { + return new StoreResDTO.GetCreateStoreDTO( + store.getId(), + store.getCreatedAt() + ); + } + + // 리뷰 생성 (ReqDTO → Entity) + public static Review toPutReview( + StoreReqDTO.CreateReviewDTO dto, Member member, Store store ) { - return StoreResDTO.GetStoreInfo.builder() + return Review.builder() + .review_text(dto.review_text()) + .star_point(dto.star_point()) + .img_id(dto.img_id()) + .member(member) + .store(store) + .build(); + } + + // 리뷰 생성 조회 (Entity → ResDTO) + public static StoreResDTO.GetCreateReviewDTO toGetReview(Review review) { + return new StoreResDTO.GetCreateReviewDTO( + review.getId(), + review.getCreatedAt() + ); + } + + // 가게 조회 + public static StoreResDTO.GetStoreInfoDTO toGetStoreInfo(Store store) { + return StoreResDTO.GetStoreInfoDTO.builder() .store_nm(store.getStore_nm()) .region_nm(store.getRegion_nm()) .open_dt(store.getOpen_dt()) @@ -17,13 +57,12 @@ public static StoreResDTO.GetStoreInfo toGetStoreInfo( .build(); } - public static StoreResDTO.GetReviewInfo toGetReviewInfo( - Review review - ) { - return StoreResDTO.GetReviewInfo.builder() + // 리뷰 조회 + public static StoreResDTO.GetReviewInfoDTO toGetReviewInfo(Review review) { + return StoreResDTO.GetReviewInfoDTO.builder() .review_text(review.getReview_text()) .star_point(review.getStar_point()) .img_id(review.getImg_id()) .build(); } -} +} \ No newline at end of file diff --git a/src/main/java/umc/domain/store/dto/StoreReqDTO.java b/src/main/java/umc/domain/store/dto/StoreReqDTO.java index 5b7747b0..171daa7c 100644 --- a/src/main/java/umc/domain/store/dto/StoreReqDTO.java +++ b/src/main/java/umc/domain/store/dto/StoreReqDTO.java @@ -1,14 +1,30 @@ package umc.domain.store.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + public class StoreReqDTO { - public record GetStoreInfo( - Long store_id + // 가게 생성 + public record CreateStoreDTO( + @NotBlank + String store_nm, + @NotBlank + String region_nm, + @NotBlank + String open_dt, + @NotBlank + String close_dt ){} - // 멤버 번호, 가게 번호로 찾기 - public record GetReviewInfo( - Long member_id, - Long store_id + // 리뷰 생성 + public record CreateReviewDTO( + @NotBlank + String review_text, + @NotBlank + String star_point, + @NotBlank + String img_id ){} -} + +} \ No newline at end of file diff --git a/src/main/java/umc/domain/store/dto/StoreResDTO.java b/src/main/java/umc/domain/store/dto/StoreResDTO.java index 7ee88007..f973b50d 100644 --- a/src/main/java/umc/domain/store/dto/StoreResDTO.java +++ b/src/main/java/umc/domain/store/dto/StoreResDTO.java @@ -2,23 +2,38 @@ import lombok.Builder; +import java.time.LocalDateTime; + public class StoreResDTO { + + // 가게 생성 응답 + @Builder + public record GetCreateStoreDTO( + Long store_id, + LocalDateTime createdAt + ){} + + // 리뷰 생성 응답 @Builder - public record GetStoreInfo( + public record GetCreateReviewDTO( + Long review_id, + LocalDateTime createdAt + ){} + + // 가게 조회 응답 + @Builder + public record GetStoreInfoDTO( String store_nm, String region_nm, String open_dt, String close_dt ){} + // 리뷰 조회 응답 @Builder - public record GetReviewInfo( + public record GetReviewInfoDTO( String review_text, String star_point, - String img_id, - String member_id, - String store_id + String img_id ){} - - } diff --git a/src/main/java/umc/domain/store/enums/ReviewSuccessCode.java b/src/main/java/umc/domain/store/enums/ReviewSuccessCode.java deleted file mode 100644 index 12018744..00000000 --- a/src/main/java/umc/domain/store/enums/ReviewSuccessCode.java +++ /dev/null @@ -1,17 +0,0 @@ -package umc.domain.store.enums; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import umc.global.apiPayload.code.BaseSuccessCode; - -@Getter -@RequiredArgsConstructor -public enum ReviewSuccessCode implements BaseSuccessCode { - - OK(HttpStatus.OK, "REVIEW200_1", "성공적으로 리뷰를 조회했습니다."); - - private final HttpStatus status; - private final String code; - private final String message; -} \ No newline at end of file diff --git a/src/main/java/umc/domain/store/enums/StoreSuccessCode.java b/src/main/java/umc/domain/store/enums/StoreSuccessCode.java deleted file mode 100644 index 93296657..00000000 --- a/src/main/java/umc/domain/store/enums/StoreSuccessCode.java +++ /dev/null @@ -1,17 +0,0 @@ -package umc.domain.store.enums; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import umc.global.apiPayload.code.BaseSuccessCode; - -@Getter -@RequiredArgsConstructor -public enum StoreSuccessCode implements BaseSuccessCode { - - OK(HttpStatus.OK, "STORE200_1", "성공적으로 가게를 조회했습니다."); - - private final HttpStatus status; - private final String code; - private final String message; -} \ No newline at end of file diff --git a/src/main/java/umc/domain/store/exception/code/StoreSuccessCode.java b/src/main/java/umc/domain/store/exception/code/StoreSuccessCode.java index 87b5d496..ea3d6804 100644 --- a/src/main/java/umc/domain/store/exception/code/StoreSuccessCode.java +++ b/src/main/java/umc/domain/store/exception/code/StoreSuccessCode.java @@ -7,9 +7,15 @@ @Getter @RequiredArgsConstructor - public enum StoreSuccessCode implements BaseSuccessCode { - OK(HttpStatus.OK, "COMMON200_1", "성공적으로 요청을 처리했습니다."); // ← 세미콜론! + + STORE_CREATED(HttpStatus.CREATED, "STORE200_1", "성공적으로 가게를 생성했습니다."), + STORE_SUCCESS_CODE(HttpStatus.OK, "STORE200_2", "성공적으로 가게를 조회했습니다."), + + REVIEW_CREATED(HttpStatus.CREATED, "REVIEW200_1", "성공적으로 리뷰를 생성했습니다."), + REVIEW_SUCCESS_CODE(HttpStatus.OK, "REVIEW200_2", "성공적으로 리뷰를 조회했습니다."), + + ; private final HttpStatus status; private final String code; diff --git a/src/main/java/umc/domain/store/service/StoreService.java b/src/main/java/umc/domain/store/service/StoreService.java index 7843f5e8..68410ab8 100644 --- a/src/main/java/umc/domain/store/service/StoreService.java +++ b/src/main/java/umc/domain/store/service/StoreService.java @@ -1,9 +1,11 @@ package umc.domain.store.service; -import lombok.Builder; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import umc.domain.member.entity.Member; +import umc.domain.member.exception.MemberException; +import umc.domain.member.exception.code.MemberErrorCode; import umc.domain.store.converter.StoreConverter; import umc.domain.store.dto.StoreReqDTO; import umc.domain.store.dto.StoreResDTO; @@ -24,49 +26,41 @@ public class StoreService { private final ReviewRepository reviewRepository; private final MemberRepository memberRepository; - public StoreResDTO.GetStoreInfo getStoreInfo(StoreReqDTO.GetStoreInfo dto) { - Long storeId = dto.store_id(); + // 가게 조회 + public StoreResDTO.GetStoreInfoDTO getStoreInfo(Long storeId) { Store store = storeRepository.findById(storeId) .orElseThrow(() -> new StoreException(StoreErrorCode.STORE_NOT_FOUND)); return StoreConverter.toGetStoreInfo(store); } - public StoreResDTO.GetReviewInfo getReviewInfo(StoreReqDTO.GetReviewInfo dto) { + // 리뷰 조회 + public StoreResDTO.GetReviewInfoDTO getReviewInfo(Long memberId, Long storeId) { List reviews = reviewRepository - .findByMemberIdAndStoreId(dto.member_id(), dto.store_id()); - + .findByMemberIdAndStoreId(memberId, storeId); if (reviews.isEmpty()) { throw new StoreException(StoreErrorCode.REVIEW_NOT_FOUND); } - return StoreConverter.toGetReviewInfo(reviews.get(0)); } - public void createStore() { - Store store = Store.builder() - .store_nm("중국집") - .region_nm("서울시 구로구") - .open_dt("060001") - .close_dt("225959") - .build(); - - storeRepository.save(store); // ← 여기서 INSERT 실행 + // 가게 생성 + @Transactional + public StoreResDTO.GetCreateStoreDTO createStore(StoreReqDTO.CreateStoreDTO dto) { + Store store = StoreConverter.toPutStore(dto); + return StoreConverter.toGetStore(storeRepository.save(store)); } - public void createReview() { - Member member = memberRepository.findById(2L) - .orElseThrow(() -> new RuntimeException("멤버 없음")); - Store store = storeRepository.findById(2L) - .orElseThrow(() -> new RuntimeException("가게 없음")); - - Review review = Review.builder() - .review_text("음~ 너무 맛있어요!") - .star_point("5") - .img_id("이미지 아이디") - .member(member) // ← Member 객체 - .store(store) - .build(); + // 리뷰 생성 + @Transactional + public StoreResDTO.GetCreateReviewDTO createReview( + Long memberId, Long storeId, StoreReqDTO.CreateReviewDTO dto + ) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new StoreException(StoreErrorCode.STORE_NOT_FOUND)); - reviewRepository.save(review); // ← 여기서 INSERT 실행 + Review review = StoreConverter.toPutReview(dto, member, store); + return StoreConverter.toGetReview(reviewRepository.save(review)); } } \ No newline at end of file diff --git a/src/main/java/umc/global/config/SecurityConfig.java b/src/main/java/umc/global/config/SecurityConfig.java index fe4f6ed9..bc536f03 100644 --- a/src/main/java/umc/global/config/SecurityConfig.java +++ b/src/main/java/umc/global/config/SecurityConfig.java @@ -1,5 +1,6 @@ package umc.global.config; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -8,6 +9,8 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import umc.global.security.handler.CustomAccessDenied; +import umc.global.security.handler.CustomEntryPoint; @EnableWebSecurity @Configuration @@ -19,15 +22,16 @@ public class SecurityConfig { "/swagger-resources/**", "/v3/api-docs/**", + // 회원가입은 로그인 없이 가능해야 함 - "/api/v1/member/me" // 회원가입 경로 + "/api/members/signup" // 회원가입만 허용 }; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) - .authorizeHttpRequests(auth -> auth + .authorizeHttpRequests(requests -> requests .requestMatchers(allowUris).permitAll() .anyRequest().authenticated() ) @@ -37,8 +41,13 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti ) .logout(logout -> logout .logoutUrl("/logout") - .logoutSuccessUrl("/swagger-ui/index.html") + .logoutSuccessUrl("/login?logout") .permitAll() + ) + + .exceptionHandling(exception -> exception + .accessDeniedHandler(customAccessDenied()) + .authenticationEntryPoint(customEntryPoint()) ); return http.build(); @@ -48,4 +57,14 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + + @Bean + public CustomAccessDenied customAccessDenied(){ + return new CustomAccessDenied(); + } + + @Bean + public CustomEntryPoint customEntryPoint(){ + return new CustomEntryPoint(); + } } \ No newline at end of file diff --git a/src/main/java/umc/global/security/handler/CustomAccessDenied.java b/src/main/java/umc/global/security/handler/CustomAccessDenied.java new file mode 100644 index 00000000..1cdee86d --- /dev/null +++ b/src/main/java/umc/global/security/handler/CustomAccessDenied.java @@ -0,0 +1,37 @@ +package umc.global.security.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; +import umc.global.apiPayload.ApiResponse; +import umc.global.apiPayload.code.BaseErrorCode; +import umc.global.apiPayload.code.GeneralErrorCode; + +import java.io.IOException; + +@Component +public class CustomAccessDenied implements AccessDeniedHandler { + + @Override + public void handle( + HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException + ) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + BaseErrorCode code = GeneralErrorCode.FORBIDDEN; + + // 응답 Content-Type, HTTP 상태코드 정의 + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(code.getStatus().value()); + + // Response Body에 응답통일한 객체를 넣기 + ApiResponse errorResponse = ApiResponse.onFailure(code, null); + + // 실제 Response로 덮어쓰기 + objectMapper.writeValue(response.getOutputStream(), errorResponse); + } +} \ No newline at end of file diff --git a/src/main/java/umc/global/security/handler/CustomEntryPoint.java b/src/main/java/umc/global/security/handler/CustomEntryPoint.java new file mode 100644 index 00000000..268eebf4 --- /dev/null +++ b/src/main/java/umc/global/security/handler/CustomEntryPoint.java @@ -0,0 +1,37 @@ +package umc.global.security.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import umc.global.apiPayload.ApiResponse; +import umc.global.apiPayload.code.BaseErrorCode; +import umc.global.apiPayload.code.GeneralErrorCode; + +import java.io.IOException; + +@Component +public class CustomEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException + ) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + BaseErrorCode code = GeneralErrorCode.UNAUTHORIZED; // ← 차이점 1: 401 + + // 응답 Content-Type, HTTP 상태코드 정의 + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(code.getStatus().value()); + + // Response Body에 응답통일한 객체를 넣기 + ApiResponse errorResponse = ApiResponse.onFailure(code, null); + + // 실제 Response로 덮어쓰기 + objectMapper.writeValue(response.getOutputStream(), errorResponse); + } +} \ No newline at end of file From fa59071c94f74fe797cd1e3de38f81577cb886de Mon Sep 17 00:00:00 2001 From: Kohseoyoung Date: Fri, 29 May 2026 01:03:29 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20JWT=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=9D=98=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85,=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 ++ ...354\260\250 \352\263\274\354\240\2341.png" | Bin ...354\260\250 \352\263\274\354\240\2342.png" | Bin ...4\260\250 \353\257\270\354\205\2301-1.png" | Bin 0 -> 43347 bytes ...4\260\250 \353\257\270\354\205\2301-2.png" | Bin 0 -> 53813 bytes .../member/controller/MemberController.java | 21 ++++- .../member/converter/MemberConverter.java | 9 +- .../umc/domain/member/dto/MemberReqDTO.java | 6 +- .../umc/domain/member/dto/MemberResDTO.java | 5 + .../exception/code/MemberErrorCode.java | 2 + .../exception/code/MemberSuccessCode.java | 2 + .../domain/member/service/MemberService.java | 33 ++++++- .../umc/global/config/SecurityConfig.java | 35 ++++--- .../global/security/filter/JwtAuthFilter.java | 74 +++++++++++++++ .../umc/global/security/util/JwtUtil.java | 89 ++++++++++++++++++ 15 files changed, 264 insertions(+), 18 deletions(-) rename "src/main/java/mission/6\354\243\274\354\260\250 \352\263\274\354\240\2341.png" => "src/main/java/mission/6\354\243\274\354\260\250/6\354\243\274\354\260\250 \352\263\274\354\240\2341.png" (100%) rename "src/main/java/mission/6\354\243\274\354\260\250 \352\263\274\354\240\2342.png" => "src/main/java/mission/6\354\243\274\354\260\250/6\354\243\274\354\260\250 \352\263\274\354\240\2342.png" (100%) create mode 100644 "src/main/java/mission/9\354\243\274\354\260\250/9\354\243\274\354\260\250 \353\257\270\354\205\2301-1.png" create mode 100644 "src/main/java/mission/9\354\243\274\354\260\250/9\354\243\274\354\260\250 \353\257\270\354\205\2301-2.png" create mode 100644 src/main/java/umc/global/security/filter/JwtAuthFilter.java create mode 100644 src/main/java/umc/global/security/util/JwtUtil.java diff --git a/build.gradle b/build.gradle index fddc4be4..86ab2874 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,12 @@ dependencies { // Security implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' + + // Jwt + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' + implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + implementation 'org.springframework.boot:spring-boot-configuration-processor' } tasks.named('test') { diff --git "a/src/main/java/mission/6\354\243\274\354\260\250 \352\263\274\354\240\2341.png" "b/src/main/java/mission/6\354\243\274\354\260\250/6\354\243\274\354\260\250 \352\263\274\354\240\2341.png" similarity index 100% rename from "src/main/java/mission/6\354\243\274\354\260\250 \352\263\274\354\240\2341.png" rename to "src/main/java/mission/6\354\243\274\354\260\250/6\354\243\274\354\260\250 \352\263\274\354\240\2341.png" diff --git "a/src/main/java/mission/6\354\243\274\354\260\250 \352\263\274\354\240\2342.png" "b/src/main/java/mission/6\354\243\274\354\260\250/6\354\243\274\354\260\250 \352\263\274\354\240\2342.png" similarity index 100% rename from "src/main/java/mission/6\354\243\274\354\260\250 \352\263\274\354\240\2342.png" rename to "src/main/java/mission/6\354\243\274\354\260\250/6\354\243\274\354\260\250 \352\263\274\354\240\2342.png" diff --git "a/src/main/java/mission/9\354\243\274\354\260\250/9\354\243\274\354\260\250 \353\257\270\354\205\2301-1.png" "b/src/main/java/mission/9\354\243\274\354\260\250/9\354\243\274\354\260\250 \353\257\270\354\205\2301-1.png" new file mode 100644 index 0000000000000000000000000000000000000000..0de48c78769cfefed07751d0e11c9f7380de3453 GIT binary patch literal 43347 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%0DBzXs>_bpPBl(p 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μ+c?LYa!ki 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 Hm#_a9q65}q literal 0 HcmV?d00001 diff --git a/src/main/java/umc/domain/member/controller/MemberController.java b/src/main/java/umc/domain/member/controller/MemberController.java index c1f495e7..fea18406 100644 --- a/src/main/java/umc/domain/member/controller/MemberController.java +++ b/src/main/java/umc/domain/member/controller/MemberController.java @@ -2,6 +2,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import umc.domain.member.dto.MemberReqDTO; import umc.domain.member.dto.MemberResDTO; @@ -10,6 +11,7 @@ import umc.domain.mission.dto.MissionResDTO; import umc.global.apiPayload.ApiResponse; import umc.global.apiPayload.code.BaseSuccessCode; +import umc.global.security.entity.AuthMember; import java.util.List; @@ -20,7 +22,7 @@ public class MemberController { private final MemberService memberService; - // Member 조회 + // 멤버 조회 - 마이페이지 @PostMapping("/me") public ApiResponse getMember( @RequestBody MemberReqDTO.GetMemberDTO dto @@ -29,6 +31,15 @@ public ApiResponse getMember( memberService.getMember(dto.id())); } + // 마이페이지 + @GetMapping("/me/v2") + public ApiResponse getMember( + @AuthenticationPrincipal AuthMember member + ) { + BaseSuccessCode code = MemberSuccessCode.MEMBER_OK; + return ApiResponse.onSuccess(code, memberService.getMember(member)); + } + // 회원가입 @PostMapping("/signup") public ApiResponse signUp( @@ -37,6 +48,14 @@ public ApiResponse signUp( return ApiResponse.onSuccess(MemberSuccessCode.MEMBER_CREATED, memberService.signUp(requestDto)); } + // 로그인 + @PostMapping("/login") + public ApiResponse login( + @RequestBody MemberReqDTO.LoginRequest request + ) { + return ApiResponse.onSuccess(MemberSuccessCode.LOGIN_SUCCESS, memberService.login(request)); + } + // 내 미션 생성 @PostMapping("/me/missions") public ApiResponse createMyMission( diff --git a/src/main/java/umc/domain/member/converter/MemberConverter.java b/src/main/java/umc/domain/member/converter/MemberConverter.java index 52f8cfdd..021e6911 100644 --- a/src/main/java/umc/domain/member/converter/MemberConverter.java +++ b/src/main/java/umc/domain/member/converter/MemberConverter.java @@ -11,7 +11,7 @@ public class MemberConverter { - // 멤버 조회 + // 멤버 조회 - 마이페이지 public static MemberResDTO.GetMemberDTO toGetMember(Member member) { return MemberResDTO.GetMemberDTO.builder() .member_id(member.getId()) @@ -54,6 +54,11 @@ public static MemberResDTO.GetSignUpDTO toGetSignUp(Member member) { ); } + // 로그인 인증 + public static MemberResDTO.LoginResponse toLoginResponse(String accessToken) { + return new MemberResDTO.LoginResponse(accessToken); + } + // 멤버 미션 생성 public static MemberMission toPutMemberMission( Mission mission, Member member @@ -78,7 +83,7 @@ public static MemberResDTO.GetMemberMissionDTO toGetMemberMission( .build(); } - // 홈 조회, 마이페이지 조회 추가 + // 홈 조회 추가 } \ No newline at end of file diff --git a/src/main/java/umc/domain/member/dto/MemberReqDTO.java b/src/main/java/umc/domain/member/dto/MemberReqDTO.java index 3a6b6ee9..528ddcd5 100644 --- a/src/main/java/umc/domain/member/dto/MemberReqDTO.java +++ b/src/main/java/umc/domain/member/dto/MemberReqDTO.java @@ -55,6 +55,10 @@ public record TermDTO( Boolean isAgreed ) {} - + // 로그인 + public record LoginRequest( + String email, + String password + ) {} } diff --git a/src/main/java/umc/domain/member/dto/MemberResDTO.java b/src/main/java/umc/domain/member/dto/MemberResDTO.java index 24434892..802da359 100644 --- a/src/main/java/umc/domain/member/dto/MemberResDTO.java +++ b/src/main/java/umc/domain/member/dto/MemberResDTO.java @@ -31,6 +31,11 @@ public record GetSignUpDTO( ) { } + // 로그인 + public record LoginResponse( + String accessToken + ) {} + // 내 미션 조회 @Builder public record GetMemberMissionDTO( diff --git a/src/main/java/umc/domain/member/exception/code/MemberErrorCode.java b/src/main/java/umc/domain/member/exception/code/MemberErrorCode.java index 877fc9f7..0b731615 100644 --- a/src/main/java/umc/domain/member/exception/code/MemberErrorCode.java +++ b/src/main/java/umc/domain/member/exception/code/MemberErrorCode.java @@ -14,6 +14,8 @@ public enum MemberErrorCode implements BaseErrorCode { DUPLICATE_EMAIL(HttpStatus.CONFLICT, "MEMBER409_1", "이미 사용 중인 이메일입니다."), + INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "MEMBER401_1", "비밀번호가 일치하지 않습니다."), + ; private final HttpStatus status; diff --git a/src/main/java/umc/domain/member/exception/code/MemberSuccessCode.java b/src/main/java/umc/domain/member/exception/code/MemberSuccessCode.java index 0def7fde..6c7d29e7 100644 --- a/src/main/java/umc/domain/member/exception/code/MemberSuccessCode.java +++ b/src/main/java/umc/domain/member/exception/code/MemberSuccessCode.java @@ -12,6 +12,8 @@ public enum MemberSuccessCode implements BaseSuccessCode { MEMBER_CREATED(HttpStatus.CREATED, "MEMBER200_1", "성공적으로 유저를 생성했습니다."), MEMBER_OK(HttpStatus.OK, "MEMBER200_2", "성공적으로 유저를 조회했습니다."), + LOGIN_SUCCESS(HttpStatus.OK, "MEMBER200_3", "성공적으로 로그인했습니다."), // 추가! + MISSION_CREATED(HttpStatus.CREATED, "MEMBERMISSION200_1", "성공적으로 멤버 미션을 생성했습니다."), MEMBER_MISSION_OK(HttpStatus.OK, "MEMBERMISSION200_2", "성공적으로 멤버미션을 조회했습니다."); diff --git a/src/main/java/umc/domain/member/service/MemberService.java b/src/main/java/umc/domain/member/service/MemberService.java index 2a5953ca..e5048d8b 100644 --- a/src/main/java/umc/domain/member/service/MemberService.java +++ b/src/main/java/umc/domain/member/service/MemberService.java @@ -20,8 +20,9 @@ import umc.domain.mission.dto.MissionResDTO; import umc.domain.mission.entity.Mission; import umc.domain.mission.repository.MissionRepository; +import umc.global.security.entity.AuthMember; +import umc.global.security.util.JwtUtil; -import javax.swing.plaf.synth.Region; import java.util.List; @Service @@ -33,14 +34,21 @@ public class MemberService { private final MemberMissionRepository memberMissionRepository; private final PasswordEncoder passwordEncoder; + private final JwtUtil jwtUtil; - // Member 조회 + // 멤버 조회 - 마이페이지 public MemberResDTO.GetMemberDTO getMember(Long id) { Member member = memberRepository.findById(id) .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); return MemberConverter.toGetMember(member); } + // 마이페이지 + public MemberResDTO.GetMemberDTO getMember(AuthMember member) { + // 컨버터를 이용해서 응답 DTO 생성 & return + return MemberConverter.toGetMember(member.getMember()); + } + // 회원가입 @Transactional public MemberResDTO.GetSignUpDTO signUp(MemberReqDTO.SignUpDTO dto) { @@ -69,6 +77,25 @@ private Member createMember(MemberReqDTO.SignUpDTO dto) { // 선호음식 + 약관 추가 + // 로그인 + public MemberResDTO.LoginResponse login(MemberReqDTO.LoginRequest request) { + // 1. 이메일로 회원 조회 + Member member = memberRepository.findByEmail(request.email()) + .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); + + // 2. 비밀번호 검증 + if (!passwordEncoder.matches(request.password(), member.getPassword())) { + throw new MemberException(MemberErrorCode.INVALID_PASSWORD); + } + + // 3. JWT 토큰 발급 + AuthMember authMember = new AuthMember(member); + String accessToken = jwtUtil.createAccessToken(authMember); + + // 4. 응답 반환 + return MemberConverter.toLoginResponse(accessToken); + } + // 내 미션 생성 @Transactional public Void createMyMission(Long memberId, Long missionId) { @@ -105,4 +132,4 @@ public List getMyMissions( return memberMissionList.map(MemberConverter::toGetMemberMission).getContent(); } -} +} \ No newline at end of file diff --git a/src/main/java/umc/global/config/SecurityConfig.java b/src/main/java/umc/global/config/SecurityConfig.java index bc536f03..0472716a 100644 --- a/src/main/java/umc/global/config/SecurityConfig.java +++ b/src/main/java/umc/global/config/SecurityConfig.java @@ -9,22 +9,30 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import umc.global.security.filter.JwtAuthFilter; import umc.global.security.handler.CustomAccessDenied; import umc.global.security.handler.CustomEntryPoint; +import umc.global.security.service.CustomUserDetailsService; +import umc.global.security.util.JwtUtil; @EnableWebSecurity @Configuration +@RequiredArgsConstructor // 추가 public class SecurityConfig { + // 추가 + private final JwtUtil jwtUtil; + private final CustomUserDetailsService customUserDetailsService; + private final String[] allowUris = { - // Swagger 허용 "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**", - - // 회원가입은 로그인 없이 가능해야 함 - "/api/members/signup" // 회원가입만 허용 + // 로그인, 회원가입은 인증 없이 가능 + "/api/members/signup", + "/api/members/login" }; @Bean @@ -35,16 +43,15 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers(allowUris).permitAll() .anyRequest().authenticated() ) - .formLogin(form -> form - .defaultSuccessUrl("/swagger-ui/index.html", true) - .permitAll() - ) + .formLogin(AbstractHttpConfigurer::disable) // 변경 + .sessionManagement(AbstractHttpConfigurer::disable) // 추가 + // JWT 필터 추가 + .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class) .logout(logout -> logout .logoutUrl("/logout") .logoutSuccessUrl("/login?logout") .permitAll() ) - .exceptionHandling(exception -> exception .accessDeniedHandler(customAccessDenied()) .authenticationEntryPoint(customEntryPoint()) @@ -53,18 +60,24 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti return http.build(); } + // 추가 + @Bean + public JwtAuthFilter jwtAuthFilter() { + return new JwtAuthFilter(jwtUtil, customUserDetailsService); + } + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean - public CustomAccessDenied customAccessDenied(){ + public CustomAccessDenied customAccessDenied() { return new CustomAccessDenied(); } @Bean - public CustomEntryPoint customEntryPoint(){ + public CustomEntryPoint customEntryPoint() { return new CustomEntryPoint(); } } \ No newline at end of file diff --git a/src/main/java/umc/global/security/filter/JwtAuthFilter.java b/src/main/java/umc/global/security/filter/JwtAuthFilter.java new file mode 100644 index 00000000..6e5bbb57 --- /dev/null +++ b/src/main/java/umc/global/security/filter/JwtAuthFilter.java @@ -0,0 +1,74 @@ +package umc.global.security.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.filter.OncePerRequestFilter; + +import umc.global.apiPayload.ApiResponse; +import umc.global.apiPayload.code.BaseErrorCode; +import umc.global.apiPayload.code.GeneralErrorCode; +import umc.global.security.service.CustomUserDetailsService; +import umc.global.security.util.JwtUtil; + +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtAuthFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + private final CustomUserDetailsService customUserDetailsService; + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) throws ServletException, IOException { + + try { + // 토큰 가져오기 + String token = request.getHeader("Authorization"); + // token이 없거나 Bearer가 아니면 넘기기 + if (token == null || !token.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + // Bearer이면 추출 + token = token.replace("Bearer ", ""); + // AccessToken 검증하기: 올바른 토큰이면 + if (jwtUtil.isValid(token)) { + // 토큰에서 이메일 추출 + String email = jwtUtil.getEmail(token); + // 인증 객체 생성: 이메일로 찾아온 뒤, 인증 객체 생성 + UserDetails user = customUserDetailsService.loadUserByUsername(email); + Authentication auth = new UsernamePasswordAuthenticationToken( + user, + null, + user.getAuthorities() + ); + // 인증 완료 후 SecurityContextHolder에 넣기 + SecurityContextHolder.getContext().setAuthentication(auth); + } + filterChain.doFilter(request, response); + } catch (Exception e) { + ObjectMapper mapper = new ObjectMapper(); + BaseErrorCode code = GeneralErrorCode.UNAUTHORIZED; + + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(code.getStatus().value()); + + ApiResponse errorResponse = ApiResponse.onFailure(code,null); + + mapper.writeValue(response.getOutputStream(), errorResponse); + } + } +} \ No newline at end of file diff --git a/src/main/java/umc/global/security/util/JwtUtil.java b/src/main/java/umc/global/security/util/JwtUtil.java new file mode 100644 index 00000000..6e002030 --- /dev/null +++ b/src/main/java/umc/global/security/util/JwtUtil.java @@ -0,0 +1,89 @@ +package umc.global.security.util; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Component; +import umc.global.security.entity.AuthMember; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.stream.Collectors; + +@Component +public class JwtUtil { + + private final SecretKey secretKey; + private final Duration accessExpiration; + + public JwtUtil( + @Value("${jwt.token.secretKey}") String secret, + @Value("${jwt.token.expiration.access}") Long accessExpiration + ) { + this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + this.accessExpiration = Duration.ofMillis(accessExpiration); + } + + // AccessToken 생성 + public String createAccessToken(AuthMember member) { + return createToken(member, accessExpiration); + } + + /** 토큰에서 이메일 가져오기 + * + * @param token 유저 정보를 추출할 토큰 + * @return 유저 이메일을 토큰에서 추출합니다 + */ + public String getEmail(String token) { + try { + return getClaims(token).getPayload().getSubject(); + } catch (JwtException e) { + return null; + } + } + + /** 토큰 유효성 확인 + * + * @param token 유효한지 확인할 토큰 + * @return True, False 반환합니다 + */ + public boolean isValid(String token) { + try { + getClaims(token); + return true; + } catch (JwtException e) { + return false; + } + } + + // 토큰 생성 + private String createToken(AuthMember member, Duration expiration) { + Instant now = Instant.now(); + + String authorities = member.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + return Jwts.builder() + .subject(member.getUsername()) + .claim("role", authorities) + .claim("email", member.getUsername()) + .issuedAt(Date.from(now)) + .expiration(Date.from(now.plus(expiration))) + .signWith(secretKey) + .compact(); + } + + // 토큰 정보 가져오기 + private Jws getClaims(String token) throws JwtException { + return Jwts.parser() + .verifyWith(secretKey) + .clockSkewSeconds(60) + .build() + .parseSignedClaims(token); + } +} \ No newline at end of file