Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] 회원가입 API #14

Merged
merged 8 commits into from
Oct 29, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;

@SpringBootApplication
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class SnsIntegrationFeedServiceApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@
@Getter
@AllArgsConstructor
public enum CustomErrorCode {
// 예시 에러코드입니다.
USER_NOT_FOUND(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 사용자입니다."),
POST_ID_NOT_FOUND(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 게시글 아이디입니다."),
;
POST_ID_NOT_FOUND(HttpStatus.BAD_REQUEST.value(),
"존재하지 않는 게시글 아이디입니다."),
USER_ALREADY_EXIST(HttpStatus.BAD_REQUEST.value(),
"이미 존재하는 사용자입니다."),
CONTAIN_PRIVATE_INFORMATION(HttpStatus.BAD_REQUEST.value(),
"비밀번호에 개인정보를 포함할 수 없습니다."),
LACK_OF_CHARACTERS(HttpStatus.BAD_REQUEST.value(),
"비밀번호는 10자 이상이어야 합니다."),
NOT_AVAILABLE_COMMON_PASSWORD(HttpStatus.BAD_REQUEST.value(),
"통상적으로 사용되는 비밀번호는 사용할 수 없습니다."),
NOT_FOLLOW_RULES(HttpStatus.BAD_REQUEST.value(),
"숫자, 문자, 특수문자 중 2가지 이상을 포함해야 합니다."),
NOT_THREE_CONSECUTIVE(HttpStatus.BAD_REQUEST.value(),
"3회 이상 연속되는 문자 사용이 불가합니다.");

private final int errorCode;
private final String errorMessage;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@
package com.snsIntegrationFeedService.user.controller;

import com.snsIntegrationFeedService.common.dto.ApiResponseDto;
import com.snsIntegrationFeedService.user.dto.SignupRequestDto;
import com.snsIntegrationFeedService.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@Tag(name = "사용자 API", description = "사용자와 관련된 API 정보를 담고 있습니다.")
public class UserController {
private final UserService userService;

@Operation(summary = "회원 가입", description = "가입에 필요한 정보를 받아 회원가입합니다.")
@PostMapping("/signup")
public ResponseEntity<ApiResponseDto> signup(@Valid @RequestBody SignupRequestDto requestDto) {
userService.signup(requestDto);
return ResponseEntity.ok().body(new ApiResponseDto(HttpStatus.OK.value(), "회원 가입 완료"));
JisooPyo marked this conversation as resolved.
Show resolved Hide resolved
}


}
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
package com.snsIntegrationFeedService.user.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class SignupRequestDto {

@NotBlank
private String account;

@NotBlank
@Email
private String email;

@NotBlank
private String password;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
import com.snsIntegrationFeedService.certificateCode.entity.CertificateCode;
import com.snsIntegrationFeedService.post.entity.Post;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -25,6 +31,7 @@ public class User {
private String email;

@Column(nullable = false)
@Builder.Default
private Boolean isAccessed = false;

@OneToMany(mappedBy = "user", orphanRemoval = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
package com.snsIntegrationFeedService.user.repository;

public interface UserRepository {
import com.snsIntegrationFeedService.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByAccount(String account);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.snsIntegrationFeedService.user.service;

import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Component
public class CommonPassword {
private final List<String> commonPasswordList;

public CommonPassword() {
String[] commonPasswordArray = {"Groupd2013", "123456789a", "12345qwert", "password123"};
this.commonPasswordList = Arrays.asList(commonPasswordArray);
}

public boolean isCommonPassword(String password) {
return commonPasswordList.contains(password);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.snsIntegrationFeedService.user.service;

import com.snsIntegrationFeedService.common.error.CustomErrorCode;
import com.snsIntegrationFeedService.common.exception.CustomException;
import com.snsIntegrationFeedService.user.dto.SignupRequestDto;
import org.springframework.stereotype.Component;

import java.util.regex.Pattern;

@Component
public class PasswordValidation {
String account;
String email;
String password;
JisooPyo marked this conversation as resolved.
Show resolved Hide resolved
private final CommonPassword commonPassword;

public PasswordValidation(CommonPassword commonPassword) {
this.commonPassword = commonPassword;
}

public void validatePassword(SignupRequestDto requestDto) {
this.account = requestDto.getAccount();
this.email = requestDto.getEmail();
this.password = requestDto.getPassword();

// 다른 개인 정보와 유사한 비밀번호는 사용할 수 없다.
if (containPrivateInformation()) {
throw new CustomException(CustomErrorCode.CONTAIN_PRIVATE_INFORMATION);
}

// 비밀번호는 최소 10자 이상이어야 한다.
if (password.length() < 10) {
throw new CustomException(CustomErrorCode.LACK_OF_CHARACTERS);
}

// 숫자, 문자, 특수문자 중 2가지 이상을 포함해야 합니다.
if (!followRules()) {
throw new CustomException(CustomErrorCode.NOT_FOLLOW_RULES);
}

// 3회 이상 연속되는 문자 사용이 불가합니다.
if (isThreeConsecutiveLetters()) {
throw new CustomException(CustomErrorCode.NOT_THREE_CONSECUTIVE);
}

// 통상적으로 자주 사용되는 비밀번호는 사용할 수 없습니다.
if (commonPassword.isCommonPassword(password)) {
throw new CustomException(CustomErrorCode.NOT_AVAILABLE_COMMON_PASSWORD);
}
}

private boolean containPrivateInformation() {
int atIndex = email.indexOf("@");
String id = email.substring(0, atIndex);

return password.contains(account) || password.contains(id);
}

private boolean followRules() {
boolean numberEnglish = Pattern.matches("^(?=.*[0-9]+)(?=.*[a-zA-Z]+).+", password);
boolean englishSpecialSymbol = Pattern.matches("^(?=.*[a-zA-Z]+)(?=.*[!@#$%^&*]+).+", password);
boolean specialSymbolsNumber = Pattern.matches("^(?=.*[!@#$%^&*]+)(?=.*[0-9]+).+", password);

return numberEnglish || englishSpecialSymbol || specialSymbolsNumber;
}

private boolean isThreeConsecutiveLetters() {
int count = 1;
for (int i = 1; i < password.length(); i++) {
if (password.charAt(i - 1) == password.charAt(i)) {
count++;
} else {
count = 1;
}
if (count == 3) {
return true;
}
}
return false;
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,47 @@
package com.snsIntegrationFeedService.user.service;

import com.snsIntegrationFeedService.common.error.CustomErrorCode;
import com.snsIntegrationFeedService.common.exception.CustomException;
import com.snsIntegrationFeedService.user.dto.SignupRequestDto;
import com.snsIntegrationFeedService.user.entity.User;
import com.snsIntegrationFeedService.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class UserService {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final PasswordValidation passwordValidation;

public void signup(SignupRequestDto requestDto) {
String account = requestDto.getAccount();
String email = requestDto.getEmail();
String password = requestDto.getPassword();

// account로 검증하여 회원이 존재하면 예외 발생
User targetUser = findUser(account);
if (targetUser != null) {
throw new CustomException(CustomErrorCode.USER_ALREADY_EXIST);
}
JisooPyo marked this conversation as resolved.
Show resolved Hide resolved

// 비밀번호 검증이 완료되면 password encoding
passwordValidation.validatePassword(requestDto);
password = passwordEncoder.encode(password);

User user = User.builder()
.account(account).email(email)
.password(password).build();
userRepository.save(user);
}

private User findUser(String account) {
return userRepository.findByAccount(account).orElse(null);
}

}
Loading