Spring Security 와 OAuth 2.0 를 이용하여 회원가입을 할 수 있다.
implementation 'org.springframework.boot:spring-boot-starter-security'
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.antMatchers("/", "/auth/**", "/post/search/**").permitAll()
/* 회원 가입 폼으로 이동 */
@GetMapping("/auth/join")
public String join() {
return "/user/user-join";
}
@PostMapping("/auth/joinProc")
public String joinProc(@Valid UserDto.RequestUserDto userDto, BindingResult bindingResult, Model model) {
/* 검증 */
if(bindingResult.hasErrors()) {
/* 회원가입 실패 시 입력 데이터 값 유지 */
model.addAttribute("userDto", userDto);
/* 유효성 검사를 통과하지 못 한 필드와 메시지 핸들링 */
Map<String, String> errorMap = new HashMap<>();
for(FieldError error : bindingResult.getFieldErrors()) {
errorMap.put("valid_"+error.getField(), error.getDefaultMessage());
log.info("회원가입 실패 ! error message : "+error.getDefaultMessage());
}
/* Model에 담아 view resolve */
for(String key : errorMap.keySet()) {
model.addAttribute(key, errorMap.get(key));
}
/* 회원가입 페이지로 리턴 */
return "/user/user-join";
}
// 회원가입 성공 시
userService.userJoin(userDto);
log.info("회원가입 성공");
return "redirect:/auth/login";
}
UserService
의 userJoin 메소드를 호출하여 회원가입 로직을 진행시킨다.회원가입 유효성 검사에 관한 포스팅은 링크
비밀번호를 암호화하여 dto에 저장하고 해당 dto를 엔티티로 저장하여 DB에 저장하는 등 회원가입 로직을 진행한다.
private final BCryptPasswordEncoder encoder;
/* 회원가입 */
@Override
public Long userJoin(RequestUserDto dto) {
/* 비밀번호 암호화 */
dto.encryptPassword(encoder.encode(dto.getPassword()));
User user = dto.toEntity();
userRepository.save(user);
log.info("DB에 회원 저장 성공");
return user.getId();
}
Spring Security에서 제공하는 비밀번호 암호화 객체
비밀번호를 암호화해서 사용할 수 있도록 Bean으로 등록한다.
SecurityConfig에서 @Bean으로 등록한다.
public class SecurityConfig extends WebSecurityConfigurerAdapter{
// 해당 메서드의 리턴되는 오브젝트는 IoC로 등록해준다
@Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}
서비스 로직에서 encoder 메소드를 이용해 dto를 통해 입력 받은 비밀번호를 해쉬 암호화 한 후, 다시 그 dto 객체에 저장하여 레포지토리에 저장하도록 한다.
{{>layout/header}}
<div id="post_list">
<div class="container col-md-4">
<form action="/auth/joinProc" method="post">
<input type="hidden" name="_csrf" value="{{_csrf.token}}"/>
<div class="form-group">
<label>아이디</label>
<input type="text" name="username" value="{{#userDto}}{{userDto.username}}{{/userDto}}" class="form-control" placeholder="아이디를 입력해주세요"/>
{{#valid_username}} <span id="valid">{{valid_username}}</span> {{/valid_username}}
</div>
<div class="form-group">
<label>비밀번호</label>
<input type="password" name="password" value="{{#userDto}}{{userDto.password}}{{/userDto}}" class="form-control" placeholder="비밀번호를 입력해주세요"/>
{{#valid_password}} <span id="valid">{{valid_password}}</span> {{/valid_password}}
</div>
<div class="form-group">
<label>닉네임</label>
<input type="text" name="nickname" value="{{#userDto}}{{userDto.nickname}}{{/userDto}}" class="form-control" placeholder="닉네임을 입력해주세요"/>
{{#valid_nickname}} <span id="valid">{{valid_nickname}}</span> {{/valid_nickname}}
</div>
<div class="form-group">
<label>이메일</label>
<input type="email" name="email" value="{{#userDto}}{{userDto.email}}{{/userDto}}" class="form-control" placeholder="이메일을 입력해주세요"/>
{{#valid_email}} <span id="valid">{{valid_email}}</span> {{/valid_email}}
</div>
<button class="btn btn-primary bi bi-person"> 가입</button>
<a href="/" role="button" class="btn btn-info bi bi-arrow-return-left"> 목록</a>
</form>
</div>
</div>
{{>layout/footer}}
public class User extends BaseTimeEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/* 로그인할 회원 아이디 */
@Column(nullable = false, length = 30, unique = true)
private String username;
@Column(length = 100)
private String password;
@Column(nullable = false, length = 50, unique = true)
private String nickname;
@Column(nullable = false, length = 50, unique = true)
private String email;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;
...
public class UserDto {
/* 회원 서비스 요청 RequestDTO 클래스 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@Setter
public static class RequestUserDto{
private Long id;
@NotBlank(message = "아이디는 필수 입력값입니다.")
@Pattern(regexp = "^[a-z0-9]{4,20}$", message = "아이디는 영어 소문자와 숫자만 사용하여 4~20자리여야 합니다.")
private String username;
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,16}$", message = "비밀번호는 8~16자리수여야 합니다. 영문 대소문자, 숫자, 특수문자를 1개 이상 포함해야 합니다.")
private String password;
@NotBlank(message = "닉네임은 필수 입력값입니다.")
@Pattern(regexp = "^[가-힣a-zA-Z0-9]{2,10}$" , message = "닉네임은 특수문자를 포함하지 않은 2~10자리여야 합니다.")
private String nickname;
@NotBlank(message = "이메일은 필수 입력값입니다.")
@Email(message = "이메일 형식이 올바르지 않습니다.")
private String email;
private Role role;
/* 암호화된 password */
public void encryptPassword(String BCryptpassword) {
this.password = BCryptpassword;
}
/* DTO -> Entity */
public User toEntity() {
User user = User.builder()
.id(id)
.username(username)
.password(password)
.nickname(nickname)
.email(email)
.role(role.USER)
.build();
return user;
}
}
}
encryptPassword
메소드 추가public interface UserRepository extends JpaRepository<User, Integer> {
}