diff --git a/build.gradle b/build.gradle index 48302d0b63b64be360d88bba25579021be933575..df58bc560c9437f75bef5437315933d3395914ea 100644 --- a/build.gradle +++ b/build.gradle @@ -24,8 +24,10 @@ repositories { } dependencies { + implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + //H2 DB 추가 runtimeOnly 'com.h2database:h2' compileOnly 'org.projectlombok:lombok' @@ -33,6 +35,13 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + + + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + } tasks.named('test') { diff --git a/src/main/java/umc/spring/board/conroller/BoardController.java b/src/main/java/umc/spring/board/conroller/BoardController.java deleted file mode 100644 index c82ace7c41d89c8c6ba1bc5c50afb2e6e56fbccf..0000000000000000000000000000000000000000 --- a/src/main/java/umc/spring/board/conroller/BoardController.java +++ /dev/null @@ -1,35 +0,0 @@ -package umc.spring.board.conroller; - -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; -import umc.spring.board.domain.Board; -import umc.spring.board.dto.BoardRequestDto; -import umc.spring.board.dto.BoardResponseDto; -import umc.spring.board.repository.BoardRepository; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/posts") -public class BoardController { - - private final BoardRepository boardRepository; - - @PostMapping("/upload") - public void upload(@RequestBody BoardRequestDto boardDto){ - System.out.println(boardDto); - Board board = boardDto.toBoard(); - boardRepository.save(board); - } - - @GetMapping("/") - public List<BoardResponseDto> getPosts(){ - return boardRepository.findAll().stream().map(BoardResponseDto::toDto).toList(); - } - - @GetMapping("/{id}") - public BoardResponseDto getPost(@PathVariable(name="id") Long boardId){ - return BoardResponseDto.toDto(boardRepository.findById(boardId).orElseThrow(RuntimeException::new)); - } -} diff --git a/src/main/java/umc/spring/board/domain/Board.java b/src/main/java/umc/spring/board/domain/Board.java deleted file mode 100644 index fdf04c7cb437ef347cee814e412f7a747afd2fbc..0000000000000000000000000000000000000000 --- a/src/main/java/umc/spring/board/domain/Board.java +++ /dev/null @@ -1,39 +0,0 @@ -package umc.spring.board.domain; - -import jakarta.persistence.*; -import lombok.*; -import org.hibernate.annotations.CreationTimestamp; -import umc.spring.member.domain.Member; - -import java.sql.Date; - -@Entity -@Getter -@Setter -public class Board { - - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String title; - - private String author; - - private String body; - - @CreationTimestamp - private Date date; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id") - Member member; - - protected Board(){} - - @Builder - public Board(String title, String author, String body) { - this.title = title; - this.author = author; - this.body = body; - } -} diff --git a/src/main/java/umc/spring/board/dto/BoardRequestDto.java b/src/main/java/umc/spring/board/dto/BoardRequestDto.java deleted file mode 100644 index 37e6818cc57bd6986d86e827194f59f737c041d7..0000000000000000000000000000000000000000 --- a/src/main/java/umc/spring/board/dto/BoardRequestDto.java +++ /dev/null @@ -1,33 +0,0 @@ -package umc.spring.board.dto; - - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import umc.spring.board.domain.Board; - -@Getter -@Setter -public class BoardRequestDto { - - private String title; - - private String author; - - private String body; - - @Builder - public BoardRequestDto(String title, String author, String body) { - this.title = title; - this.author = author; - this.body = body; - } - - public Board toBoard() { - return Board.builder() - .author(this.getAuthor()) - .title(this.getTitle()) - .body(this.getBody()) - .build(); - } -} diff --git a/src/main/java/umc/spring/board/dto/BoardResponseDto.java b/src/main/java/umc/spring/board/dto/BoardResponseDto.java deleted file mode 100644 index 29950a4417e97f229e4998542113e2fc14a7ac4e..0000000000000000000000000000000000000000 --- a/src/main/java/umc/spring/board/dto/BoardResponseDto.java +++ /dev/null @@ -1,36 +0,0 @@ -package umc.spring.board.dto; - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import umc.spring.board.domain.Board; - -@Getter -@Setter -public class BoardResponseDto { - - private Long id; - - private String title; - - private String author; - - private String body; - - @Builder - public BoardResponseDto(Long id, String title, String author, String body) { - this.id = id; - this.title = title; - this.author = author; - this.body = body; - } - - public static BoardResponseDto toDto(Board board){ - return BoardResponseDto.builder() - .id(board.getId()) - .body(board.getBody()) - .title(board.getTitle()) - .author(board.getAuthor()) - .build(); - } -} diff --git a/src/main/java/umc/spring/board/repository/BoardRepository.java b/src/main/java/umc/spring/board/repository/BoardRepository.java deleted file mode 100644 index 4c9b4b74e72e1ba6a8788a755a085f5d0dcea4c3..0000000000000000000000000000000000000000 --- a/src/main/java/umc/spring/board/repository/BoardRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package umc.spring.board.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import umc.spring.board.domain.Board; - -import java.util.Optional; - -public interface BoardRepository extends JpaRepository<Board, Long> { - Optional<Board> findById(Long id); -} \ No newline at end of file diff --git a/src/main/java/umc/spring/board/service/BoardService.java b/src/main/java/umc/spring/board/service/BoardService.java deleted file mode 100644 index 82a99ffa3d474b1592ead89e43ee740060b154aa..0000000000000000000000000000000000000000 --- a/src/main/java/umc/spring/board/service/BoardService.java +++ /dev/null @@ -1,9 +0,0 @@ -package umc.spring.board.service; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional(readOnly = true) -public class BoardService { -} diff --git a/src/main/java/umc/spring/member/controller/MemberController.java b/src/main/java/umc/spring/member/controller/MemberController.java deleted file mode 100644 index 29c005aee19e7e1e6d11706f9324231d45555d56..0000000000000000000000000000000000000000 --- a/src/main/java/umc/spring/member/controller/MemberController.java +++ /dev/null @@ -1,4 +0,0 @@ -package umc.spring.member.controller; - -public class MemberController { -} diff --git a/src/main/java/umc/spring/member/domain/Member.java b/src/main/java/umc/spring/member/domain/Member.java deleted file mode 100644 index 6b228957e1f36ba1fcd58bd3aa1afa68ef6d25be..0000000000000000000000000000000000000000 --- a/src/main/java/umc/spring/member/domain/Member.java +++ /dev/null @@ -1,16 +0,0 @@ -package umc.spring.member.domain; - -import jakarta.persistence.*; -import lombok.Getter; - -@Entity -@Getter -public class Member { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String username; - -} diff --git a/src/main/java/umc/spring/member/dto/MemberRequestDto.java b/src/main/java/umc/spring/member/dto/MemberRequestDto.java deleted file mode 100644 index e7e87cd5666032d2bb0934193ca06ed9b1cbc9f8..0000000000000000000000000000000000000000 --- a/src/main/java/umc/spring/member/dto/MemberRequestDto.java +++ /dev/null @@ -1,4 +0,0 @@ -package umc.spring.member.dto; - -public class MemberRequestDto { -} diff --git a/src/main/java/umc/spring/member/repository/MemberRepository.java b/src/main/java/umc/spring/member/repository/MemberRepository.java deleted file mode 100644 index 8d22cc92a864bc5f2ca22bfecfd9d6cbbeb4e3be..0000000000000000000000000000000000000000 --- a/src/main/java/umc/spring/member/repository/MemberRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package umc.spring.member.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import umc.spring.member.domain.Member; - -public interface MemberRepository extends JpaRepository<Long, Member> { -} diff --git a/src/main/java/umc/spring/member/service/MemberService.java b/src/main/java/umc/spring/member/service/MemberService.java deleted file mode 100644 index 795b0162e686a5262fb4157570d6d771e37dbbdb..0000000000000000000000000000000000000000 --- a/src/main/java/umc/spring/member/service/MemberService.java +++ /dev/null @@ -1,4 +0,0 @@ -package umc.spring.member.service; - -public class MemberService { -} diff --git a/src/main/java/umc/spring/post/config/WebConfig.java b/src/main/java/umc/spring/post/config/WebConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..47fe215d791708e457df46c21a1183000e1921dc --- /dev/null +++ b/src/main/java/umc/spring/post/config/WebConfig.java @@ -0,0 +1,17 @@ +package umc.spring.post.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "DELETE") + .allowCredentials(false) + .maxAge(3000); + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/post/config/security/JwtAuthenticationFilter.java b/src/main/java/umc/spring/post/config/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..bc77b2a20bad8b4929fa9730364acc239cf8f6c9 --- /dev/null +++ b/src/main/java/umc/spring/post/config/security/JwtAuthenticationFilter.java @@ -0,0 +1,42 @@ +package umc.spring.post.config.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + + +@RequiredArgsConstructor +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + String token = resolveToken((HttpServletRequest) request); + if (token != null && jwtTokenProvider.validateToken(token)) { + Authentication authentication = jwtTokenProvider.getAuthentication(token); + System.out.println(authentication); + SecurityContextHolder.getContext().setAuthentication(authentication); + System.out.println(authentication); + } + chain.doFilter(request, response); + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer")) { + return bearerToken.substring(7); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/post/config/security/JwtTokenProvider.java b/src/main/java/umc/spring/post/config/security/JwtTokenProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..d96c1f616ca505da13ca7449e793fd52348aed62 --- /dev/null +++ b/src/main/java/umc/spring/post/config/security/JwtTokenProvider.java @@ -0,0 +1,95 @@ +package umc.spring.post.config.security; + +import io.jsonwebtoken.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.stream.Collectors; + +@Component +public class JwtTokenProvider { + + private final String secretKey; + + public JwtTokenProvider( @Value("${jwt.secret}") final String secretKey) { + this.secretKey = secretKey; + } + + public TokenInfo generateToken(Authentication authentication) { + System.out.println(authentication); + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + System.out.println("auth " + authorities); + long now = (new Date()).getTime(); + Date accessTokenExpiration = new Date(now + 3600000); // 1h + Date refreshTokenExpiration = new Date(now + 1209600000); // 14d + String accessToken = Jwts.builder() + .setSubject(authentication.getName()) + .claim("auth", authorities) + .setExpiration(accessTokenExpiration) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + + String refreshToken = Jwts.builder() + .setExpiration(refreshTokenExpiration) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + + TokenInfo tokenInfo = new TokenInfo(); + tokenInfo.setGrantType("Bearer"); + tokenInfo.setAccessToken(accessToken); + tokenInfo.setRefreshToken(refreshToken); + + return tokenInfo; + } + + public Authentication getAuthentication(String accessToken) { + Claims claims = parseClaims(accessToken); + System.out.println("log" + claims); + if (claims.get("auth") == null) { + throw new RuntimeException("권한 정보가 없는 토큰입니다."); + } + + Collection<? extends GrantedAuthority> authorities = + Arrays.stream(claims.get("auth").toString().split(",")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + UserDetails principal = new User(claims.getSubject(), "", authorities); + return new UsernamePasswordAuthenticationToken(principal, "", authorities); + } + + public boolean validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token); + return true; + } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { + System.out.println("Invalid JWT Token" + e); + } catch (ExpiredJwtException e) { + System.out.println("Expired JWT Token" + e); + } catch (UnsupportedJwtException e) { + System.out.println("Unsupported JWT Token" + e); + } catch (IllegalArgumentException e) { + System.out.println("JWT claims string is empty." + e); + } + return false; + } + + private Claims parseClaims(String accessToken) { + try { + return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(accessToken).getBody(); + } catch (ExpiredJwtException e) { + return e.getClaims(); + } + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/post/config/security/Role.java b/src/main/java/umc/spring/post/config/security/Role.java new file mode 100644 index 0000000000000000000000000000000000000000..2bfb3da667e4a525b102ce30943d24cf46d7f1c0 --- /dev/null +++ b/src/main/java/umc/spring/post/config/security/Role.java @@ -0,0 +1,16 @@ +package umc.spring.post.config.security; + +public enum Role { + USER("ROLE_USER"), + ADMIN("ROLE_ADMIN"); + + private final String value; + + public String getValue() { + return value; + } + + Role(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/post/config/security/SecurityConfig.java b/src/main/java/umc/spring/post/config/security/SecurityConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..1d26fefe2b3c847c306722b1148d3bb0eee61964 --- /dev/null +++ b/src/main/java/umc/spring/post/config/security/SecurityConfig.java @@ -0,0 +1,43 @@ +package umc.spring.post.config.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; + +@EnableWebSecurity +@Configuration +public class SecurityConfig { + + private final JwtAuthenticationFilter jwtAuthenticationFilter; + + @Autowired + public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) { + this.jwtAuthenticationFilter = jwtAuthenticationFilter; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.httpBasic(HttpBasicConfigurer::disable) + .csrf(CsrfConfigurer::disable) + .cors(Customizer.withDefaults()) + .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(authorize -> authorize.requestMatchers("/**").permitAll()) + .addFilterBefore(jwtAuthenticationFilter, BasicAuthenticationFilter.class); + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/post/config/security/SecurityUtil.java b/src/main/java/umc/spring/post/config/security/SecurityUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..5577af306438c6b9ff3bd963cbb575b3d975a408 --- /dev/null +++ b/src/main/java/umc/spring/post/config/security/SecurityUtil.java @@ -0,0 +1,22 @@ +package umc.spring.post.config.security; + + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import umc.spring.post.data.dto.UserInfoDto; + +public class SecurityUtil { + public static UserInfoDto getCurrentMemberId() { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || authentication.getName() == null) { + throw new RuntimeException("No authentication information."); + } + + UserInfoDto userInfoDto = new UserInfoDto(); + userInfoDto.setUserId(authentication.getName()); + userInfoDto.setMemberRole(authentication.getAuthorities().stream().toList().get(0).toString().replaceAll("ROLE_", "")); + + return userInfoDto; + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/post/config/security/TokenInfo.java b/src/main/java/umc/spring/post/config/security/TokenInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..7f9ad8464b611eb541568221dfd251116130c9b0 --- /dev/null +++ b/src/main/java/umc/spring/post/config/security/TokenInfo.java @@ -0,0 +1,52 @@ +package umc.spring.post.config.security; + +import lombok.Data; + +@Data +public class TokenInfo { + private String grantType; + private String accessToken; + private String refreshToken; + private String email; + private String memberRole; + + public String getGrantType() { + return grantType; + } + + public void setGrantType(String grantType) { + this.grantType = grantType; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getMemberRole() { + return memberRole; + } + + public void setMemberRole(String memberRole) { + this.memberRole = memberRole; + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/post/controller/AuthController.java b/src/main/java/umc/spring/post/controller/AuthController.java new file mode 100644 index 0000000000000000000000000000000000000000..14fc05b92468625f866aed7ac081b21e3213d58e --- /dev/null +++ b/src/main/java/umc/spring/post/controller/AuthController.java @@ -0,0 +1,37 @@ +package umc.spring.post.controller; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import umc.spring.post.config.security.TokenInfo; +import umc.spring.post.data.dto.UserInfoDto; +import umc.spring.post.data.dto.UserJoinDto; +import umc.spring.post.data.dto.UserLoginDto; +import umc.spring.post.service.AuthService; + +@RestController +@RequestMapping("/user") +public class AuthController { + + @Autowired + private final AuthService authService; + + public AuthController(AuthService authService) { + this.authService = authService; + } + + @PostMapping("/login") + public TokenInfo login(@RequestBody UserLoginDto userLoginDto) { + return authService.login(userLoginDto); + } + + @PostMapping("/register") + public void register(@RequestBody UserJoinDto userJoinDto) { + authService.join(userJoinDto); + } + + @GetMapping("/info") + public UserInfoDto info() { + return authService.info(); + } +} diff --git a/src/main/java/umc/spring/post/controller/PostController.java b/src/main/java/umc/spring/post/controller/PostController.java new file mode 100644 index 0000000000000000000000000000000000000000..ef71aec36276efc31c9238e8399fdbb2e7164457 --- /dev/null +++ b/src/main/java/umc/spring/post/controller/PostController.java @@ -0,0 +1,88 @@ +package umc.spring.post.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.transaction.Transactional; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; +import umc.spring.post.data.dto.CommentDto; +import umc.spring.post.data.dto.PostDto; +import umc.spring.post.data.dto.PostResDto; +import umc.spring.post.data.entity.Post; +import umc.spring.post.service.PostService; + +import java.util.List; + +import static org.springframework.data.jpa.domain.AbstractPersistable_.id; + +@RestController +public class PostController { + @Autowired + private final PostService postService; + public PostController(PostService postService) { + this.postService = postService; + } + @ResponseStatus(HttpStatus.OK) + @GetMapping("/posts") + public List<PostResDto> getAllPost(){ + return postService.getAllPost(); + } + + @ResponseStatus(HttpStatus.OK) + @PostMapping("/post/upload") + public void upload(@RequestBody PostDto postDto){ + postService.upload(postDto); + } + + @GetMapping("/post/{id}") + public PostResDto getPostById(@PathVariable Long id){ + return postService.getPostById(id); + } + + @DeleteMapping("/post/{id}") + @ResponseStatus(HttpStatus.OK) + public void deletePost(@PathVariable Long id){ + if(!postService.deletePost(id)){ + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found"); + } + } + @PutMapping("/post/{id}") + @ResponseStatus(HttpStatus.OK) + public void editPost(@RequestBody PostDto postDto,@PathVariable Long id){ + if(!postService.editPost(postDto,id)){ + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found"); + } + } + @PostMapping("/post/likes") + @ResponseStatus(HttpStatus.OK) + public void likeCrew(@RequestParam Long id){ + postService.likeCrew(id); + } + @DeleteMapping ("/post/likes") + @ResponseStatus(HttpStatus.OK) + public void dislikeCrew(@RequestParam Long id){ + postService.dislikeCrew(id); + } + + @PostMapping("/post/search") + public List<PostResDto> search(@RequestBody PostDto postDto){ + return postService.search(postDto.getTitle()); + } + + @PostMapping("/post/comments") + @ResponseStatus(HttpStatus.OK) + public void addComment(@RequestBody CommentDto commentDto){ + postService.addComment(commentDto); + } + + @DeleteMapping("/post/comments") + @ResponseStatus(HttpStatus.OK) + public void deleteComment(@RequestParam Long id){ + if(!postService.deleteComment(id)){ + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found"); + } + } +} diff --git a/src/main/java/umc/spring/post/data/dto/CommentDto.java b/src/main/java/umc/spring/post/data/dto/CommentDto.java new file mode 100644 index 0000000000000000000000000000000000000000..23567b60bb30a3f338bcc2b20e9a5d0d2e9658c0 --- /dev/null +++ b/src/main/java/umc/spring/post/data/dto/CommentDto.java @@ -0,0 +1,13 @@ +package umc.spring.post.data.dto; + +import lombok.Data; + +@Data +public class CommentDto { + + private Long postId; + private Long userId; + private String author; + private String text; + +} diff --git a/src/main/java/umc/spring/post/data/dto/CommentResDto.java b/src/main/java/umc/spring/post/data/dto/CommentResDto.java new file mode 100644 index 0000000000000000000000000000000000000000..18e0d6a33dd34d7041c051d6f0362450ed8fe492 --- /dev/null +++ b/src/main/java/umc/spring/post/data/dto/CommentResDto.java @@ -0,0 +1,31 @@ +package umc.spring.post.data.dto; + +import jakarta.persistence.Column; +import lombok.Builder; +import lombok.Data; +import umc.spring.post.data.entity.Comment; +import umc.spring.post.data.entity.Post; + +import java.util.Date; + +@Data +@Builder +public class CommentResDto { + private Long id; + private Long postId; + private Long userId; + private String author; + private String text; + private Date timestamp; + public static CommentResDto toDTO(Comment comment){ + + return CommentResDto.builder() + .id(comment.getId()) + .postId(comment.getPostId()) + .userId(comment.getUserId()) + .author(comment.getAuthor()) + .timestamp(comment.getTimestamp()) + .text(comment.getText()).build(); + } + +} diff --git a/src/main/java/umc/spring/post/data/dto/PostDto.java b/src/main/java/umc/spring/post/data/dto/PostDto.java new file mode 100644 index 0000000000000000000000000000000000000000..0b08be568bc8be224e3e939e50be738546d99b4a --- /dev/null +++ b/src/main/java/umc/spring/post/data/dto/PostDto.java @@ -0,0 +1,15 @@ +package umc.spring.post.data.dto; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +@Data +public class PostDto { + Long userId; + String title; + String author; + String body; + String image; + int likeCount; +} diff --git a/src/main/java/umc/spring/post/data/dto/PostResDto.java b/src/main/java/umc/spring/post/data/dto/PostResDto.java new file mode 100644 index 0000000000000000000000000000000000000000..48ef01fa89f5083de0cd0f76cf00652ae58d77e6 --- /dev/null +++ b/src/main/java/umc/spring/post/data/dto/PostResDto.java @@ -0,0 +1,45 @@ +package umc.spring.post.data.dto; + +import jakarta.persistence.*; +import lombok.*; +import umc.spring.post.data.entity.Comment; +import umc.spring.post.data.entity.Post; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@Data +@Builder +public class PostResDto { + Long id; + Long userId; + String title; + String author; + String body; + String image; + int likeCount; + Date createdTime; + Date modifiedTime; + List<CommentResDto> comments; + public static PostResDto toDTO(Post post){ + List<CommentResDto> resDtos = new ArrayList<>(); + post.getComments().forEach(comment -> { + CommentResDto dto = CommentResDto.toDTO(comment); + resDtos.add(dto); + }); + return PostResDto.builder() + .id(post.getId()) + .userId(post.getUserId()) + .title(post.getTitle()) + .author(post.getAuthor()) + .body(post.getBody()) + .image(post.getImage()) + .likeCount(post.getLikeCount()) + .createdTime(post.getCreatedTime()) + .modifiedTime(post.getModifiedTime()) + .comments(resDtos).build(); + } + + +} diff --git a/src/main/java/umc/spring/post/data/dto/UserInfoDto.java b/src/main/java/umc/spring/post/data/dto/UserInfoDto.java new file mode 100644 index 0000000000000000000000000000000000000000..bd96f34f7c68f7c9f9595e0d584eb977508201b6 --- /dev/null +++ b/src/main/java/umc/spring/post/data/dto/UserInfoDto.java @@ -0,0 +1,14 @@ +package umc.spring.post.data.dto; + +import lombok.*; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString +@Builder +public class UserInfoDto { + private String userId; + private String memberRole; + +} \ No newline at end of file diff --git a/src/main/java/umc/spring/post/data/dto/UserJoinDto.java b/src/main/java/umc/spring/post/data/dto/UserJoinDto.java new file mode 100644 index 0000000000000000000000000000000000000000..a6701bdb5684ff513155523db1deb89f5f0c4f99 --- /dev/null +++ b/src/main/java/umc/spring/post/data/dto/UserJoinDto.java @@ -0,0 +1,10 @@ +package umc.spring.post.data.dto; + +import lombok.Data; + +@Data +public class UserJoinDto { + private String userId; + private String password; + private String userName; +} \ No newline at end of file diff --git a/src/main/java/umc/spring/post/data/dto/UserLoginDto.java b/src/main/java/umc/spring/post/data/dto/UserLoginDto.java new file mode 100644 index 0000000000000000000000000000000000000000..24d523d76942bef96ec20648f0dcffc5dd569848 --- /dev/null +++ b/src/main/java/umc/spring/post/data/dto/UserLoginDto.java @@ -0,0 +1,9 @@ +package umc.spring.post.data.dto; + +import lombok.Data; + +@Data +public class UserLoginDto { + String userId; + String password; +} diff --git a/src/main/java/umc/spring/post/data/entity/Comment.java b/src/main/java/umc/spring/post/data/entity/Comment.java new file mode 100644 index 0000000000000000000000000000000000000000..92aab2c6c7b6ead7bb5c76e333f26438d6e82bcc --- /dev/null +++ b/src/main/java/umc/spring/post/data/entity/Comment.java @@ -0,0 +1,36 @@ +package umc.spring.post.data.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +@Entity +@Getter @Setter +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long id; + + @Column(nullable = false) + private Long userId; + + @Column(nullable = false) + private Long postId; + + @Column(nullable = false) + private String author; + + @Column(nullable = false) + private String text; + + @ManyToOne(fetch=FetchType.EAGER) + @JoinColumn(name = "postId", nullable = false,insertable=false, updatable=false) + private Post post; + + @Column(nullable = false) + private Date timestamp; + +} diff --git a/src/main/java/umc/spring/post/data/entity/Post.java b/src/main/java/umc/spring/post/data/entity/Post.java new file mode 100644 index 0000000000000000000000000000000000000000..c3df9aa1cab46842779a100279057bfd8e814822 --- /dev/null +++ b/src/main/java/umc/spring/post/data/entity/Post.java @@ -0,0 +1,49 @@ +package umc.spring.post.data.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@Entity +@Table(name = "Post") +@Getter @Setter +public class Post{ + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long id; + + @Column(nullable = false) + private Long userId; + + @Column(nullable = false) + private String title; + + @Column(nullable = false) + private String body; + + @Column(nullable = false) + private String image; + + @Column(nullable = false) + private int likeCount; + + @Column(nullable = false) + private String author; + + @Column(nullable = true) + private Date createdTime; + + @Column(nullable = true) + private Date modifiedTime; + + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL,fetch = FetchType.EAGER, + orphanRemoval = true) + private List<Comment> comments = new ArrayList<>(); + +} diff --git a/src/main/java/umc/spring/post/data/entity/User.java b/src/main/java/umc/spring/post/data/entity/User.java new file mode 100644 index 0000000000000000000000000000000000000000..188308b9f9a9777dd8b23a4b506ec7c9ea2a776e --- /dev/null +++ b/src/main/java/umc/spring/post/data/entity/User.java @@ -0,0 +1,75 @@ +package umc.spring.post.data.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import umc.spring.post.config.security.Role; + +import java.util.ArrayList; +import java.util.Collection; + +//@Builder +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Data +@Table(name="user") +public class User implements UserDetails { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long id; + + @Column(nullable = false) + private String userId; + + @Column(nullable = false) + private String password; + + @Column(nullable = false) + private String userName; + + @Enumerated(EnumType.STRING) + private Role role = Role.USER; + + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + Collection<GrantedAuthority> authorities = new ArrayList<>(); + System.out.println("entity " + authorities); + authorities.add(new SimpleGrantedAuthority(role.toString())); + System.out.println("entity " + authorities); + return authorities; + } + + @Override + public String getUsername() { + return this.userName; + } + + @Override + public boolean isAccountNonExpired() { + return false; + } + + @Override + public boolean isAccountNonLocked() { + return false; + } + + @Override + public boolean isCredentialsNonExpired() { + return false; + } + + @Override + public boolean isEnabled() { + return false; + } + + +} diff --git a/src/main/java/umc/spring/post/repository/CommentRepository.java b/src/main/java/umc/spring/post/repository/CommentRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..4515af9f849d52863309c4ac73595c0b89015022 --- /dev/null +++ b/src/main/java/umc/spring/post/repository/CommentRepository.java @@ -0,0 +1,7 @@ +package umc.spring.post.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import umc.spring.post.data.entity.Comment; + +public interface CommentRepository extends JpaRepository<Comment,Long> { +} diff --git a/src/main/java/umc/spring/post/repository/PostRepository.java b/src/main/java/umc/spring/post/repository/PostRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..a524e37c6df610e305433125ef1d3334929ff3cb --- /dev/null +++ b/src/main/java/umc/spring/post/repository/PostRepository.java @@ -0,0 +1,9 @@ +package umc.spring.post.repository; + + +import org.springframework.data.jpa.repository.JpaRepository; +import umc.spring.post.data.entity.Post; + +public interface PostRepository extends JpaRepository<Post,Long> { + +} diff --git a/src/main/java/umc/spring/post/repository/UserRepository.java b/src/main/java/umc/spring/post/repository/UserRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..870af44442d65b4815c19c0c76d588da609ffc58 --- /dev/null +++ b/src/main/java/umc/spring/post/repository/UserRepository.java @@ -0,0 +1,10 @@ +package umc.spring.post.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import umc.spring.post.data.entity.User; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository<User, Long> { + Optional<User> findByUserId(String userId); +} diff --git a/src/main/java/umc/spring/post/service/AuthService.java b/src/main/java/umc/spring/post/service/AuthService.java new file mode 100644 index 0000000000000000000000000000000000000000..ee097e894d5ea6685aae319d3228ca593aa98b8c --- /dev/null +++ b/src/main/java/umc/spring/post/service/AuthService.java @@ -0,0 +1,15 @@ +package umc.spring.post.service; + + +import umc.spring.post.config.security.TokenInfo; +import umc.spring.post.data.dto.UserInfoDto; +import umc.spring.post.data.dto.UserJoinDto; +import umc.spring.post.data.dto.UserLoginDto; + +public interface AuthService { + TokenInfo login(UserLoginDto userLoginDto); + + void join(UserJoinDto userJoinDto); + + UserInfoDto info(); +} diff --git a/src/main/java/umc/spring/post/service/AuthServiceImpl.java b/src/main/java/umc/spring/post/service/AuthServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..1880a4e3ff792acdd8d845e39b59e764208f9e37 --- /dev/null +++ b/src/main/java/umc/spring/post/service/AuthServiceImpl.java @@ -0,0 +1,90 @@ +package umc.spring.post.service; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + + + +import umc.spring.post.data.entity.User; +import umc.spring.post.config.security.JwtTokenProvider; +import umc.spring.post.config.security.Role; +import umc.spring.post.config.security.SecurityUtil; +import umc.spring.post.config.security.TokenInfo; +import umc.spring.post.data.dto.UserInfoDto; +import umc.spring.post.data.dto.UserJoinDto; +import umc.spring.post.data.dto.UserLoginDto; +import umc.spring.post.repository.UserRepository; + + +@Service +public class AuthServiceImpl implements AuthService, UserDetailsService { + + @Autowired + private final UserRepository userRepository; + + @Autowired + private final PasswordEncoder passwordEncoder; + + @Autowired + private final JwtTokenProvider jwtTokenProvider; + + public AuthServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder, JwtTokenProvider jwtTokenProvider) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + public TokenInfo login(UserLoginDto userLoginDto) { + User user = userRepository.findByUserId(userLoginDto.getUserId()).orElseThrow(() -> new UsernameNotFoundException("아이디 혹은 비밀번호를 확인하세요.")); + + boolean matches = passwordEncoder.matches(userLoginDto.getPassword(), user.getPassword()); + if (!matches) throw new BadCredentialsException("아이디 혹은 비밀번호를 확인하세요."); + + Authentication authentication = new UsernamePasswordAuthenticationToken(user.getUserId(), user.getPassword(), user.getAuthorities()); + + TokenInfo tokenInfo = jwtTokenProvider.generateToken(authentication); + tokenInfo.setEmail(user.getUserId()); + + tokenInfo.setMemberRole(user.getRole().toString()); + return tokenInfo; + } + + @Override + public void join(UserJoinDto userJoinDto) { + User user = new User(); + user.setUserId(userJoinDto.getUserId()); + user.setPassword(passwordEncoder.encode(userJoinDto.getPassword())); + user.setUserName(userJoinDto.getUserName()); + userRepository.save(user); + } + + @Override + public UserInfoDto info() { + UserInfoDto userInfoDto = SecurityUtil.getCurrentMemberId(); + return userInfoDto; + } + + @Override + public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { + return userRepository.findByUserId(userId) + .map(this::createUserDetails) + .orElseThrow(() -> new UsernameNotFoundException("해당하는 유저를 찾을 수 없습니다.")); + } + + private UserDetails createUserDetails(User user) { + return org.springframework.security.core.userdetails.User.builder() + .username(user.getUsername()) + .password(passwordEncoder.encode(user.getPassword())) + .roles(user.getRole().toString()) + .build(); + } +} diff --git a/src/main/java/umc/spring/post/service/PostService.java b/src/main/java/umc/spring/post/service/PostService.java new file mode 100644 index 0000000000000000000000000000000000000000..b3382a7aa1cda94e07ef117a6ed803a7ab1b46c1 --- /dev/null +++ b/src/main/java/umc/spring/post/service/PostService.java @@ -0,0 +1,35 @@ +package umc.spring.post.service; + + +import jakarta.servlet.http.HttpServletResponse; +import umc.spring.post.data.dto.CommentDto; +import umc.spring.post.data.dto.PostDto; +import umc.spring.post.data.dto.PostResDto; +import umc.spring.post.data.entity.Post; + +import java.io.IOException; +import java.util.List; + +public interface PostService { + void upload(PostDto postDto); + + List<PostResDto> getAllPost(); + + PostResDto getPostById(Long id); + + void likeCrew(Long id); + + void dislikeCrew(Long id); + + boolean deletePost(Long id); + + + boolean editPost(PostDto postDto, Long id); + + List<PostResDto> search(String title); + + + void addComment(CommentDto commentDto); + + boolean deleteComment(Long id); +} diff --git a/src/main/java/umc/spring/post/service/PostServiceImpl.java b/src/main/java/umc/spring/post/service/PostServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..40082cc0328f460cc1a9d2962578f9d3991aa8d7 --- /dev/null +++ b/src/main/java/umc/spring/post/service/PostServiceImpl.java @@ -0,0 +1,160 @@ +package umc.spring.post.service; + + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import umc.spring.post.data.dto.CommentDto; +import umc.spring.post.data.dto.PostDto; +import umc.spring.post.data.dto.PostResDto; +import umc.spring.post.data.entity.Comment; +import umc.spring.post.data.entity.Post; +import umc.spring.post.repository.CommentRepository; +import umc.spring.post.repository.PostRepository; +import umc.spring.post.repository.UserRepository; + +import java.io.IOException; +import java.util.*; + +@Service +public class PostServiceImpl implements PostService{ + + @Autowired + private final PostRepository postRepository; + + @Autowired + private final CommentRepository commentRepository; + + public PostServiceImpl(PostRepository postRepository, CommentRepository commentRepository) { + this.postRepository = postRepository; + this.commentRepository = commentRepository; + } + + @Override + public void upload(PostDto postDto){ + Post post = new Post(); + setPost(postDto, post); + post.setUserId(postDto.getUserId()); + post.setCreatedTime((new Date())); + post.setModifiedTime(post.getCreatedTime()); + postRepository.save(post); + } + + @Override + public List<PostResDto> getAllPost(){ + List<Post> posts = postRepository.findAll(); + List<PostResDto> resDtos = new ArrayList<>(); + posts.forEach(post -> { + PostResDto dto = PostResDto.toDTO(post); + resDtos.add(dto); + }); + return resDtos; + } + + + @Override + public PostResDto getPostById(Long id){ + Post post = postRepository.findById(id).get(); + return PostResDto.toDTO(post); + } + + @Override + public void likeCrew(Long id) { + Post post = postRepository.findById(id).get(); + int likeCount = post.getLikeCount(); + post.setLikeCount(++likeCount); + postRepository.save(post); + } + + @Override + public void dislikeCrew(Long id) { + Post post = postRepository.findById(id).get(); + int likeCount = post.getLikeCount(); + if(likeCount!=0){ + post.setLikeCount(--likeCount); + postRepository.save(post); + + } + } + + @Override + public boolean deletePost(Long id) { + if(postRepository.findById(id).isPresent()){ + postRepository.deleteById(id); + return true; + } + else return false; + } + + @Override + public boolean editPost(PostDto postDto, Long id) { + Post post = postRepository.findById(id).get(); + if(post!=null){ + post.setTitle(postDto.getTitle() != null ? postDto.getTitle() : post.getTitle()); + post.setBody(postDto.getBody() != null ? postDto.getBody() : post.getBody()); + post.setImage(postDto.getImage() != null ? postDto.getImage() : post.getImage()); + post.setModifiedTime(new Date()); + postRepository.save(post); + return true; + } + else return false; + } + + @Override + public List<PostResDto> search(String title) { + List<Post> postList = postRepository.findAll(); + List<PostResDto> findList = new ArrayList<>(); + + postList.forEach(post->{ + if(post.getTitle().equals(title)){ + findList.add(PostResDto.toDTO(post)); + } + }); + return findList; + } + + @Override + public void addComment(CommentDto commentDto){ + Comment comment = setComment(commentDto); + commentRepository.save(comment); + } + + @Override + public boolean deleteComment(Long id) { + Optional<Comment> option = commentRepository.findById(id); + if(option.isPresent()){ + Comment comment = option.get(); + Post post = comment.getPost(); + if(post!=null){ + post.getComments().removeIf(data -> + data.getId().equals(id) + ); + postRepository.save(post); + } + return true; + } + else return false; + } + + + private Comment setComment(CommentDto commentDto) { + Comment comment = new Comment(); + Post post = postRepository.findById(commentDto.getPostId()).get(); + post.getComments().add(comment); + comment.setPost(post); + comment.setUserId(commentDto.getUserId()); + comment.setTimestamp(new Date()); + comment.setText(commentDto.getText()); + comment.setAuthor(commentDto.getAuthor()); + comment.setPostId(commentDto.getPostId()); + return comment; + } + + private static void setPost(PostDto postDto, Post post) { + post.setTitle(postDto.getTitle()); + post.setBody(postDto.getBody()); + post.setAuthor(postDto.getAuthor()); + post.setLikeCount(postDto.getLikeCount()); + post.setImage(postDto.getImage()); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8c4b895e108a87bb4c276977e08781ce43f5b675..dd5fcda00e209e6416c39cc3fd0dc1e834b1c1de 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,13 +1,28 @@ # mySql +#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +#spring.datasource.url=jdbc:mysql://localhost:3000/study?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&serverTimezone=UTC +#spring.datasource.username=root +#spring.datasource.password=1234 +#spring.jpa.properties.hibernate.jdbc.batch_size=100 + spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.datasource.url=jdbc:mysql://localhost:3000/study?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&serverTimezone=UTC +spring.datasource.url=jdbc:mysql://localhost:3306/umc spring.datasource.username=root -spring.datasource.password=1234 -spring.jpa.properties.hibernate.jdbc.batch_size=100 +spring.datasource.password=6235 + +spring.jpa.properties.hibernate.show-sql=true + + +logging.level.org.hibernate.type.descriptor.sql=DEBUG +logging.level.org.hibernate.SQL=DEBUG +spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER # jpa spring.jpa.database=mysql spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.format_sql=true -spring.jpa.open-in-view=false \ No newline at end of file +spring.jpa.open-in-view=false + +jwt.secret = rutyweorituwyerotiuweyrtoiuweyrtoweiurtywoeighdfsojkghsdfgsdofiguwyertouw +jwt.expirationtime = 3600