Skip to content

feat: 재발급 토큰 엔티티 RefreshToken 추가 (#32) - #57

Merged
ji2081 merged 1 commit into
developfrom
feat/32-auth-refresh-token
Nov 2, 2026
Merged

ji2081 merged 1 commit into
developfrom
feat/32-auth-refresh-token

Conversation

@ji2081

@ji2081 ji2081 commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

관련 이슈

작업 내용

  • RefreshToken 엔티티·ClientType(WEB/EXTENSION)·레포지토리 (ERD 3-3)
  • 원문 대신 SHA-256 해시(CHAR 64) 저장, 같은 로그인의 토큰은 family_id로 묶음
  • revoke(처음 폐기 시각 유지), isExpired(만료 시각과 같으면 만료)
  • revokeFamily(재사용 감지 시 계보 폐기), revokeAllByUserId(로그아웃 시 웹·확장 모두 폐기): 이미 폐기된 토큰은 건드리지 않음

명세와 달라진 점

없음

테스트

10개, 전체 ./gradlew build 통과

  • 해시 63·65자 실패, 필수 값 누락, 두 번 폐기해도 첫 시각 유지, 만료 경계(1마이크로초 전·정각)
  • 해시 조회(CHAR 매핑), 해시 유니크, 계보 폐기가 다른 계보를 건드리지 않음, 로그아웃 폐기가 다른 회원을 건드리지 않음

확인 체크리스트

  • ./gradlew build 통과
  • 테스트 포함
  • 변경 400줄 안팎 (+368 / 운영 154, 테스트 214)
  • 병합된 마이그레이션 수정 안 함
  • 비밀 값·개인정보 없음
  • global 공통 코드를 바꿨다면 디스코드에 공유함 (해당 없음)

@ji2081 ji2081 self-assigned this Oct 31, 2026
@ji2081 ji2081 added feature 기능 개발 auth 로그인·가입·토큰 labels Oct 31, 2026

@oTAETAEo oTAETAEo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다아 👍

@ji2081
ji2081 merged commit 9c453d0 into develop Nov 2, 2026
1 check passed
@ji2081
ji2081 deleted the feat/32-auth-refresh-token branch November 2, 2026 08:25

@chjpooh2002 chjpooh2002 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 머지되긴 했는데 findByTokenHash에 남긴 코멘트는 한 번 확인 부탁드립니다!


public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {

Optional<RefreshToken> findByTokenHash(String tokenHash);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

재발급 서비스에서 이 메서드로 토큰을 찾고 revoke()로 폐기한 뒤 새 토큰을 저장하는 흐름이 될 것 같은데, 같은 토큰으로 요청 두 개가 동시에 오면 둘 다 "폐기 안 됨"을 읽어서 둘 다 새 토큰을 받게 됩니다. 탈취된 토큰을 공격자와 사용자가 거의 동시에 쓰면 재사용 감지가 안 걸리는 거라, 계보 폐기 설계가 막으려는 상황이 그대로 뚫립니다.

둘 중 하나로 막으면 될 것 같습니다!

  • 이 조회에 @Lock(LockModeType.PESSIMISTIC_WRITE)를 붙인 재발급 전용 조회를 따로 두기
  • update RefreshToken t set t.revokedAt = :now where t.id = :id and t.revokedAt is null 조건부 갱신을 두고, 반영 건수가 0이면 재사용으로 처리

이번 PR에 같이 넣어도 되고, 재발급 PR에서 넣어도 괜찮습니다. 어느 쪽으로 할지만 정해 주세요!

* 계보 전체를 폐기한다 (이미 쓴 토큰의 재사용 감지). 이미 폐기된 토큰은 처음 폐기 시각을 그대로 둔다.
* refresh_tokens에는 updated_at이 없어 직접 넣을 시각 컬럼이 따로 없다.
*/
@Modifying(flushAutomatically = true, clearAutomatically = true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clearAutomatically = true라서 이 메서드를 부른 뒤에는 같은 트랜잭션에서 미리 읽어 둔 RefreshToken이 영속 상태에서 빠지는 점만 재발급 서비스 만들 때 참고해 주세요. 계보 폐기 후 그 엔티티를 고쳐도 반영이 안 됩니다. (설정 자체는 맞다고 생각해요!)

}

/** 만료 시각과 같으면 만료다. */
public boolean isExpired(LocalDateTime now) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사소한 건데, revoke()는 시각이 null이면 규칙 위반으로 막는데 isExpired()는 검사 없이 바로 NullPointerException이 나요. 맞춰 두면 실수했을 때 메시지가 더 분명할 것 같습니다! 수정은 선택사항입니다!


private static final String HASH = RefreshTokenFixture.hash(1);
private static final String FAMILY_ID = RefreshTokenFixture.FAMILY_ID;
private static final LocalDateTime EXPIRES_AT = LocalDateTime.of(2026, 11, 8, 10, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

픽스처에 같은 값(RefreshTokenFixture.EXPIRES_AT)이 있어서 그걸 쓰면 될 것 같아요. 바로 위 HASH, FAMILY_ID처럼요! 선택사항입니다~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth 로그인·가입·토큰 feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 소셜 계정 · 재발급 토큰 엔티티 · 레포지토리

3 participants