240614~16 백오피스 프로젝트 - 기능 개발 마무리

노재원·2024년 6월 17일
0

내일배움캠프

목록 보기
61/90

정신 없이 지냈고 TIL에 내용을 담기도 애매해서 주말까지 작업한 내용을 한꺼번에 담기로 했다. 일기는 매일 쓰긴 하는데 일기에 비하면 TIL은 내용을 좀 더 알차게 채워야 하다보니 쓸 걸 생각하고 쓰는게 나은 것 같다.

이전 비밀번호 3회 제한하기

회원가입 / 비밀번호 변경시 이전 비밀번호 테이블에 레코드를 쌓게 만들었다. 이후 Repository에서 로직적으로 처리만 해주면 됐다.

override fun findLastThreePasswordsByUserId(userId: Long): List<String> {
    return queryFactory
        .select(passwordHistory.password)
        .from(passwordHistory)
        .where(passwordHistory.user.id.eq(userId))
        .orderBy(passwordHistory.createdAt.desc())
        .limit(3)
        .fetch()
}
    
override fun deleteHistoriesIfCountOver(userId: Long) {
    val deleteIds = queryFactory
        .select(passwordHistory.id)
        .from(passwordHistory)
        .where(passwordHistory.user.id.eq(userId))
        .orderBy(passwordHistory.createdAt.desc())
        .offset(3)
        .fetch()
    if (deleteIds.isNotEmpty()) {
        queryFactory
            .delete(passwordHistory)
            .where(passwordHistory.id.`in`(deleteIds))
            .execute()
    }
}

limit으로 최신 3회 비밀번호를 조회하고 이후 추가할 때는 offset으로 3회를 넘기는 모든 record를 삭제하게 했다. 모든 record로 한 이유는 혹시 뭐 버그나거나 정책적으로 바뀔 수도 있으니 범위로 삭제하게 만들었다.

user 테이블에 old_password 1,2,3로 구분하기?

old_password 1,2,3 column을 생성해 구분하는 경우도 있었는데 이번에 테이블을 분리한 건 유지보수, 확장성, 데이터 관리의 이유로 쓰게 됐다.

지금은 3회 제한이지만 나중에 5회로 늘어날 수도 있고 아예 password history 체크 자체를 안하게 될 수도 있게 될 것이다. 이럴 때 처리를 column 추가/삭제로 관리하게 되면 굉장히 난처해질 것이다.

그리고 테이블별로 보안을 다르게 할 수도 있고 스키마가 깔끔하게 떨어진다는 점에서도 분리하는게 훨씬 좋아보인다.

리프레쉬 토큰 관리하기

이제 만들기만 하던 Refresh token을 쓸 때가 왔다. 우선 Refresh token의 재발급 처리를 고민했는데 레퍼런스에는 Filter에서 구현해 즉시 재발급 처리를 하게끔 만든 곳도 있었지만 조금 이슈가 있었다.

재발급을 자동으로 해줄 거면 Refresh token의 이유가 퇴색되고 Refresh token이 요청에 자주 노출되므로 Refresh token의 탈취 가능성도 올라가는 만큼 Auth api 하나를 새로 파서 클라이언트가 해당 요청에만 RefreshToken을 Header에 담아 보내게 만들었다.

이후 로그인 할 때 Refresh token service를 통해 refresh_token 테이블에 토큰을 기록해두고 토큰 재발급시 검증, 갱신을 돕는다.

override fun findTokenByUserId(userId: Long): String? {
    return refreshTokenJpaRepository.findByUserId(userId)?.refreshToken
}

override fun updateToken(refreshToken: RefreshToken) {
    refreshTokenJpaRepository.findByUserId(refreshToken.userId)
        ?.updateToken(refreshToken.refreshToken)
        ?: refreshTokenJpaRepository.save(refreshToken)
}

override fun deleteTokenByUserId(userId: Long) {
    refreshTokenJpaRepository.deleteByUserId(userId)
}

토큰 타입의 구분

Claim에 type 구분이 없어 Refresh token으로도 사실 될 건 다 되는 상황이었기에 type을 추가해서 access, refresh로 나누고 Filter에서 구분하게끔 했다. 이제 Refresh token으로는 인증을 받을 수 없다.

리프레쉬 토큰의 재발급?

처음엔 로그인 세션을 유지한다는 명목하에 Refresh token도 갱신해서 테이블에 저장하게 하고 같이 반환했었는데 이건 너무 무식한 생각이었다.

만약 Refresh token이 단 한 번이라도 탈취당하면 무제한으로 토큰 발급을 진행할 수 있게 되므로 문제가 생긴다. 그래서 이런저런 레퍼런스를 찾아보고 보안상의 이슈로 일단 클라이언트가 사용성 개선을 원하면 자동 로그인 처리를 하는 방향으로 나갔다.

다른 대안으론 reissue column을 생성해서 발급 최대 횟수 3회같은 식으로 정책을 걸 수도 있는데 엄청 오래 걸릴 개발은 아니지만 이번엔 갈수록 시간이 없어 간단하게 조사했다.

0개의 댓글