diff --git a/.gitignore b/.gitignore index c2065bc..4c75e42 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ out/ ### VS Code ### .vscode/ + +.env \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9189b81..b4ef0fc 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,16 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-security' + + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JSON 파싱용 + + implementation 'io.github.cdimascio:dotenv-java:3.2.0' + + + } tasks.named('test') { diff --git a/src/main/java/com/example/devSns/Comment/CommentController.java b/src/main/java/com/example/devSns/Comment/CommentController.java index efeb117..1617a85 100644 --- a/src/main/java/com/example/devSns/Comment/CommentController.java +++ b/src/main/java/com/example/devSns/Comment/CommentController.java @@ -2,7 +2,6 @@ import com.example.devSns.Comment.Dto.CreateCommentDto; import com.example.devSns.Comment.Dto.UpdateCommentDto; - import jakarta.persistence.EntityNotFoundException; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; @@ -12,6 +11,7 @@ import java.util.List; @RestController +@RequestMapping("/api") public class CommentController { private final CommentService commentService; @@ -20,7 +20,7 @@ public CommentController(CommentService commentService) { this.commentService = commentService; } - @PostMapping("posts/{post_id}/comments") + @PostMapping("/posts/{post_id}/comments") public ResponseEntity postComment( @Valid @RequestBody CreateCommentDto dto, @@ -30,13 +30,13 @@ public ResponseEntity postComment( commentService.createComment(post_id, dto); return ResponseEntity.status(HttpStatus.CREATED).build(); }catch (EntityNotFoundException e){ - return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + return ResponseEntity.notFound().build(); } } - @PostMapping("posts/{post_id}/comments/{comment_id}") + @PostMapping("/posts/{post_id}/comments/{comment_id}") public ResponseEntity postReplyComment( @Valid @RequestBody CreateCommentDto dto, @@ -47,24 +47,24 @@ public ResponseEntity postReplyComment( commentService.createReplyComment(post_id, comment_id, dto); return ResponseEntity.status(HttpStatus.CREATED).build(); }catch (EntityNotFoundException e){ - return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + return ResponseEntity.notFound().build(); } } - @GetMapping("posts/{post_id}/comments") + @GetMapping("/posts/{post_id}/comments") public ResponseEntity> getComments(@PathVariable("post_id") Long post_id) { - return ResponseEntity.status(HttpStatus.OK).body(commentService.getAllComments(post_id)); + return ResponseEntity.ok().body(commentService.getAllComments(post_id)); } - @GetMapping("posts/{post_id}/comments/{comment_id}") + @GetMapping("/posts/{post_id}/comments/{comment_id}") public ResponseEntity getCommentsByPostId( @PathVariable("post_id") Long post_id, @PathVariable("comment_id") Long comment_id) { try{ - return ResponseEntity.status(HttpStatus.OK).body(commentService.getCommentById(post_id, comment_id)); + return ResponseEntity.ok(commentService.getCommentById(post_id, comment_id)); }catch (EntityNotFoundException e){ - return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + return ResponseEntity.notFound().build(); } } @@ -75,21 +75,21 @@ public ResponseEntity updateComment( @Valid @RequestBody UpdateCommentDto dto) { try{ - return ResponseEntity.status(HttpStatus.OK).body(commentService.updateComment(comment_id, dto)); + return ResponseEntity.ok().body(commentService.updateComment(comment_id, dto)); }catch (EntityNotFoundException e){ - return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + return ResponseEntity.notFound().build(); } } - @DeleteMapping("comments/{comment_id}") + @DeleteMapping("/comments/{comment_id}") public ResponseEntity deleteComment(@PathVariable("comment_id") Long comment_id) { try{ commentService.deleteCommentById(comment_id); - return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + return ResponseEntity.noContent().build(); } catch (EntityNotFoundException e){ - return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + return ResponseEntity.notFound().build(); } } diff --git a/src/main/java/com/example/devSns/Heart/Heart.java b/src/main/java/com/example/devSns/Heart/Heart.java index 90ae7ee..6670d94 100644 --- a/src/main/java/com/example/devSns/Heart/Heart.java +++ b/src/main/java/com/example/devSns/Heart/Heart.java @@ -11,6 +11,12 @@ @Getter @NoArgsConstructor @AllArgsConstructor +@Table( + name = "heart", + indexes = { + @Index(name ="idx_heart_post_liked", columnList = "post_id , like_status") + } +) public class Heart { @Id @@ -29,21 +35,15 @@ public class Heart { // 리포지터리에 메소드로 계산하면 되겠지? @Column(name ="like_status", nullable = false) - @Enumerated(EnumType.STRING) - private LikeStatus like; + private boolean liked; - public Heart(Post post, Member member, LikeStatus heart) { + public Heart(Post post, Member member, boolean like) { this.post = post; this.member = member; - this.like = heart; + this.liked = like; } public void toggleLike() { - - if (like == LikeStatus.LIKE) { - like = LikeStatus.NONE; - }else if(like == LikeStatus.NONE){ - like = LikeStatus.LIKE; - } + this.liked = !this.liked; } } diff --git a/src/main/java/com/example/devSns/Heart/HeartRepository.java b/src/main/java/com/example/devSns/Heart/HeartRepository.java index 43b677f..f41510b 100644 --- a/src/main/java/com/example/devSns/Heart/HeartRepository.java +++ b/src/main/java/com/example/devSns/Heart/HeartRepository.java @@ -8,5 +8,5 @@ public interface HeartRepository extends JpaRepository { Boolean existsByPostIdAndMemberId(Long postId, Long memberId); Optional findByPostIdAndMemberId(Long postId, Long memberId); - long countByPostIdAndLike(Long postId, LikeStatus like); + long countByPostIdAndLiked(Long postId, Boolean Liked); } diff --git a/src/main/java/com/example/devSns/Jwt/JwtUtil.java b/src/main/java/com/example/devSns/Jwt/JwtUtil.java new file mode 100644 index 0000000..991da63 --- /dev/null +++ b/src/main/java/com/example/devSns/Jwt/JwtUtil.java @@ -0,0 +1,53 @@ +package com.example.devSns.Jwt; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Component +public class JwtUtil { + + + private final String secret;// 실제 프로젝트에서는 환경변수로 관리 + private final long EXPIRATION = 1000L * 60 * 60; // 1시간 + + public JwtUtil(@Value("${jwt.secret}") String secret) { + this.secret = secret; + } + public String createToken(Long memberId) { + Date now = new Date(); + Date expireTime = new Date(now.getTime() + EXPIRATION); + + return Jwts.builder() + .setSubject(String.valueOf(memberId)) + .setIssuedAt(now) + .setExpiration(expireTime) + .signWith(SignatureAlgorithm.HS256, secret) + .compact(); + } + + public boolean validateToken(String token) { + try { + Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token); + return true; + } catch (Exception e) { + return false; + } + } + + public Long extractMemberId(String token) { + return Long.valueOf( + Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody() + .getSubject() + ); + } +} + diff --git a/src/main/java/com/example/devSns/Login/LoginController.java b/src/main/java/com/example/devSns/Login/LoginController.java new file mode 100644 index 0000000..34f47ec --- /dev/null +++ b/src/main/java/com/example/devSns/Login/LoginController.java @@ -0,0 +1,27 @@ +package com.example.devSns.Login; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +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; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/auth") +public class LoginController { + + private final LoginService loginService; + + @PostMapping("/login") + public String login(@RequestBody LoginRequest request) { + return loginService.login(request.getEmail(), request.getPassword()); + } + + @Data + static class LoginRequest { + private String email; + private String password; + } +} diff --git a/src/main/java/com/example/devSns/Login/LoginService.java b/src/main/java/com/example/devSns/Login/LoginService.java new file mode 100644 index 0000000..8d491ae --- /dev/null +++ b/src/main/java/com/example/devSns/Login/LoginService.java @@ -0,0 +1,32 @@ +package com.example.devSns.Login; + +import com.example.devSns.Jwt.JwtUtil; +import com.example.devSns.Member.Member; +import com.example.devSns.Member.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class LoginService { + + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + private final JwtUtil jwtUtil; + + public String login(String email, String rawPassword) { + + Member member = memberRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 이메일입니다.")); + + if (!passwordEncoder.matches(rawPassword, member.getPassword())) { + throw new IllegalArgumentException("비밀번호가 일치하지 않습니다."); + } + + // JWT 발급 + return jwtUtil.createToken(member.getId()); + } +} diff --git a/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java b/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java index dd8c218..dc06145 100644 --- a/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java +++ b/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java @@ -2,28 +2,29 @@ import com.example.devSns.Member.Gender; import com.example.devSns.Member.Member; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; +import jakarta.validation.constraints.*; public record SignMemberRequestDto( - @NotBlank(message = "공백 또는 null 값은 허용하지 않습니다") + @NotBlank(message = "이름은 공백 또는 null 값은 허용하지 않습니다") String nickname, - @NotBlank(message = "공백 또는 null 값은 허용하지 않습니다") + @NotBlank(message = "이메일은 공백 또는 null 값은 허용하지 않습니다") @Email(message ="올바른 이메일 양식이 아닙니다") String email, - @NotBlank(message = "공백 또는 null 값은 허용하지 않습니다") + @NotBlank(message = "비밀번호는 공백 또는 null 값은 허용하지 않습니다") @Size(min = 8, max =16, message = "비밀번호는 8자 이상 16자 이하로 설정해주세요") + @Pattern( + regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]+$", + message = "영어, 숫자, 특수문자를 각각 최소 1개 이상 포함해야 합니다" + ) String password, - @NotNull(message = "공백 또는 null 값은 허용하지 않습니다") + @NotNull(message = "성별은 공백 또는 null 값은 허용하지 않습니다") Gender gender, - @NotNull(message = "공백 또는 null 값은 허용하지 않습니다") + @NotNull(message = "나이는 공백 또는 null 값은 허용하지 않습니다") Integer age ) { diff --git a/src/main/java/com/example/devSns/Member/MemberController.java b/src/main/java/com/example/devSns/Member/MemberController.java index 4a037ae..1dcdec2 100644 --- a/src/main/java/com/example/devSns/Member/MemberController.java +++ b/src/main/java/com/example/devSns/Member/MemberController.java @@ -6,12 +6,12 @@ import jakarta.persistence.EntityNotFoundException; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor +@RequestMapping("/api") public class MemberController { private final MemberService memberService; @@ -22,7 +22,7 @@ public ResponseEntity createMember( @Valid @RequestBody SignMemberRequestDto dto) { - return ResponseEntity.status(HttpStatus.CREATED).body(memberService.createMember(dto)); + return ResponseEntity.status(201).body(memberService.createMember(dto)); } // 특정 멤버 검색하기 @@ -30,7 +30,7 @@ public ResponseEntity createMember( public ResponseEntity getMember( @PathVariable(name ="member_id") Long memberId ) { - return ResponseEntity.status(200).body(memberService.getMemberById(memberId)); + return ResponseEntity.ok().body(memberService.getMemberById(memberId)); } // like_status toggle @@ -52,7 +52,7 @@ public ResponseEntity convertLike( public ResponseEntity getMemberPostAndComment( @PathVariable(name = "member_id") Long memberId ){ - return ResponseEntity.status(200).body(memberService.getMemberPostAndComment(memberId)); + return ResponseEntity.ok().body(memberService.getMemberPostAndComment(memberId)); } } diff --git a/src/main/java/com/example/devSns/Member/MemberRepository.java b/src/main/java/com/example/devSns/Member/MemberRepository.java index 160b4d0..f84b93b 100644 --- a/src/main/java/com/example/devSns/Member/MemberRepository.java +++ b/src/main/java/com/example/devSns/Member/MemberRepository.java @@ -2,6 +2,8 @@ import org.springframework.data.jpa.repository.JpaRepository; -public interface MemberRepository extends JpaRepository { +import java.util.Optional; +public interface MemberRepository extends JpaRepository { + Optional findByEmail(String email); } diff --git a/src/main/java/com/example/devSns/Member/MemberService.java b/src/main/java/com/example/devSns/Member/MemberService.java index 0053d29..d4e5a26 100644 --- a/src/main/java/com/example/devSns/Member/MemberService.java +++ b/src/main/java/com/example/devSns/Member/MemberService.java @@ -2,7 +2,6 @@ import com.example.devSns.Heart.Heart; import com.example.devSns.Heart.HeartRepository; -import com.example.devSns.Heart.LikeStatus; import com.example.devSns.Member.Dto.GetMemberPostAndCommentResponseDto; import com.example.devSns.Member.Dto.GetMemberResponseDto; import com.example.devSns.Member.Dto.SignMemberRequestDto; @@ -12,6 +11,7 @@ import com.example.devSns.Post.PostRepository; import com.example.devSns.Post.PostService; import com.example.devSns.global.EntityNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -27,6 +27,7 @@ public class MemberService { private final PostService postService; private final HeartRepository heartRepository; private final PostRepository postRepository; + private final BCryptPasswordEncoder passwordEncoder; // (선택) 팔로우 기능 구현 (닉네임으로 친구 추가 보내기) @@ -34,7 +35,17 @@ public class MemberService { // 멤버 객체 생성 (회원가입_느낌으로다가) @Transactional public GetMemberResponseDto createMember(SignMemberRequestDto dto) { - Member saved = memberRepository.save(dto.toEntity()); + String encryptedPw = passwordEncoder.encode(dto.password()); + + Member member = new Member( + dto.nickname(), + dto.email(), + encryptedPw, + dto.gender(), + dto.age() + ); + + Member saved = memberRepository.save(member); return new GetMemberResponseDto(saved); } @@ -77,22 +88,22 @@ public void convertLikeStatus(Long postId, Long memberId){ // memberId는 나중 Heart heart = new Heart( post, member, - LikeStatus.NONE + false ); heart.toggleLike(); - heartRepository.save(heart); } else{ Heart heart = heartRepository.findByPostIdAndMemberId(postId, memberId) .orElseThrow(() -> new EntityNotFoundException("게시글 없음")); heart.toggleLike(); - heartRepository.save(heart); } } + + } diff --git a/src/main/java/com/example/devSns/Post/Post.java b/src/main/java/com/example/devSns/Post/Post.java index 2dda81f..f7dabc5 100644 --- a/src/main/java/com/example/devSns/Post/Post.java +++ b/src/main/java/com/example/devSns/Post/Post.java @@ -48,10 +48,10 @@ public void Update(UpdatePostRequestDto Dto){ this.userName = Dto.username(); this.updatedAt = LocalDateTime.now(); } - public Post(String content, String userName, Long likeCount) { + public Post(String content, String userName) { this.content = content; this.userName = userName; - this.likeCount = likeCount; + this.likeCount = 0L; } @PrePersist diff --git a/src/main/java/com/example/devSns/Post/PostController.java b/src/main/java/com/example/devSns/Post/PostController.java index 9862eb3..3ab4657 100644 --- a/src/main/java/com/example/devSns/Post/PostController.java +++ b/src/main/java/com/example/devSns/Post/PostController.java @@ -5,7 +5,6 @@ import com.example.devSns.Post.Dto.UpdatePostRequestDto; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -13,18 +12,19 @@ @RestController @RequiredArgsConstructor +@RequestMapping("/api") public class PostController { private final PostService postService; - @PostMapping("members/{member_id}/posts") + @PostMapping("/members/{member_id}/posts") public ResponseEntity createPost( @Valid @RequestBody AddPostRequestDto Dto, @PathVariable("member_id") Long member_id) { postService.createPost(Dto,member_id); - return ResponseEntity.status(HttpStatus.CREATED).build(); + return ResponseEntity.status(201).build(); } @GetMapping("/posts") diff --git a/src/main/java/com/example/devSns/Post/PostService.java b/src/main/java/com/example/devSns/Post/PostService.java index 0ce1ce5..3391b25 100644 --- a/src/main/java/com/example/devSns/Post/PostService.java +++ b/src/main/java/com/example/devSns/Post/PostService.java @@ -2,7 +2,6 @@ import com.example.devSns.Comment.CommentRepository; import com.example.devSns.Heart.HeartRepository; -import com.example.devSns.Heart.LikeStatus; import com.example.devSns.Member.Member; import com.example.devSns.Member.MemberRepository; import com.example.devSns.Post.Dto.AddPostRequestDto; @@ -30,8 +29,7 @@ public class PostService { public void createPost(AddPostRequestDto Dto, Long memberId) { Post post = new Post( Dto.content(), - Dto.username(), - 0L + Dto.username() ); Member member = memberRepository.findById(memberId) .orElseThrow(()->new EntityNotFoundException("Member not found")); @@ -75,7 +73,7 @@ public void countLikes() { List posts = postRepository.findAll(); for (Post post : posts) { - long likeCount = heartRepository.countByPostIdAndLike(post.getId(), LikeStatus.LIKE); + long likeCount = heartRepository.countByPostIdAndLiked(post.getId(), true); post.updateLikeCount((Long) likeCount); } } diff --git a/src/main/java/com/example/devSns/global/AuthInterceptor.java b/src/main/java/com/example/devSns/global/AuthInterceptor.java new file mode 100644 index 0000000..9823068 --- /dev/null +++ b/src/main/java/com/example/devSns/global/AuthInterceptor.java @@ -0,0 +1,43 @@ +package com.example.devSns.global; + +import com.example.devSns.Jwt.JwtUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class AuthInterceptor implements HandlerInterceptor { + + private final JwtUtil jwtUtil; + + public AuthInterceptor(JwtUtil jwtUtil) { + this.jwtUtil = jwtUtil; + } + + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, + Object handler) throws Exception { + + String auth = request.getHeader("Authorization"); + + if (auth == null || !auth.startsWith("Bearer ")) { + response.sendError(401, "Missing token"); + return false; + } + + String token = auth.substring(7); + + if (!jwtUtil.validateToken(token)) { + response.sendError(401, "Invalid token"); + return false; + } + + Long memberId = jwtUtil.extractMemberId(token); + request.setAttribute("memberId", memberId); + + return true; + } +} + diff --git a/src/main/java/com/example/devSns/global/GlobalExceptionHandler.java b/src/main/java/com/example/devSns/global/GlobalExceptionHandler.java index 7b0ecf4..e82f43b 100644 --- a/src/main/java/com/example/devSns/global/GlobalExceptionHandler.java +++ b/src/main/java/com/example/devSns/global/GlobalExceptionHandler.java @@ -7,16 +7,28 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.Map; +import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleValidation() { - String message = "공백 또는 null 값은 혀용하지 않습니다"; + public ResponseEntity> handleValidation(MethodArgumentNotValidException e) { + + // 필드별 검증 메시지를 Map으로 변환 + Map errors = e.getBindingResult() + .getFieldErrors() + .stream() + .collect(Collectors.toMap( + fe -> fe.getField(), // 필드 이름 + fe -> fe.getDefaultMessage(), // 메시지 + (existing, replacement) -> existing // 중복 필드 처리 + )); + return ResponseEntity .status(HttpStatus.BAD_REQUEST) - .body(Map.of("error", message)); + .body(errors); } + } diff --git a/src/main/java/com/example/devSns/global/config/AppConfig.java b/src/main/java/com/example/devSns/global/config/AppConfig.java new file mode 100644 index 0000000..f05c3fc --- /dev/null +++ b/src/main/java/com/example/devSns/global/config/AppConfig.java @@ -0,0 +1,14 @@ +package com.example.devSns.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +public class AppConfig { + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/example/devSns/global/config/JwtConfig.java b/src/main/java/com/example/devSns/global/config/JwtConfig.java new file mode 100644 index 0000000..5feb225 --- /dev/null +++ b/src/main/java/com/example/devSns/global/config/JwtConfig.java @@ -0,0 +1,17 @@ +package com.example.devSns.global.config; + +import com.example.devSns.Jwt.JwtUtil; + +import io.github.cdimascio.dotenv.Dotenv; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JwtConfig { + @Bean + public JwtUtil jwtUtil() { + Dotenv dotenv = Dotenv.load(); + String secret = dotenv.get("JWT_SECRET"); + return new JwtUtil(secret); + } +} diff --git a/src/main/java/com/example/devSns/global/config/SecurityConfig.java b/src/main/java/com/example/devSns/global/config/SecurityConfig.java new file mode 100644 index 0000000..6adefc9 --- /dev/null +++ b/src/main/java/com/example/devSns/global/config/SecurityConfig.java @@ -0,0 +1,23 @@ +package com.example.devSns.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) // CSRF 비활성화 + .authorizeHttpRequests(auth -> auth + .anyRequest().permitAll() // 모든 요청 허용 (인터셉터로 인증 처리) + ) + .httpBasic(httpBasic -> httpBasic.disable()); // 기본 로그인 비활성화 + + return http.build(); + } +} + diff --git a/src/main/java/com/example/devSns/global/config/WebConfig.java b/src/main/java/com/example/devSns/global/config/WebConfig.java new file mode 100644 index 0000000..beb0012 --- /dev/null +++ b/src/main/java/com/example/devSns/global/config/WebConfig.java @@ -0,0 +1,22 @@ +package com.example.devSns.global.config; + +import com.example.devSns.global.AuthInterceptor; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final AuthInterceptor authInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authInterceptor) + .addPathPatterns("/api/**") + .excludePathPatterns("/auth/**","/api/members"); + } +} + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f78a5f7..d88d463 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,4 +9,6 @@ spring.datasource.password= # H2 Console (Optional, for development) spring.h2.console.enabled=true -spring.h2.console.path=/h2-console \ No newline at end of file +spring.h2.console.path=/h2-console +#jwt.secret=7cb89b6399df27ebc401cb0286e60871a2d5bdd7445a0072065d3d5268ffd3cecf11f602961d6673f4c1ddb2295974f33dba3a8ab7168e2d5cb44924ae3b22a8 +jwt.secret=${JWT_SECRET} \ No newline at end of file diff --git a/src/test/java/com/example/devSns/Heart/HeartEntityTest.java b/src/test/java/com/example/devSns/Heart/HeartEntityTest.java index ba5366c..d55696e 100644 --- a/src/test/java/com/example/devSns/Heart/HeartEntityTest.java +++ b/src/test/java/com/example/devSns/Heart/HeartEntityTest.java @@ -16,11 +16,11 @@ void toggleLike_noneToLike() { Post post = new Post("content", "writer", 0L); Member member = new Member("nick", "email", "pwd", Gender.MALE, 28); - Heart heart = new Heart(post, member, LikeStatus.NONE); + Heart heart = new Heart(post, member, false); heart.toggleLike(); - assertEquals(LikeStatus.LIKE, heart.getLike()); + assertEquals(true, heart.isLiked()); } @Test @@ -29,11 +29,11 @@ void toggleLike_likeToNone() { Post post = new Post("content", "writer", 0L); Member member = new Member("nick", "email", "pwd", Gender.MALE, 28); - Heart heart = new Heart(post, member, LikeStatus.LIKE); + Heart heart = new Heart(post, member, true); heart.toggleLike(); - assertEquals(LikeStatus.NONE, heart.getLike()); + assertEquals(false, heart.isLiked()); } } diff --git a/src/test/java/com/example/devSns/Member/MemberServiceTest.java b/src/test/java/com/example/devSns/Member/MemberServiceTest.java index 99eb64e..20228ed 100644 --- a/src/test/java/com/example/devSns/Member/MemberServiceTest.java +++ b/src/test/java/com/example/devSns/Member/MemberServiceTest.java @@ -146,7 +146,7 @@ void convertLikeStatus_newHeart() { void convertLikeStatus_existingHeart() { Post post = new Post("content", "writer", 0L); Member member = new Member("nickname", "email", "password", Gender.FEMALE, 22); - Heart heart = new Heart(post, member, LikeStatus.NONE); + Heart heart = new Heart(post, member, false); when(postRepository.findById(1L)).thenReturn(Optional.of(post)); when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); @@ -155,7 +155,7 @@ void convertLikeStatus_existingHeart() { memberService.convertLikeStatus(1L, 1L); - assertEquals(LikeStatus.LIKE, heart.getLike()); // 토글 확인 + assertEquals(true, heart.isLiked()); // 토글 확인 verify(heartRepository).save(heart); } diff --git a/src/test/java/com/example/devSns/Post/PostServiceTest.java b/src/test/java/com/example/devSns/Post/PostServiceTest.java index 286322e..4c62adb 100644 --- a/src/test/java/com/example/devSns/Post/PostServiceTest.java +++ b/src/test/java/com/example/devSns/Post/PostServiceTest.java @@ -3,7 +3,6 @@ import com.example.devSns.Comment.Comment; import com.example.devSns.Comment.CommentRepository; import com.example.devSns.Heart.HeartRepository; -import com.example.devSns.Heart.LikeStatus; import com.example.devSns.Member.Member; import com.example.devSns.Member.MemberRepository; import com.example.devSns.Post.Dto.AddPostRequestDto; @@ -162,15 +161,15 @@ void countLikes_success() { List posts = List.of(post1, post2); when(postRepository.findAll()).thenReturn(posts); - when(heartRepository.countByPostIdAndLike(1L, LikeStatus.LIKE)).thenReturn(5L); - when(heartRepository.countByPostIdAndLike(2L, LikeStatus.LIKE)).thenReturn(3L); + when(heartRepository.countByPostIdAndLiked(1L, true)).thenReturn(5L); + when(heartRepository.countByPostIdAndLiked(2L, true)).thenReturn(3L); // when postService.countLikes(); // then verify(postRepository).findAll(); - verify(heartRepository, times(2)).countByPostIdAndLike(anyLong(), eq(LikeStatus.LIKE)); + verify(heartRepository, times(2)).countByPostIdAndLiked(anyLong(), eq(true)); assertEquals(5L, post1.getLikeCount()); assertEquals(3L, post2.getLikeCount());