sm 기술 블로그
[스프링 시큐리티] 회원가입 본문
DB
CREATE TABLE site_user(
id INT(20) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(30) NOT NULL UNIQUE,
`password` VARCHAR(150) NOT NULL,
email VARCHAR(50) NOT NULL UNIQUE
);
# 테스트 케이스
INSERT INTO site_user SET
username = "유저1",
`password` = "1234",
email = "test@test.com";
SELECT * FROM site_user;
password 길이를 적게하면 암호화 도중 에러가 발생한다.
SiteUser 엔티티
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
public class SiteUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
private String password;
@Column(unique = true)
private String email;
}
중복 허용을 방지하기 위해서 name과 email에 unique를 주었다.
User 리포지터리 작성
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<SiteUser, Long> {
}
SecurityConfig 작성
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll()
.and()
.csrf().ignoringAntMatchers("/h2-console/**")
.and()
.headers()
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
;
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
BCryptPasswordEncoder 는 BCrypt 해싱 함수(BCrypt hashing function)를 사용해서 비밀번호를 암호화한다.
또한 Bruteforce attack(하나씩 비교) 이나 Rainbow table attack(변환 가능한 모든 해시 값을 저장 시켜 놓은 표) 과 같은 Password Cracking에 대한 저항력을 높이기 위해 의도적으로 느리게 설정되어 있다.
예를들어 암호화를 하면 아래와 같이 보여준다.
- PasswordEncoder는 BCryptPasswordEncoder 에서 제공하는 인터페이스 이다.
PasswordEncoder는 다음과 같이 구성되어 있다.
public interface PasswordEncoder {
// 비밀번호를 단방향 암호화
String encode(CharSequence rawPassword);
// 암호화되지 않은 비밀번호(raw-)와 암호화된 비밀번호(encoded-)가 일치하는지 비교
boolean matches(CharSequence rawPassword, String encodedPassword);
// 암호화된 비밀번호를 다시 암호화하고자 할 경우 true를 return하게 설정
default boolean upgradeEncoding(String encodedPassword) { return false; };
}
User 서비스 작성
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public SiteUser create(String username, String email, String password) {
SiteUser user = new SiteUser();
user.setUsername(username);
user.setEmail(email);
user.setPassword(passwordEncoder.encode(password));
this.userRepository.save(user);
return user;
}
}
passwordEncoder는 @Bean으로 등록했기 때문에 UserService에서 다음과 같이 사용이 가능하다.
회원가입 폼
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserCreateForm {
@Size(min = 3, max = 25)
@NotEmpty(message = "사용자ID는 필수항목입니다.")
private String username;
@NotEmpty(message = "비밀번호는 필수항목입니다.")
private String password1;
@NotEmpty(message = "비밀번호 확인은 필수항목입니다.")
private String password2;
@NotEmpty(message = "이메일은 필수항목입니다.")
@Email
private String email;
}
회원가입 폼에서 데이터를 받을 때 사용한다.
- @Size(min = , max = , message = ) 최소,최대 크기를 제한함.
- @NotEmpty(message = ) 비어 있다면 message를 bindingResult에 담아 출력함
- @Email 이메일 형식이어야 한다.
회원가입 컨트롤러
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Controller
@RequestMapping("/user")
public class UserController {
private final UserService userService;
@GetMapping("/signup")
public String signup(UserCreateForm userCreateForm) {
return "signup_form";
}
@PostMapping("/signup")
public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
//UserCreateForm 자체에서 유효성을 검사 했을 때 에러가 발생하면 이 조건에 들어온다.
return "signup_form";
}
if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) {
// 패스워드 1과 패스워드 2가 다르다면 이 조건에 들어온다.
bindingResult.rejectValue("password2", "passwordInCorrect",
"2개의 패스워드가 일치하지 않습니다.");
// 위의 문법은 rejectValue(@Nullable String field, String errorCode, String defaultMessage); 다음과 같다.
// 참고로 에러 코드만 제대로 쓰면 되기 때문에 bindingResult.rejectValue("","passwordInCorrect","2개의 패스워드가 일치하지 않습니다."); 다음과 같이 바꿔 쓸 수 있다.
return "signup_form";
}
try {
// 정상적으로 회원이 생성 된다.
userService.create(userCreateForm.getUsername(),
userCreateForm.getEmail(), userCreateForm.getPassword1());
}catch(DataIntegrityViolationException e) {
// 중복이 발생할 경우 발생하는 에러
e.printStackTrace();
// 시스템 에러를 출력해준다.
bindingResult.reject("signupFailed", "이미 등록된 사용자입니다.");
// bindingResult.rejectValue 와 비슷하다.(일치한다.)
return "signup_form";
}catch(Exception e) {
// 그외 예상치 못한 에러들
e.printStackTrace();
// 시스템 에러를 출력해준다.
bindingResult.reject("signupFailed", e.getMessage());
// bindingResult.rejectValue 와 비슷하다.(일치한다.)
return "signup_form";
}
return "redirect:/";
}
}
유효성 검사에서 할 수 있는 일은 많지 않다.(표 보러가기)
따라서 비밀번호가 맞지 않는 경우, 중복이 발생할 경우등은 직접 bindingResult를 해주어 사용자에게 알려주는 것이다.
'스프링부트' 카테고리의 다른 글
[스프링 시큐리티] 로그인 (0) | 2022.07.23 |
---|---|
[스프링 부트] 유효값과 예외 처리 (0) | 2022.07.23 |
[스프링 시큐리티] CSRF(cross site request forgery) (0) | 2022.07.23 |
[스프링 시큐리티] 설치 및 설정 (0) | 2022.07.23 |
[스프링부트] ID와 GeneratedValue (0) | 2022.07.17 |
Comments