diff --git a/build.gradle b/build.gradle index 1a785f5..57ec9a2 100644 --- a/build.gradle +++ b/build.gradle @@ -25,15 +25,20 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' + /* + thymeleaf + */ + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + /* Data JPA */ - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' /* Lombok */ - compileOnly 'org.projectlombok:lombok' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' /* diff --git a/src/main/java/demo/api/config/SecurityConfig.java b/src/main/java/demo/api/config/SecurityConfig.java index 729d927..edece9c 100644 --- a/src/main/java/demo/api/config/SecurityConfig.java +++ b/src/main/java/demo/api/config/SecurityConfig.java @@ -1,11 +1,14 @@ package demo.api.config; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /** @@ -14,7 +17,9 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; * @Configuration 애노테이션 대신 @EnableWebSecurity 애노테이션을 추가한다. */ @EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter { + private final UserDetailsService userDetailsService; /** * PasswordEncoder를 Bean으로 등록 @@ -32,6 +37,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { // web.ignoring().antMatchers("/?/**"); // } + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); + } + /** * 인증 or 인가에 대한 설정 */ @@ -39,9 +49,16 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() - .formLogin().disable()//loginPage("/user/signIn") + .formLogin() + .loginPage("/user/signIn") + .loginProcessingUrl("/user/signInProc") + .usernameParameter("email") + .passwordParameter("password") + .defaultSuccessUrl("/") + .failureUrl("/user/signIn?fail=true"); + http .authorizeRequests() - .antMatchers("/", "/user/signUp").permitAll() + .antMatchers("/", "/user/signUp", "/user/userList", "/user/signIn*").permitAll() .anyRequest().authenticated(); } } diff --git a/src/main/java/demo/api/config/UserDetailsServiceImpl.java b/src/main/java/demo/api/config/UserDetailsServiceImpl.java new file mode 100644 index 0000000..e51fb68 --- /dev/null +++ b/src/main/java/demo/api/config/UserDetailsServiceImpl.java @@ -0,0 +1,35 @@ +package demo.api.config; + +import demo.api.user.domain.User; +import demo.api.user.exception.UserNotFoundException; +import demo.api.user.repository.UserRepository; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UserDetailsServiceImpl implements UserDetailsService { + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UserNotFoundException { + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new UserNotFoundException()); + Set grantedAuthorities = new HashSet<>(); + + return new org + .springframework + .security + .core + .userdetails + .User(user.getEmail(), user.getPassword(), grantedAuthorities); + } +} diff --git a/src/main/java/demo/api/home/HomeController.java b/src/main/java/demo/api/home/HomeController.java new file mode 100644 index 0000000..b88a338 --- /dev/null +++ b/src/main/java/demo/api/home/HomeController.java @@ -0,0 +1,12 @@ +package demo.api.home; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class HomeController { + @GetMapping("/") + public String home() { + return "home"; + } +} diff --git a/src/main/java/demo/api/user/UserController.java b/src/main/java/demo/api/user/UserController.java index e943611..a20de9a 100644 --- a/src/main/java/demo/api/user/UserController.java +++ b/src/main/java/demo/api/user/UserController.java @@ -1,25 +1,71 @@ package demo.api.user; import demo.api.user.domain.User; +import demo.api.user.dtos.UserSignInRequest; import demo.api.user.dtos.UserSignUpRequest; +import demo.api.user.exception.UserNotFoundException; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; /** * User 관련 HTTP 요청 처리 */ -@RestController +@Controller @RequestMapping("/user") @RequiredArgsConstructor public class UserController { private final UserService userService; + @GetMapping("/signUp") + public String signUp() { + return "user/signUp"; + } + @PostMapping("/signUp") - public User signUp(@Validated UserSignUpRequest signUpReq) throws Exception { - return userService.signUp(signUpReq); + public String signUp(@Validated UserSignUpRequest signUpReq) throws Exception { + User user = userService.signUp(signUpReq); + + return "redirect:/user/signIn"; + } + + @GetMapping("/signIn") + public String signIn(@RequestParam(value = "fail", required = false) String flag, Model model) { + if(flag == null || !flag.equals("true")) { + model.addAttribute("failed", false); + return "user/signIn"; + } + else { + model.addAttribute("failed", true); + return "user/signIn"; + } + } + + @GetMapping("/profile") + public String profile(Model model, @AuthenticationPrincipal User user) { + User userDetail = userService.findByEmail(user.getEmail()) + .orElseThrow(() -> new UserNotFoundException()); + + model.addAttribute("userDetail", userDetail); + + return "user/profile"; + } + + @GetMapping("/user/userList") + public String showUserList(Model model) { + List userList = userService.findAll(); + + model.addAttribute("userList", userList); + + return "user/userList"; } } diff --git a/src/main/java/demo/api/user/UserService.java b/src/main/java/demo/api/user/UserService.java index dfeee6d..df52512 100644 --- a/src/main/java/demo/api/user/UserService.java +++ b/src/main/java/demo/api/user/UserService.java @@ -1,6 +1,7 @@ package demo.api.user; import demo.api.user.domain.User; +import demo.api.user.dtos.UserSignInRequest; import demo.api.user.dtos.UserSignUpRequest; import java.util.List; import java.util.Optional; diff --git a/src/main/java/demo/api/user/UserServiceImpl.java b/src/main/java/demo/api/user/UserServiceImpl.java index acb5ade..0269e03 100644 --- a/src/main/java/demo/api/user/UserServiceImpl.java +++ b/src/main/java/demo/api/user/UserServiceImpl.java @@ -1,6 +1,7 @@ package demo.api.user; import demo.api.user.domain.User; +import demo.api.user.dtos.UserSignInRequest; import demo.api.user.dtos.UserSignUpRequest; import demo.api.user.repository.UserRepository; import java.util.List; diff --git a/src/main/java/demo/api/user/domain/User.java b/src/main/java/demo/api/user/domain/User.java index 8af4944..7edbab5 100644 --- a/src/main/java/demo/api/user/domain/User.java +++ b/src/main/java/demo/api/user/domain/User.java @@ -1,8 +1,11 @@ package demo.api.user.domain; import demo.api.common.domain.CoreEntity; +import demo.api.user.repository.UserRepository; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -23,11 +26,15 @@ public class User extends CoreEntity { @Column(length = 10, nullable = false) private String name; +// @Enumerated(EnumType.STRING) +// private UserRole role; + @Builder - public User(String email, String password, String name) { + public User(String email, String password, String name /*UserRole role*/) { this.email = email; this.password = password; this.name = name; +// this.role = role; } // https://reflectoring.io/spring-security-password-handling/ diff --git a/src/main/java/demo/api/user/domain/UserRole.java b/src/main/java/demo/api/user/domain/UserRole.java new file mode 100644 index 0000000..5627d2f --- /dev/null +++ b/src/main/java/demo/api/user/domain/UserRole.java @@ -0,0 +1,8 @@ +package demo.api.user.domain; + +import lombok.Getter; + +@Getter +public enum UserRole { + ROLE_USER // Spring Security의 role 네이밍 규칙 : ROLE_권한이름 +} diff --git a/src/main/java/demo/api/user/dtos/UserSignInRequest.java b/src/main/java/demo/api/user/dtos/UserSignInRequest.java new file mode 100644 index 0000000..09556e2 --- /dev/null +++ b/src/main/java/demo/api/user/dtos/UserSignInRequest.java @@ -0,0 +1,20 @@ +package demo.api.user.dtos; + +import demo.api.user.domain.User; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@ToString +public class UserSignInRequest { + @NotEmpty(message = "Please enter your Email") + @Email + private String email; + + @NotEmpty(message = "Please enter your Password") + private String password; +} diff --git a/src/main/java/demo/api/user/exception/UserNotFoundException.java b/src/main/java/demo/api/user/exception/UserNotFoundException.java new file mode 100644 index 0000000..bf18daa --- /dev/null +++ b/src/main/java/demo/api/user/exception/UserNotFoundException.java @@ -0,0 +1,8 @@ +package demo.api.user.exception; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException() { + super("Can't find User"); + } + +} diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html new file mode 100644 index 0000000..d100a74 --- /dev/null +++ b/src/main/resources/templates/home.html @@ -0,0 +1,17 @@ + + + +
+
+

