✅ 관련 PR
https://github.com/MyToy-Project/my-little-blog/pull/6
feat: 유저 회원가입 비즈니스 로직 구현 by HiiWee · Pull Request #6 · MyToy-Project/my-little-blog-back
관련 이슈 #5 구현내용 회원 가입 기능 사항 회원 가입시 이름, 닉네임, 아이디, 비밀번호, 비밀번호 확인, 이메일, 전화번호를 입력해야 한다. 이름, 닉네임, 아이디, 비밀번호, 비밀번호 확인은
github.com
✅ 목표
1. https://github.com/MyToy-Project/my-little-blog/issues/3(엔티티 생성 검토)
[FEATURE] 엔티티 생성 · Issue #3 · MyToy-Project/my-little-blog-back
📌 기능 설명 테이블에 매핑되는 엔티티를 생성합니다. 📌 해당 기능은 최소한 이 정도는 구현 해야 한다. 엔티티 매핑은 기본적으로 설정함 양방향 연관관계는 꼭 필요할때만 설정
github.com
2. 유저기능: 회원가입 비즈니스 로직 부분 구현 완료
✅ 회원 가입 요구사항 분석
비즈니스 로직
- 기능 사항
- 회원 가입시 이름, 닉네임, 아이디, 비밀번호, 비밀번호 확인, 이메일, 전화번호를 입력해야 한다.
- 이름, 닉네임, 아이디, 비밀번호, 비밀번호 확인은 필수사항이다.
- 이름, 비밀번호는 암호화 되어야 한다.
- 검증사항
- 비밀번호, 비밀번호 확인은 일치해야 한다.
- 비밀번호는 최소 8자이상 ~ 20자이하 까지 이며 하나 이상의 대문자, 소문자, 숫자, 특수 문자(@$!%*#?&)를 가져야 합니다.
- 정규식:
^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,20}$
- 정규식:
- 아이디는 중복 검증을 해야한다.
- 닉네임은 중복 검증을 해야한다.
✅ 진행 내용
엔티티 생성 검토
→ 프로젝트를 진행하면서 변경된 부분이 많음
- 임베디드 타입 이용, 필드명 변경 등
회원가입: JPA의 임베디드 기능 적극 사용
프로젝드 구상까지만 해도 보안의 중요성을 크게 알지 못했는데, 참고하던 프로젝트에서 사용자의 중요 정보들을 인코딩 하는 모습을 보고, 보안의 중요성일 인지할 수 있었다.
패스워드와 실명의 경우 개인적인 정보이므로 MessageDigest 인스턴스를 이용해 SHA-256으로 인코딩 함.
인코딩을 하면서 Member 도메인에 직접 패스워드와 이름을 삽입하게 되면, 인코딩할때 필요한 정보들이나 패스워드에서 검증해야할 문자 형식들에 대한 코드가 서비스 레이어에 같이 보이게 된다.
이런 부분들은 가독성을 해칠뿐만 아니라 도메인에 관련된 코드들이 굳이 서비스에서 보여야 할 필요가 있는지 의심을 해봐야 한다.
따라서 패스워드와 닉네임에 대한 @Embeddable 객체를 만들어 캡슐화를 하고, 주요 로직들을 해당 클래스로 옮겨 도메인이 도메인의 역할을 할 수 있었다. 또한 서비스 코드에서 도메인 로직을 제거할 수 있어 가독성도 향상된다.
Member 엔티티와 Password, Username 임베디드 객체들입니다.
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
@Column(length = 100, nullable = false)
private String loginId;
@Embedded
private Username username;
@Column(length = 150)
private String nickname;
@Embedded
private Password password;
@Column(length = 150)
private String email;
@Column(length = 50)
private String phone;
protected Member() {
}
@Builder
public Member(final String loginId, final Username username, final String nickname, final Password password,
final String email, final String phone) {
this.loginId = loginId;
this.username = username;
this.nickname = nickname;
this.password = password;
this.email = email;
this.phone = phone;
}
}
@Embeddable
public class Password {
private static final Pattern PATTERN = Pattern.compile(
"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,20}$");
@Column(name = "password", length = 100, nullable = false)
private String value;
protected Password() {
}
private Password(final String encryptedValue) {
this.value = encryptedValue;
}
public static Password of(final Encryptor encryptor, final String password) {
validate(password);
return new Password(encryptor.encrypt(password));
}
private static void validate(final String password) {
if (!PATTERN.matcher(password).matches()) {
throw new InvalidPasswordFormatException();
}
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Password password = (Password) o;
return Objects.equals(value, password.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
@Getter
@Embeddable
public class Username {
@Column(name = "username", length = 100)
private String value;
protected Username() {
}
private Username(final String encryptedValue) {
this.value = encryptedValue;
}
public static Username of(final Encryptor encryptor, final String name) {
return new Username(encryptor.encrypt(name));
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Username username = (Username) o;
return Objects.equals(value, username.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
@Column(length = 100, nullable = false)
private String loginId;
@Embedded
private Username username;
@Column(length = 150)
private String nickname;
@Embedded
private Password password;
@Column(length = 150)
private String email;
@Column(length = 50)
private String phone;
protected Member() {
}
@Builder
public Member(final String loginId, final Username username, final String nickname, final Password password,
final String email, final String phone) {
this.loginId = loginId;
this.username = username;
this.nickname = nickname;
this.password = password;
this.email = email;
this.phone = phone;
}
}
@Embeddable
public class Password {
private static final Pattern PATTERN = Pattern.compile(
"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,20}$");
@Column(name = "password", length = 100, nullable = false)
private String value;
protected Password() {
}
private Password(final String encryptedValue) {
this.value = encryptedValue;
}
public static Password of(final Encryptor encryptor, final String password) {
validate(password);
return new Password(encryptor.encrypt(password));
}
private static void validate(final String password) {
if (!PATTERN.matcher(password).matches()) {
throw new InvalidPasswordFormatException();
}
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Password password = (Password) o;
return Objects.equals(value, password.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
@Getter
@Embeddable
public class Username {
@Column(name = "username", length = 100)
private String value;
protected Username() {
}
private Username(final String encryptedValue) {
this.value = encryptedValue;
}
public static Username of(final Encryptor encryptor, final String name) {
return new Username(encryptor.encrypt(name));
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Username username = (Username) o;
return Objects.equals(value, username.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}