회원가입 기능 설계 - ttasjwi/board-system GitHub Wiki
개요
- 회원가입을 위해서는 사용자에 관한 정보가 필요한데, 사용자는 우리 서비스에게 다음 파라미터를 전달해야한다.
- username : 아이디
- password : 비밀번호
- email : 이메일
- nickname : 닉네임
userId, username 의 차이
- userId
- 사용자 정보는 관계형 데이터베이스에 저장되어지고, 고유키(PK)가 필요하다. 이 값은 userId 라는 컬럼으로 별도로 관리되어진다.
- 사용자 고유 식별자로서, 자주 변경되어선 안 된다.
- 사용자들이 이 값을 알아도 상관없고, 몰라도 상관은 없다. 그래도 직접적으로 사용자가 사용하는 페이지에 노출될 필요는 없다.
- 예) userId: 12312413
- username
- 사용자 입장에서는 데이터베이스에 어떤 식별자로 저장되는지를 기억하는 것은 불편하다.
- 자신이 기억하기 쉬운 식별명을 가지는 것이 서비스 이용에서 더 편리하다. 사용자 입장에서는 이 값을 아이디로 인식하게 하는 것이 보안상 좋다.
- username 을 변경할 수 없게 하는 서비스도 있고(네이버 등...), 변경할 수 있게 하는 서비스도 있다.(트위터 등...) 우리 서비스는 변경 가능하게 하려고 한다.
- 예) username: ttasjwi1234
password (비밀번호)

- 사용자를 로그인 시키려면 비밀번호가 필요한데, 비밀번호 값은 결국 우리 서비스에서 저장해서 관리해야한다.
- 비밀번호는 민감한 값이므로 데이터베이스 테이블에 그대로 저장해선 안 된다. (보안상의 문제)
- 혹시 모를 데이터 유출 사고로 인해 데이터가 유출될 수 있다.
- 데이터 유출은 서비스 외부의 악성 해커들도 가능하지만, 우리 서비스 관리자들이 악의적인 목적으로 사용자의 비밀번호를 악용할 수 있다.
- 그래서 사용자 비밀번호는 암호화를 해서 관리하고, 사용자가 로그인을 할 때마다 비밀번호 값을 우리 서비스에 전달하여, 이 값이 저장된 값과 일치하는 지 불일치하는 지 확인하도록 한다.
private val idGenerator: IdGenerator = IdGenerator.create()
fun create(
email: String,
rawPassword: String,
username: String,
nickname: String,
currentTime: AppDateTime
): User {
return User.create(
userId = idGenerator.nextId(),
email = email,
password = passwordEncryptionPort.encode(rawPassword),
username = username,
nickname = nickname,
registeredAt = currentTime,
)
}
- 구체적인 코드를 보면 위와 같다. 회원 생성시, 사용자가 전달한 패스워드는 별도로 인코딩되어 생성된다.
가입 기능 흐름
@Transactional
fun register(command: RegisterUserCommand): User {
checkDuplicate(command)
checkEmailVerificationAndRemove(command)
val user = createUser(command)
userPersistencePort.save(user)
return user
}
private fun checkDuplicate(command: RegisterUserCommand) {
if (userPersistencePort.existsByEmail(command.email)) {
throw DuplicateUserEmailException(command.email)
}
if (userPersistencePort.existsByUsername(command.username)) {
throw DuplicateUserUsernameException(command.username)
}
if (userPersistencePort.existsByNickname(command.nickname)) {
throw DuplicateUserNicknameException(command.nickname)
}
}
private fun checkEmailVerificationAndRemove(command: RegisterUserCommand) {
val emailVerification = emailVerificationPersistencePort.findByEmailOrNull(command.email)
?: throw EmailVerificationNotFoundException(command.email)
// 이메일이 인증됐는지, 그리고 인증이 현재 유효한 지 확인
emailVerification.throwIfNotVerifiedOrCurrentlyNotValid(command.currentTime)
// 더 이상 이메일 인증이 필요 없으므로 말소
emailVerificationPersistencePort.remove(emailVerification.email)
}
private fun createUser(command: RegisterUserCommand): User {
return userCreator.create(
email = command.email,
rawPassword = command.rawPassword,
username = command.username,
nickname = command.nickname,
currentTime = command.currentTime,
)
}
- 중복 확인 : email, username, nickname 중복 여부를 확인한다.
- 이메일 인증 유효성 확인 : email 을 통해 이메일 인증(EmailVerification)을 조회하고 인증이 현재 유효한 지 확인한다.
- 회원 생성 및 저장