Spring Boot를 이용하여 Login까지 간단히 끝냄. Maven버전이 아닌 Gradle 버전으로 남김
1. 새로운 프로젝트 생성
- Type : Gradle 로 선택(필수)
2. Next 버튼 누른 후 Dependency 설정
- 이전에 사용했던 항목이 있으면 Frequently Used 로 바로 보임. 체크를 하면 됨
- 없으면 왼쪽 하단 부분에서 찾아(세모 클릭하면 펼쳐짐) 체크
- 선택된 dependencies는 오른쪽 하단에 Selected 로 표시 됨
3.프로젝트 구조
- 파일 위치 및 파일명 참조
4. Build.gradle 확인
- dependencies 에서 implementation은 선택한 항목-
- commons-beanutils 를 추가함.
-- jquery, bootstrap 추가
https://www.webjars.org/ 에서 해당 버전에 맞게 찾아옴
- Build Tool 에서 해당 Tool 선택
- 사용할 jar 의 추가방법 복사 : compile 'org.webjars:jquery:3.4.1'
-- 추가한 build.gradle
5. DB 연결 : src/main/resources 아래에 application.properties
- server 포트 변경할 때 사용. 기본값은 8080이므로 변경할 때만 추가
- mysql 설정에 localhost:3306 뒤에 연결할 db명을 적고 username, password
- JPA를 사용하므로 hibernate.dll-auto=create 으로 추가
6. db script
- mysql 을 사용하는 경우 id column에 PK, Auto_increment 속성을 넣는다.
- oracle을 사용하는 경우에는 auto_increment 는 필요없다(별도의 sequence 사용)
- key 정보를 잘못 설정한 경우 : table 생성 후 auto_increment 를 추가하려고 하면 foreign key가 있다고 오류남 # unique key 삭제 및 생성 |
- 추가 후
source
- UserRegistrationDto : package com.nobang.sample.web.dto
import javax.validation.constraints.AssertTrue; import javax.validation.constraints.Email; import javax.validation.constraints.NotEmpty; import com.nobang.sample.constraint.FieldMatch; @FieldMatch.List({ @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"), @FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match") }) public class UserRegistrationDto { @NotEmpty private String firstName; @NotEmpty private String lastName; @NotEmpty private String password; @NotEmpty private String confirmPassword; @NotEmpty private String email; @NotEmpty private String confirmEmail; @AssertTrue private Boolean terms; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getConfirmPassword() { return confirmPassword; } public void setConfirmPassword(String confirmPassword) { this.confirmPassword = confirmPassword; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getConfirmEmail() { return confirmEmail; } public void setConfirmEmail(String confirmEmail) { this.confirmEmail = confirmEmail; } public Boolean getTerms() { return terms; } public void setTerms(Boolean terms) { this.terms = terms; } } |
- SampleUser : package com.nobang.sample.model
import javax.persistence.*; import java.util.Collection; @Entity @Table(uniqueConstraints = @UniqueConstraint(columnNames = "email")) public class SampleUser { @Id // @GeneratedValue(strategy = GenerationType.AUTO) // ORACLE @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String firstName; private String lastName; private String email; private String password; @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinTable( name = "sample_users_roles", joinColumns = @JoinColumn( name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn( name = "role_id", referencedColumnName = "id")) private Collection < SampleRole > roles; public SampleUser() {} public SampleUser(String firstName, String lastName, String email, String password) { this.firstName = firstName; this.lastName = lastName; this.email = email; this.password = password; } public SampleUser(String firstName, String lastName, String email, String password, Collection < SampleRole > roles) { this.firstName = firstName; this.lastName = lastName; this.email = email; this.password = password; this.roles = roles; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Collection < SampleRole > getRoles() { return roles; } public void setRoles(Collection < SampleRole > roles) { this.roles = roles; } @Override public String toString() { return "User{" + "id=" + id + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + ", password='" + "*********" + '\'' + ", roles=" + roles + '}'; } } |
- SampleRole : package com.nobang.sample.model;
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class SampleRole { @Id // @GeneratedValue(strategy = GenerationType.AUTO) // ORACLE @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; public SampleRole() {} public SampleRole(String name) { this.name = name; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Role{" + "id=" + id + ", name='" + name + '\'' + '}'; } } |
- UserRepository : package com.nobang.sample.repository;
@Repository public interface UserRepository extends JpaRepository < SampleUser, Long > { SampleUser findByEmail(String email); } |
참고 : JPA에서 기본으로 제공하는 method
- FieldMatch : package com.nobang.sample.constraint;
import javax.validation.Payload; import javax.validation.Constraint; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target({ TYPE, ANNOTATION_TYPE }) @Retention(RUNTIME) @Constraint(validatedBy = FieldMatchValidator.class) @Documented public @interface FieldMatch { // String message() default "{constraints.field-match}"; // Class << ? > [] groups() default {}; // Class << ? extends Payload > [] payload() default {}; String message() default "{constraints.fieldmatch}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; String first(); String second(); @Target({ TYPE, ANNOTATION_TYPE }) @Retention(RUNTIME) @Documented @interface List { FieldMatch[] value(); } } |
- FieldMatchValidator : package com.nobang.sample.constraint;
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import org.apache.commons.beanutils.BeanUtils; public class FieldMatchValidator implements ConstraintValidator < FieldMatch, Object > { private String firstFieldName; private String secondFieldName; @Override public void initialize(final FieldMatch constraintAnnotation) { firstFieldName = constraintAnnotation.first(); secondFieldName = constraintAnnotation.second(); } @Override public boolean isValid(final Object value, final ConstraintValidatorContext context) { try { final Object firstObj = BeanUtils.getProperty(value, firstFieldName); final Object secondObj = BeanUtils.getProperty(value, secondFieldName); return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj); } catch (final Exception ignore) {} return true; } } |
- UserService : package com.nobang.sample.service;
import org.springframework.security.core.userdetails.UserDetailsService; import com.nobang.sample.model.SampleUser; import com.nobang.sample.web.dto.UserRegistrationDto; public interface UserService extends UserDetailsService { SampleUser findByEmail(String email); SampleUser save(UserRegistrationDto registration); } |
- UserServiceImpl : package com.nobang.sample.service;
import java.util.Arrays; import java.util.Collection; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import com.nobang.sample.model.SampleRole; import com.nobang.sample.model.SampleUser; import com.nobang.sample.repository.UserRepository; import com.nobang.sample.web.dto.UserRegistrationDto; @Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Autowired private BCryptPasswordEncoder passwordEncoder; public SampleUser findByEmail(String email) { return userRepository.findByEmail(email); } public SampleUser save(UserRegistrationDto registration) { SampleUser user = new SampleUser(); user.setFirstName(registration.getFirstName()); user.setLastName(registration.getLastName()); user.setEmail(registration.getEmail()); user.setPassword(passwordEncoder.encode(registration.getPassword())); user.setRoles(Arrays.asList(new SampleRole("ROLE_USER"))); return userRepository.save(user); } @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { SampleUser user = userRepository.findByEmail(email); if (user == null) { throw new UsernameNotFoundException("Invalid username or password."); } return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), mapRolesToAuthorities(user.getRoles())); } // private Collection << ? extends GrantedAuthority > mapRolesToAuthorities(Collection < Role > roles) { // return roles.stream() // .map(role - > new SimpleGrantedAuthority(role.getName())) // .collect(Collectors.toList()); // } private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Collection roles){ return roles.stream() .map(role -> new SimpleGrantedAuthority(role.getName())) .collect(Collectors.toList()); } } |
- SecurityConfiguration : package com.nobang.sample.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 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.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import com.nobang.sample.service.UserService; @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private UserService userService; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers( "/registration**", "/js/**", "/css/**", "/img/**", "/webjars/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .invalidateHttpSession(true) .clearAuthentication(true) .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutSuccessUrl("/login?logout") .permitAll(); } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider auth = new DaoAuthenticationProvider(); auth.setUserDetailsService(userService); auth.setPasswordEncoder(passwordEncoder()); return auth; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider()); } } |
- MainController : package com.nobang.sample.controller;
import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @Controller public class MainController { @GetMapping("/") public String root() { return "index"; } @GetMapping("/login") public String login(Model model) { return "login"; } @GetMapping("/user") public String userIndex() { return "user/index"; } } |
- UserRegistrationController :
import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; 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 com.nobang.sample.model.SampleUser; import com.nobang.sample.service.UserService; import com.nobang.sample.web.dto.UserRegistrationDto; @Controller @RequestMapping("/registration") public class UserRegistrationController { @Autowired private UserService userService; @ModelAttribute("user") public UserRegistrationDto userRegistrationDto() { return new UserRegistrationDto(); } @GetMapping public String showRegistrationForm(Model model) { return "registration"; } @PostMapping public String registerUserAccount(@ModelAttribute("user") @Valid UserRegistrationDto userDto, BindingResult result) { SampleUser existing = userService.findByEmail(userDto.getEmail()); if (existing != null) { result.rejectValue("email", null, "There is already an account registered with that email"); } if (result.hasErrors()) { return "registration"; } userService.save(userDto); return "redirect:/registration?success"; } } |
참조 : https://www.javaguides.net/2019/02/spring-mvc-5-spring-security-5-hibernate-5-mysql.html