one-day-one-meal / one-hour-one-meal

1 stars 0 forks source link

✨ Refresh token을 저장하고 토큰 재발급 / 로그아웃을 할 수 있게 구현 #87

Closed mobzzzzz closed 4 months ago

mobzzzzz commented 4 months ago

요약

간단 요약

User와 OneToOne 으로 구성되어 있는 RefreshToken Entity로

  1. 유저가 로그인시 Refresh token을 저장합니다
  2. Access token이 만료되면 Exception이 발생하고 클라이언트는 /auth/refresh-token 으로 "RefreshToken" header에 refresh token을 담아 보냅니다.
  3. refresh token이 만료되지 않은 상태라면 새로운 AccessToken / RefreshToken을 발급해 클라이언트에 반환합니다. 3-1. Refresh token이 재발급됨으로서 로그인 세션의 유지 기간이 늘어나는 기능도 겸합니다. 3-2. Refresh token도 만료된 상태라면 토큰 재발급이 불가능하므로 로그아웃 된 상태로 간주됩니다.
  4. DB의 Refresh token도 새롭게 갱신합니다.
  5. 로그아웃시 DB의 Refresh token을 삭제합니다. 6-1. Refresh token이 만료되기 전까지는 Access token을 계속 사용할 수 있는 상태인 건 어쩔 수 없습니다.

JWT에 type: "access", "refresh" 를 구분해 이제 Refresh token으로 인증이 불가능합니다. 조사해보니 Security Filter에서 만료를 검증하고 즉시 재발급해 Response에 담아 보내는 방법도 존재하나

작업 사항

PR에서 작업한 사항들을 적어주세요(이미지, 스크린샷등을 활용해도 좋아요)

Refresh 토큰 처리 (Access token 재발급 포함) 로그아웃 구현 Token type 구분

리뷰 요청 사항(선택)

리뷰시 봐주었으면 하는 부분이 있다면 적어주세요

추가로 @OneToOne 관계의 트러블슈팅이 좀 있었는데 양방향이 아님에도 Cascade를 진행해야 로그인시 User Entity의 정보를 Hibernate가 확실히 체크하고 관계를 맺어주는 이슈가 있었습니다. Assertion failure null identifier 이후 CascadeType.ALL로 진행하면 로그아웃으로 Refresh token을 삭제할 때 User에 전파하려고 하는 이슈도 있어 준영속 상태의 User를 즉시 반영하는 CascadeType.MERGE 만 사용했습니다. (테스트 결과 Persist는 필요없는 것 같습니다.)

단방향이라 Cascade를 의심 할 생각이 없어 이걸로 고생좀 했는데 JPA가 이해하는 @OneToOne 관계에 대해 혹시 알려주실 분 있으면 저 좀 알려주세요 땡큐


해결한 이슈

closes: #26

mobzzzzz commented 4 months ago

Refresh token을 무한정 재발급하는 건 보안 정책에 좋지 않아 내용좀 수정하고 Access token만 재발급하게 수정하겠습니다

mobzzzzz commented 4 months ago

OneToOne Null identifier 이슈 추가 조사

ManyToOne과 OneToOne의 관계에서 오는 차이가 좀 있는 것 같습니다 ManyToOne은 보통 Many쪽이 연관 관계의 주인이 되어 자식을 추가할 때 자식이 영속화 되어있지 않은 경우가 드물어 Null identifier가 발생할 여지가 적은 반면에 OneToOne은 관계 주인이 명확하지 않아 어느 한 쪽이 영속화가 되어있지 않은 경우가 발생할 여지가 있고 이 과정에서 CascadeType.MERGE를 해주면 다른 한 쪽도 영속화해서 단방향임에도 관계를 맺어줄 때 관계된 엔티티에 대해 영속성 컨텍스트가 연관 엔티티의 식별자를 확실히 할당받고 진행하는 구조인 것 같습니다. 이번 User-Refresh 관계의 경우 Refresh가 User를 들고 있는 단방향 OneToOne인데 @Transcational signIn 내부에서 User를 불러오긴 했지만 updateRefreshToken 이 끝나는게 먼저라서 Sign-in transactional 이 끝나기 않았고 이 과정에서 updateRefreshToken은 UserId까지 다 들고있는 엔티티를 받았음에도 영속성 컨텍스트에선 영속화 되지 않은 상태라 null identifier 문제가 발생했다고 볼 수 있을 것 같습니다. 해결책으론 이번에 쓴 Cascade로 OneToOne 관계의 병합을 명시적으로 관리해주거나 updateRefreshToken 안에서 user를 또 조회하는 방법이나 sign-in 내부에서 중간에 영속성 갱신을 하는 방법이 있을 것 같기도 하네요 (.save 하면 될 것 같습니다)