프로필 수정 - 비밀번호 변경 제한

박미소·2024년 1월 27일
0

코틀린

목록 보기
35/44

  1. 비밀번호만 저장하는 테이블 생성

  2. PasswordHistory 엔티티 클래스 작성

@Entity
@Table(name = "passwords")
class PasswordHistory private constructor(
    _email: String,
    _password: String
): BaseEntity() {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null
        private set

    @Column(name = "email")
    var email: String = _email
        private set

    @Column(name = "password")
    var password: String = _password
        private set

    fun update(password: String) {
        this.password = password
    }

    companion object {
        fun of(email: String, password: String) = PasswordHistory(
            _email = email,
            _password = password
        )

    }
}
  1. PasswordRepository 리포지토리
interface PasswordRepository: JpaRepository<PasswordHistory, Long> {
    fun findByEmailOrderByUpdatedAtDesc(email: String): List<PasswordHistory>
}
  1. 최근 3번안에 사용한 비밀번호는 사용할 수 없도록 제한하는 로직
@Transactional
fun updateProfile(request: ProfileRequest): ProfileResponse {
    val passwordHistories = passwordRepository.findByEmailOrderByUpdatedAtDesc(request.email)
    val isDuplicate = passwordHistories.any { passwordEncoder.matches(request.password, it.password) }

    if (isDuplicate) {
        throw InvalidPasswordException()
        }

    val authenticatedId = AuthenticationUtil.getAuthenticationUserId()
    val member = getByEmailOrNull(request.email)
    member.verify(authenticatedId)

    val updatedPassword = passwordEncoder.encode(request.password)
    member.update(request.introduce, updatedPassword)

    if (passwordHistories.size < 3) {
        passwordRepository.save(PasswordHistory.of(request.email, updatedPassword))
    } else {
    	passwordHistories[0].update(updatedPassword)
    }

    return member.from()
}

저장된 비밀번호를 findByEmailOrderByUpdatedAtDesc 메서드로 내림차순 정렬을 적용한 상태로 리스트 형식으로 passwordHistories 변수에 담는다.

any { } 를 사용해 리스트를 돌면서 변경을 요청한(request.password) 비밀번호와 it.password(PasswordHistory의 프로퍼티 password 하나)가 중복되는지 확인해 true/false로 반환된 반환값을 isDuplicate 변수에(Bolean Type) 담는다.

DB에 저장된 멤버의 아이디인지 확인하기 위해서 스프링시큐리티 authentication 안에있는 principal로 사용된 userdetails에서 스트링으로 저장했던 id를 다시 Long 타입으로 변환해 authenticatedId 변수에 담는다.

요청받은 이메일로 member 객체를 불러와 Member 엔티티 클래스에서 멤버의 id와 authentication의 principal이 같은지 검증을 한 뒤 변경한 비밀번호는 암호화해서 저장한다.

if문: passwordHistories 리스트에서 비밀번호 개수가 3개 미만일때 새로운 비밀번호가 DB에 들어간다.


// 스프링 시큐리티를 통해 인증을 받은 유저인지 확인하기 위해서 전역에 쓰일 수 있는 객체 생성
object AuthenticationUtil {
    private fun getAuthenticatedUser(): UserDetails =
        SecurityContextHolder.getContext().authentication.principal as UserDetails

    fun getAuthenticationUserId() = getAuthenticatedUser().username.toLong()

}

이 유저가 스프링 시큐리티로 인증을 받은 유저인지 확인하기 위해서 스프링 시큐리티의 SecurityContextHolder 안에 있는 Authentication에 있는 principal을 가져와 UserDetails를 반환하는 getAuthenticatedUser private 메서드를 생성한다.

이후 UserDetails로 getAuthenticationUserId 메서드를 생성한다. 이 메서드는 서비스 전역에서 쓰인다.

principal로 이용했던 UserDetails에서 member.id.toString()이 username으로 사용됐다.

getAuthenticationUserId 메서드는 private으로 만든 getAuthenticatedUser를 호출해 String으로 저장했던 member 객체의 아이디를 Long 타입으로 바꿔 반환한다.

0개의 댓글