Add user, post 게시판 기능 추가

This commit is contained in:
Daeil Choi
2023-02-02 18:01:03 +09:00
parent 8a6acc9dfc
commit 41c2dc134a
29 changed files with 711 additions and 90 deletions

View File

@@ -0,0 +1,9 @@
package com.example.springsecuritystudy.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@Configuration
@EnableJpaAuditing
public class AuditorConfig {
}

View File

@@ -0,0 +1,16 @@
package com.example.springsecuritystudy.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/home").setViewName("index");
registry.addViewController("/admin").setViewName("admin/index");
registry.addViewController("/login").setViewName("login");
}
}

View File

@@ -1,4 +1,4 @@
package com.example.springsecuritystudy;
package com.example.springsecuritystudy.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

View File

@@ -1,15 +1,18 @@
package com.example.springsecuritystudy;
package com.example.springsecuritystudy.config;
import org.springframework.context.annotation.Bean;
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.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.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.example.springsecuritystudy.user.UserRepository;
import lombok.RequiredArgsConstructor;
@@ -18,24 +21,37 @@ import lombok.RequiredArgsConstructor;
public class SecurityConfig {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.antMatchers("/", "/example").permitAll()
.antMatchers("/user").hasRole("USER")
.antMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.authorizeHttpRequests(auth -> auth
.antMatchers("/", "/home", "/signup", "/example").permitAll()
.antMatchers("/post").hasRole("USER")
.antMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults())
.logout()
.logoutSuccessUrl("/login")
;
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll()
)
.logout(logout -> logout
.deleteCookies("remove")
.invalidateHttpSession(false)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
);
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring().antMatchers("/css/**", "/js/**", "/h2-console/**");
}
@Bean
public UserDetailsService users() {
UserDetails user = User.withUsername("user")
@@ -46,6 +62,10 @@ public class SecurityConfig {
.password(passwordEncoder.encode("admin"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
UserDetails tester = User.withUsername("test")
.password(passwordEncoder.encode("test"))
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin, tester);
}
}

View File

@@ -1,21 +1,11 @@
package com.example.springsecuritystudy;
package com.example.springsecuritystudy.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/user")
public String user() {
return "user";
}
@GetMapping("/admin")
public String admin() {
return "admin";
}
public class SampleController {
@GetMapping("/example")
public String example(Model model) {

View File

@@ -0,0 +1,32 @@
package com.example.springsecuritystudy.model;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.ToString;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
@ToString(callSuper = true)
public abstract class BaseTimeEntity {
@JsonFormat(timezone = "Asia/Seoul")
@CreatedDate
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
@JsonFormat(timezone = "Asia/Seoul")
@LastModifiedDate
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,46 @@
package com.example.springsecuritystudy.post;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import com.example.springsecuritystudy.model.BaseTimeEntity;
import com.example.springsecuritystudy.user.User;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Table
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post extends BaseTimeEntity {
@Id
@GeneratedValue
private Long id;
private String title;
@Lob
private String content;
@Enumerated(EnumType.STRING)
private PostStatus status;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "USER_ID")
private User user;
public Post(String title, String content, User user) {
this.title = title;
this.content = content;
this.status = PostStatus.Y;
this.user = user;
}
}

View File

@@ -0,0 +1,42 @@
package com.example.springsecuritystudy.post;
import java.security.Principal;
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 lombok.RequiredArgsConstructor;
@Controller
@RequiredArgsConstructor
@RequestMapping("/post")
public class PostController {
private final PostService postService;
@GetMapping
public String findByPost(Principal principal, Model model) {
List<Post> posts = postService.findByUserName(principal.getName());
model.addAttribute("posts", posts);
return "post/index";
}
@PostMapping
public String savePost(@ModelAttribute PostDto postDto, Principal principal) {
postService.savePost(principal.getName(), postDto.getTitle(), postDto.getContent());
return "redirect:post";
}
@DeleteMapping
public String deletePost(@RequestParam Long id, Principal principal) {
postService.deletePost(principal.getName(), id);
return "redirect:post";
}
}

View File

@@ -0,0 +1,12 @@
package com.example.springsecuritystudy.post;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class PostDto {
private String title;
private String content;
}

View File

@@ -0,0 +1,14 @@
package com.example.springsecuritystudy.post;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.springsecuritystudy.user.User;
public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findByUserAndStatus(User user, PostStatus status);
Post findByIdAndUser(Long id, User user);
}

View File

@@ -0,0 +1,43 @@
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;
import com.example.springsecuritystudy.user.User;
import com.example.springsecuritystudy.user.UserRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class PostService {
private final UserRepository userRepository;
private final PostRepository postRepository;
@Transactional(readOnly = true)
public List<Post> findByUserName(String username) {
User user = getUser(username);
return postRepository.findByUserAndStatus(user, PostStatus.Y);
}
public Post savePost(String username, String title, String content) {
User user = getUser(username);
return postRepository.save(new Post(title, content, user));
}
public void deletePost(String username, Long id) {
User user = getUser(username);
Post post = postRepository.findByIdAndUser(id, user);
postRepository.delete(post);
}
private User getUser(String username) {
return userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("유저가 없습니다."));
}
}

View File

@@ -0,0 +1,6 @@
package com.example.springsecuritystudy.post;
public enum PostStatus {
Y,
N
}

View File

@@ -0,0 +1,61 @@
package com.example.springsecuritystudy.user;
import java.util.Collection;
import java.util.Collections;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "users")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User implements UserDetails {
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
private String authority;
public User(String username, String password, String authority) {
this.username = username;
this.password = password;
this.authority = authority;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton((GrantedAuthority) () -> authority);
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@@ -0,0 +1,28 @@
package com.example.springsecuritystudy.user;
import org.springframework.stereotype.Controller;
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 lombok.RequiredArgsConstructor;
@Controller
@RequiredArgsConstructor
@RequestMapping("/signup")
public class UserController {
private final UserService userService;
@GetMapping
public String signupView() {
return "signup";
}
@PostMapping
public String signup(@ModelAttribute UserDto userDto) {
userService.signup(userDto.getUsername(), userDto.getPassword());
return "redirect:login";
}
}

View File

@@ -0,0 +1,15 @@
package com.example.springsecuritystudy.user;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserDto {
private String username;
private String password;
}

View File

@@ -0,0 +1,9 @@
package com.example.springsecuritystudy.user;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}

View File

@@ -0,0 +1,35 @@
package com.example.springsecuritystudy.user;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public User signup(String username, String password) {
if (userRepository.findByUsername(username).isPresent()) {
throw new RuntimeException("이미 등록된 유저입니다.");
}
return userRepository.save(new User(username, passwordEncoder.encode(password), "ROLE_USER"));
}
public User signupAdmin(String username, String password) {
if (userRepository.findByUsername(username).isPresent()) {
throw new RuntimeException("이미 등록된 Admin유저입니다.");
}
return userRepository.save(new User(username, passwordEncoder.encode(password), "ROLE_ADMIN"));
}
public User findByUsername(String username) {
return userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("존재하지 않는 유저입니다."));
}
}