diff --git a/build.gradle b/build.gradle index 97adef8..2fbc11d 100644 --- a/build.gradle +++ b/build.gradle @@ -25,11 +25,13 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' compileOnly 'org.projectlombok:lombok' - developmentOnly 'org.springframework.boot:spring-boot-devtools' +// developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' } tasks.named('test') { diff --git a/src/main/java/com/example/springsecuritystudy/config/MvcConfig.java b/src/main/java/com/example/springsecuritystudy/config/MvcConfig.java index 4e92ac7..f4fa6fe 100644 --- a/src/main/java/com/example/springsecuritystudy/config/MvcConfig.java +++ b/src/main/java/com/example/springsecuritystudy/config/MvcConfig.java @@ -1,6 +1,8 @@ package com.example.springsecuritystudy.config; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -13,4 +15,9 @@ public class MvcConfig implements WebMvcConfigurer { registry.addViewController("/admin").setViewName("admin/index"); registry.addViewController("/login").setViewName("login"); } + + @Bean + public HiddenHttpMethodFilter hiddenHttpMethodFilter(){ + return new HiddenHttpMethodFilter(); + } } diff --git a/src/main/java/com/example/springsecuritystudy/config/SecurityConfig.java b/src/main/java/com/example/springsecuritystudy/config/SecurityConfig.java index f97e0fb..5155e6c 100644 --- a/src/main/java/com/example/springsecuritystudy/config/SecurityConfig.java +++ b/src/main/java/com/example/springsecuritystudy/config/SecurityConfig.java @@ -1,14 +1,12 @@ package com.example.springsecuritystudy.config; import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; 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.configuration.WebSecurityCustomizer; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -20,28 +18,31 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class SecurityConfig { - private final PasswordEncoder passwordEncoder; private final UserRepository userRepository; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth - .antMatchers("/", "/home", "/signup", "/example").permitAll() - .antMatchers("/post").hasRole("USER") - .antMatchers("/admin").hasRole("ADMIN") - .anyRequest().authenticated() + .antMatchers("/", "/home", "/signup", "/example", + "/css/**", "/js/**", "/h2-console/**").permitAll() + .antMatchers("/post").hasRole("USER") + .antMatchers("/admin").hasRole("ADMIN") + .antMatchers(HttpMethod.POST, "/notice").hasRole("ADMIN") + .antMatchers(HttpMethod.DELETE, "/notice").hasRole("ADMIN") + .anyRequest().authenticated() ) .formLogin(form -> form - .loginPage("/login") - .defaultSuccessUrl("/") - .permitAll() + .loginPage("/login") + .defaultSuccessUrl("/") + .permitAll() ) .logout(logout -> logout - .deleteCookies("remove") - .invalidateHttpSession(false) - .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) - .logoutSuccessUrl("/") + // .logoutUrl("/logout") // post 방식으로만 동작 + .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) // get 방식으로도 동작 + .logoutSuccessUrl("/") + .deleteCookies("JSESSIONID") + .invalidateHttpSession(true) ); return http.build(); @@ -53,19 +54,8 @@ public class SecurityConfig { } @Bean - public UserDetailsService users() { - UserDetails user = User.withUsername("user") - .password(passwordEncoder.encode("user")) - .roles("USER") - .build(); - UserDetails admin = User.withUsername("admin") - .password(passwordEncoder.encode("admin")) - .roles("ADMIN") - .build(); - UserDetails tester = User.withUsername("test") - .password(passwordEncoder.encode("test")) - .roles("ADMIN", "USER") - .build(); - return new InMemoryUserDetailsManager(user, admin, tester); + public UserDetailsService userDetailsService() { + return username -> userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("유저를 찾지 못 했습니다.")); } } diff --git a/src/main/java/com/example/springsecuritystudy/notice/Notice.java b/src/main/java/com/example/springsecuritystudy/notice/Notice.java new file mode 100644 index 0000000..68c40bb --- /dev/null +++ b/src/main/java/com/example/springsecuritystudy/notice/Notice.java @@ -0,0 +1,36 @@ +package com.example.springsecuritystudy.notice; + +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; + +import com.example.springsecuritystudy.model.BaseTimeEntity; +import com.example.springsecuritystudy.post.PostStatus; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Notice extends BaseTimeEntity { + + @Id + @GeneratedValue + private Long id; + private String title; + @Lob + private String content; + @Enumerated(EnumType.STRING) + private PostStatus status; + + public Notice(String title, String content) { + this.title = title; + this.content = content; + this.status = PostStatus.Y; + } +} diff --git a/src/main/java/com/example/springsecuritystudy/notice/NoticeController.java b/src/main/java/com/example/springsecuritystudy/notice/NoticeController.java new file mode 100644 index 0000000..879d49e --- /dev/null +++ b/src/main/java/com/example/springsecuritystudy/notice/NoticeController.java @@ -0,0 +1,43 @@ +package com.example.springsecuritystudy.notice; + +import java.util.List; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import com.example.springsecuritystudy.post.PostDto; + +import lombok.RequiredArgsConstructor; + +@Controller +@RequiredArgsConstructor +@RequestMapping("/notice") +public class NoticeController { + + private final NoticeService noticeService; + + @GetMapping + public String findByPost(Model model) { + List notices = noticeService.findAll(); + model.addAttribute("notices", notices); + return "notice/index"; + } + + @PostMapping + public String savePost(@ModelAttribute PostDto postDto) { + noticeService.saveNotice(postDto.getTitle(), postDto.getContent()); + return "redirect:notice"; + } + + @DeleteMapping + public String deletePost(@RequestParam Long id) { + noticeService.deleteNotice(id); + return "redirect:notice"; + } +} diff --git a/src/main/java/com/example/springsecuritystudy/notice/NoticeRepository.java b/src/main/java/com/example/springsecuritystudy/notice/NoticeRepository.java new file mode 100644 index 0000000..34e2682 --- /dev/null +++ b/src/main/java/com/example/springsecuritystudy/notice/NoticeRepository.java @@ -0,0 +1,6 @@ +package com.example.springsecuritystudy.notice; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface NoticeRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/springsecuritystudy/notice/NoticeService.java b/src/main/java/com/example/springsecuritystudy/notice/NoticeService.java new file mode 100644 index 0000000..417cb98 --- /dev/null +++ b/src/main/java/com/example/springsecuritystudy/notice/NoticeService.java @@ -0,0 +1,29 @@ +package com.example.springsecuritystudy.notice; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional +public class NoticeService { + + private final NoticeRepository noticeRepository; + + @Transactional(readOnly = true) + public List findAll() { + return noticeRepository.findAll(); + } + + public Notice saveNotice(String title, String content) { + return noticeRepository.save(new Notice(title, content)); + } + + public void deleteNotice(Long id) { + noticeRepository.findById(id).ifPresent(noticeRepository::delete); + } +} diff --git a/src/main/java/com/example/springsecuritystudy/post/PostController.java b/src/main/java/com/example/springsecuritystudy/post/PostController.java index 3078679..31e9f97 100644 --- a/src/main/java/com/example/springsecuritystudy/post/PostController.java +++ b/src/main/java/com/example/springsecuritystudy/post/PostController.java @@ -1,8 +1,8 @@ package com.example.springsecuritystudy.post; -import java.security.Principal; import java.util.List; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.DeleteMapping; @@ -12,6 +12,8 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import com.example.springsecuritystudy.user.User; + import lombok.RequiredArgsConstructor; @Controller @@ -22,21 +24,24 @@ public class PostController { private final PostService postService; @GetMapping - public String findByPost(Principal principal, Model model) { - List posts = postService.findByUserName(principal.getName()); + public String findByPost(Authentication authentication, Model model) { + User user = (User) authentication.getPrincipal(); + List posts = postService.findByUser(user); model.addAttribute("posts", posts); return "post/index"; } @PostMapping - public String savePost(@ModelAttribute PostDto postDto, Principal principal) { - postService.savePost(principal.getName(), postDto.getTitle(), postDto.getContent()); + public String savePost(@ModelAttribute PostDto postDto, Authentication authentication) { + User user = (User) authentication.getPrincipal(); + postService.savePost(user, postDto.getTitle(), postDto.getContent()); return "redirect:post"; } @DeleteMapping - public String deletePost(@RequestParam Long id, Principal principal) { - postService.deletePost(principal.getName(), id); + public String deletePost(@RequestParam Long id, Authentication authentication) { + User user = (User) authentication.getPrincipal(); + postService.deletePost(user, id); return "redirect:post"; } } diff --git a/src/main/java/com/example/springsecuritystudy/post/PostService.java b/src/main/java/com/example/springsecuritystudy/post/PostService.java index 03ddfa1..50bb189 100644 --- a/src/main/java/com/example/springsecuritystudy/post/PostService.java +++ b/src/main/java/com/example/springsecuritystudy/post/PostService.java @@ -2,7 +2,6 @@ package com.example.springsecuritystudy.post; import java.util.List; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -13,31 +12,33 @@ import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor +@Transactional public class PostService { private final UserRepository userRepository; private final PostRepository postRepository; @Transactional(readOnly = true) - public List findByUserName(String username) { - User user = getUser(username); + public List findByUser(User user) { + userNullCheck(user); return postRepository.findByUserAndStatus(user, PostStatus.Y); } - public Post savePost(String username, String title, String content) { - User user = getUser(username); + private static void userNullCheck(User user) { + if (user == null) { + throw new RuntimeException("유저가 없습니다."); + } + } + + public Post savePost(User user, String title, String content) { + userNullCheck(user); return postRepository.save(new Post(title, content, user)); } - public void deletePost(String username, Long id) { - User user = getUser(username); + public void deletePost(User user, Long id) { + userNullCheck(user); Post post = postRepository.findByIdAndUser(id, user); postRepository.delete(post); } - private User getUser(String username) { - return userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("유저가 없습니다.")); - } - } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index eac2654..754ae56 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,14 +1,9 @@ server: port: 8080 - mvc: - hiddenmethod: - filter: - enabled: true - logging: level: root: info org.springframework.web: debug sql: error - org.springframework.security: debug +# org.springframework.security: debug diff --git a/src/main/resources/static/css/signin.css b/src/main/resources/static/css/signin.css index 1f8e559..aa087ab 100644 --- a/src/main/resources/static/css/signin.css +++ b/src/main/resources/static/css/signin.css @@ -29,4 +29,4 @@ margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; -} \ No newline at end of file +} diff --git a/src/main/resources/templates/fragments.html b/src/main/resources/templates/fragments.html index 2ff9df0..085b936 100644 --- a/src/main/resources/templates/fragments.html +++ b/src/main/resources/templates/fragments.html @@ -16,6 +16,14 @@ rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.6/css/all.css" > + @@ -27,6 +35,7 @@ +