Test Page

+

User

+

+ Sign Up + Sign In +
+ Profile +

+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/user/profile.html b/src/main/resources/templates/user/profile.html new file mode 100644 index 0000000..c2144a2 --- /dev/null +++ b/src/main/resources/templates/user/profile.html @@ -0,0 +1,24 @@ + + + +
+
+ + + + + + + + + + + + + +
#My Profile
+
+ Home +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/user/signIn.html b/src/main/resources/templates/user/signIn.html new file mode 100644 index 0000000..1c6d9f9 --- /dev/null +++ b/src/main/resources/templates/user/signIn.html @@ -0,0 +1,26 @@ + + + + + Login + + +
+
+

로그인

+
+

Fail to login

+
+
+ + +
+ + +
+ +
+
+ Home + + \ No newline at end of file diff --git a/src/main/resources/templates/user/signUp.html b/src/main/resources/templates/user/signUp.html new file mode 100644 index 0000000..e24d719 --- /dev/null +++ b/src/main/resources/templates/user/signUp.html @@ -0,0 +1,24 @@ + + + +
+
+

회원가입

+
+
+
+ + +
+ + +
+ + +
+ +
+
+ Home + + \ No newline at end of file diff --git a/src/main/resources/templates/user/userList.html b/src/main/resources/templates/user/userList.html new file mode 100644 index 0000000..e692cfc --- /dev/null +++ b/src/main/resources/templates/user/userList.html @@ -0,0 +1,24 @@ + + + +
+
+ + + + + + + + + + + + + +
#User List
+
+ Home +
+ + \ No newline at end of file diff --git a/src/test/java/demo/api/user/service/UserServiceTest.java b/src/test/java/demo/api/user/service/UserServiceTest.java index 0fad8ba..538715d 100644 --- a/src/test/java/demo/api/user/service/UserServiceTest.java +++ b/src/test/java/demo/api/user/service/UserServiceTest.java @@ -69,6 +69,22 @@ class UserServiceTest { assertThat(newUser.getPassword()).isNotEqualTo(PASSWORD); } + @Test + @DisplayName("유저 로그인") + void signIn() throws Exception { + // given + UserSignUpRequest user = createSignUpRequest(); + System.out.println("user = " + user.toString()); + User newUser = userService.signUp(user); + + // when + Boolean flag = newUser.checkPassword(PASSWORD, bCryptPasswordEncoder); + System.out.println("flag = " + flag); + + // then + + } + @Test @DisplayName("모든 유저 리스트를 반환") void findAll() throws Exception {