[픽플] (고민) FE-BE 간 API 의논

이지우·2021년 5월 3일
0
post-thumbnail

이전 학기 프로젝트에서 리액트로 FE까지 손대본적이 있지만,
그때는 FE, BE 거의 구분 없이 기능별로 구현했었는데 워낙 주먹구구식 무지성 개발이어서 살짝씩 맛보기만 했었다.

픽플 프로젝트에서는 FE, BE 구분이 명확하여 내가 어떤 기능이 필요한지, 어떤 기능을 개발해야 하는지 요구하고 개발할 수 있게 되었다.

내가 맡고 있는 분야는 백엔드인데, 프론트에서 "헤더에 있는 토큰을 이용해서 사용자 정보의 일부를 클라이언트측에서 알고싶다" 라고 했다.

처음에는 'jwt토큰 내에 사용자의 아이디를 가지고 있는데, 왜 재요청을 해서 사용자의 정보를 가져야 하지?' 라고 생각했다.

FE측 이야기를 들어보니 '예를 들면 회원이 모집글 수정버튼을 누를 때, 내가 이 버튼을 누를 수 있는지 아닌지 클라이언트 측에서 판별해야 한다. 혹은, 내가 body에 회원의 id를 넣어서 넘겨주는 요청이 있을 때, 클라이언트 측에서는 토큰만 가지고 있을 뿐 id를 가지고 있지 않으니 해당 api가 필요해진다' 는 말인 것 같았다.

우선 요청이 들어왔으니 개발을 하던 중에, 무언가 모순을 느껴서 생각을 정리할 겸 고민을 적어본다.

픽플에서 사용하는 환경들을 나열해 본다.

  • spring boot
  • spring security (filter, security context에 대한 이해가 필요함)
  • jwt

클라이언트-서버에서 jwt토큰을 가지고 request & response 하는 흐름을 내가 이해한대로 정리해보자면,

  1. client에서 header에 token을 가지고 있고, 특정 api request
  2. server측에서는 filter를 통해 token을 풀어헤쳐, 해당 토큰의 유효성 검증
  3. 유효한 토큰이면 토큰에서 id 뽑아냄
  4. context에 account 객체 저장 (해당 api 내에서는 context에 계속 접근 가능하고, 끝나는 순간 context는 사라짐)
  5. 뽑아낸 id를 이용해, repository에서 findById(id)한 후 account테이블에서 권한을 알아낼 수 있고, 해당 api에 대한 권한 검증 가능 (우리는 권한 테이블이 따로 있지 않고, 사용자 테이블에 enum타입으로 가지고 있음)
  6. 해당 api에 대한 권한이 있으면 (@PreAuthorize) api 로직 시작
  7. client에 response 보내줌

이런 흐름이라서, 토큰을 가지고 요청만 하면 되는 거 아닌가? 라고 생각을 했는데,
React에서 상태를 관리하기 위해 Redux를 사용하는데, 유저로딩이라는 개념을 위해 사용자 정보를 저장하는 것이 바람직하다고 한다. 정확히는 정보를 저장한다기보다는, 정보를 이용한다는 개념인 것 같다.

어떻게 해결해야 할까?

토큰을 헤더에 가지고 있는 채로 '요청'을 할텐데, 이 요청 자체가 '토큰을 이용해 사용자 정보를 불러오고 싶다' 인 것이다.
즉 '토큰을 이용해 사용자 정보를 불러오고싶다'는 api를 만들어달라는 요청인데, api는 필터를 거치면서 벌써 토큰을 이용한다. 토큰을 두 번 접근해서 풀어헤친다는 개념이 말이 안되는 것 같았다.
내가 뭔가 착각하는 것 같다. 다시 생각해보자.

1. 토큰은 request를 했을 때 HttpServletRequset안에 넣은 채로 들어온다.

  • 모든 요청은 필터를 거친다. filter에서는 내부적으로,
    • 이 요청의 Header에서 토큰 내용을 가져온다.
    • 가져온 토큰이 유효한지 검사한다
    • 유효하다면 context에 저장을 한다.

해결!

위 과정까지 생각했을때 도움을 받았다.
'토큰 주면 -> 회원 정보 반환' 이라는 'api'라고 생각하는 것에 집착했기 때문에 모순을 느낀것이다.

토큰에 두 번 접근하는 것은 말이 안된다.
따라서 이미 사용자에 대한 context를 저장하고 있는 곳으로 접근하면 해결되는 것이었다.

  • Context에 대한 Service를 담당하는 AccountContextService클래스를 생성했다.
  • Account ContextServiceSecurityContextHolder를 통해 SecurityContext에 접근한다.
  • 거기서 또 context에 있는 사용자 Authentication에 접근한다.
  • Authentication이 가지고 있는 Name 정보를 반환한다. UserDetails에서 Name정보를 Long타입의 Id(고유번호)로 저장해두었기 때문에 getName()을 하면 회원의 아이디를 불러낼 수 있다.
  • 해당 아이디를 이용해 jpaAccountRepository에서 Account.findById(id)를 이용해 객체를 Dto에 담아서 return했다.

SecurityContextHolder.getContext().getAuthentication();

+) 2021.06.13
우선 프로젝트를 끝내기는 했지만, User에 대한 Info를 FE측에 넘겨주는 것은 잘못된 것이라 본다. 애써 보안에 신경을 썼는데 뭔가 찜찜한 느낌이 사라지지 않았다.
SecurityContext의 핵심에서 조금 벗어나서 고민을 했던 모양이다.

개인적으로 결론지어 보자면 FE측은 api를 요청만 하는 게 맞다. User에 대한 Info를 가지고 다니면서 해당 info를 이용해서 이것저것 요청을 하는 것은 안 될 것이었다. 가령 mypage를 보려고 미리 요청해둔 UserInfo에서 내 id, 이름, ... 등등에 대한 정보를 활용해서(가령 파라미터로 id를 넣는다던가)BE측에 요청하는 것은 잘못되었다.
FE는 Token을 담은 Header를 가지고 api를 요청하는 것이고, BE에서는 SecurityContext에서 해당 토큰의 사용자가 누구인지 파악해서 '필요한'정보만을 넘겨줬어야 했다. 🧐

profile
개발 관찰일지

0개의 댓글