10cheon00 / drf-practice

0 stars 0 forks source link

Create token refresh flow. Redirect when different user access to article flow. #26

Closed 10cheon00 closed 3 years ago

10cheon00 commented 3 years ago

Token Refresh flow

참고한 사이트

  1. api 요청을 하기전, 내가 갖고 있는 토큰이 유효한지 검사해야한다. 안그러면 요청이 무조건 거절당하기 때문에!
  2. axios 객체를 매번 설치된 서드파티에서 갖고 오기보다, 내 입맛에 맞게 생성한 객체를 이용하기로 했다. https://github.com/10cheon00/RESTful-blog/blob/77e82ff9d6190591a31c4c9de6c9505f603b8694/blog_project/frontend/src/store/BackendApi/axiosWrapper.js#L8-L11
  3. axiosInstance를 만들고, interceptor를 이용해 요청을 보내기 전에 토큰이 유효한지 검사하기로 했다.
  4. interceptor에서는 request와 response 두 가지 모두 관찰할 수 있다. request에서 갖고 있는 토큰을 담아 전송, response에서는 요청한 토큰검증이 잘 되었는지 검사한다. https://github.com/10cheon00/RESTful-blog/blob/539ceda970cb1384f6696d29cfb5e90f06376d38/blog_project/frontend/src/store/BackendApi/axiosWrapper.js#L13-L46 headers와 data에 모두 담아 전송하는데, headers에 담는 이유는 authentication을 통과하기 위함이다. data에 담는 경우는 url이 verify, refresh일 때만 검증을 위해 담았다.
  5. 검증이 일어나는 상황은 다음과 같다.
    • 토큰이 필요한 route에 접근. → router.beforeEach에서 캐치하여 dispatch('VerifyToken')을 호출. → interceptors.request에서 토큰을 담아 전송. (VerifyToken request) → 만료시간이 지나지 않았다면 통과, 정상적으로 페이지 이동. ( 에러가 발생했다면 ) → 만료시간이 지났다면 401 Unauthorized 발생, interceptors.response에서 캐치, → 401에러라면 config에 isRetried를 추가해 무한 요청을 방지 후 dispatch('RefreshToken') 호출. (RefreshToken request) →RefreshToken으로 갱신된 토큰을 갖고 다시 api 요청. (VerifyToken request)router.beforeEach에서 요청한 VerifyToken이 통과되었으므로 정상적으로 페이지 이동.
    • 추가, 수정, 삭제 요청. → request에서 headers에 토큰을 추가하여 요청. (Create/Update/Destroy request) → 만약 만료되었거나, 토큰이 정상적이지 않은 경우, 위의 에러가 발생했을 때처럼dispatch.('RefreshToken') 호출...

문제가 있다면 어떤 요청에도 토큰을 검사해, 만료되었다면 새로 발급해주어야 한다는 점이다.

Check user equals to article author

  1. 먼저 Article 모델에 author필드를 추가, foreignKey로 Profile과 연결했다.
  2. IsOwner Permission을 추가, 토큰을 검증해 나온 user id와 게시글의 author id가 같아야 권한을 허용하도록 변경했다. https://github.com/10cheon00/RESTful-blog/blob/539ceda970cb1384f6696d29cfb5e90f06376d38/blog_project/backend/articles/views.py#L31-L33 이렇게 했다면 api요청 시 토큰만 가지고도 접근을 막을 수 있다. 하지만 백엔드는 최후의 방어막이고, 프론트에서 막는 방법도 추가해야했다.

권한을 변경하는 작업때문에 뷰를 읽기전용뷰와 쓰기전용뷰로 나누었다. https://github.com/10cheon00/RESTful-blog/blob/539ceda970cb1384f6696d29cfb5e90f06376d38/blog_project/backend/articles/views.py#L14-L28

https://github.com/10cheon00/RESTful-blog/blob/539ceda970cb1384f6696d29cfb5e90f06376d38/blog_project/backend/articles/views.py#L36-L54

각 http 요청에 따라 매칭되는 액션을 호출했다. APIView들이 이렇게 다 되어있어서 일일이 호출해주어야 했다.

  1. 게시글을 수정 또는 삭제하려고 할 때 사용자의 id와 작성자의 id가 같아야만 페이지가 이동되도록 해야한다. 그러려면 먼저 사용자의 id를 알아야 하는데 프론트는 토큰만 갖고 있기 때문에 토큰을 뜯어보기 전까진 알 수가 없다. JS로 뜯으면 되지만, 깔끔한 방법은 아니라고 생각되어 백엔드에서 가능한 방법을 생각해봤다.
  2. Verify나 SignIn 요청을 할 때, 성공할 경우에만 사용자의 정보를 넘겨주는 방법이 적절한 것 같다. https://github.com/10cheon00/RESTful-blog/blob/539ceda970cb1384f6696d29cfb5e90f06376d38/blog_project/backend/profiles/views.py#L41-L48 검증에 사용되는 TokenVerifyView를 상속, post 메소드만 override했다. 지금보니까 response.status가 200이 아닐 때에도 전송한다. 수정해야겠다.

https://github.com/10cheon00/RESTful-blog/blob/539ceda970cb1384f6696d29cfb5e90f06376d38/blog_project/backend/profiles/views.py#L51-L61 JWTAuthentication객체에 토큰을 검증하는 코드와 유저id를 알아내는 코드가 담겨 있어 그대로 이용했다. SimpleJWT에서 제공하는 객체다. 객체는 serialize될 수 없어서 dictionary로 일일이 쪼개 넘겼다. 아마 이것도 유틸이 있을 것 같다.

로그인에 성공할 때도 똑같이 response.data['profile']에 유저 정보를 담아 넘겼다. 이렇게 api설정을 마치면 토큰을 이용해 사용자 정보를 얻을 수 있고, vuex에 이 정보를 저장, 필요할 때마다 조회할 수 있다.

  1. Route객체의 beforeEnter로 네비게이션 가드를 할 수 있다. https://github.com/10cheon00/RESTful-blog/blob/539ceda970cb1384f6696d29cfb5e90f06376d38/blog_project/frontend/src/router/articleRoute.js#L15-L32 url 직접접근을 할 수도 있어 비동기로 작성했다. 게시글을 갖고온 후, id를 비교한다. 같으면 통과, 틀리면 게시글 페이지로 이동한다.