티스토리 뷰
DND라는 IT 동아리에서 프로젝트를 기획할때 우리가 중요하게 생각한 한가지는 바로 로그인 없이 사용자가 접근할 수 있게 하는거였다.
로그인이 있다면 귀차니즘이 강한 사용자는 바로 나갈게 뻔하다. 나또한 로그인이 있어야 사용할 수 있는 서비스를 보다보면 그냥 나가기 일쑤였다.
하지만 로그인없이 사용자가 했던 행동들을 다 어떻게 기억할 수 있단 말인가? 우리가 개발하기로 한 프로젝트에서는 로그인은 없지만 사용자가 촬영한 사진, 사용자가 참여하고 있는 모임, 사용자가 모임에서 사용하고 있는 닉네임 등을 알고 있어야 했다.
로그인의 주 역할은 해당 요청을 보낸 사용자가 누구인지 알려주는 역할을 한다. HTTP는 상태를 유지하지 않는 프로토콜이라는 것은 네트워크를 배우다 보면 누구나 접하는 개념이다. 그렇기 때문에 쿠키, 또는 인증 header를 요청마다 보내 해당 요청이 어떤 사용자의 요청인지 확인을 하지만 우린 로그인 없이 사용자가 누구인지 알아야한다.
잘 생각해보면 웹사이트 UI적으로 로그인이 없다는거지 백엔드에 로그인 기능이 없으라는 말은 아니다. 다시말해 사용자는 로그인이 없다고 인지하고 있겠지만 백엔드 내부적으로는 로그인 기능을 만들면 된다.
현재 기획에서는 사용자가 사이트에 접근하기 위해서는 딱 한가지 경로 밖에 없다. 바로 만들어진 모임에 참여하기를 눌러 닉네임을 입력했을때다. 난 이때가 사용자가 회원가입을 하는 상태라고 여겼다.
즉, 원래 우리가 알고있던 회원가입은 사용자가 원하는 이메일과, 비밀번호를 입력해 회원가입을 하지만 여기서는 닉네임 입력을 이용하여 회원가입한다는 것만 달라진 것이었다.
처음 설계한 흐름은 이렇다.
1. 사용자는 모임에 참여한다. 이때 백엔드로 요청을 보낸다. 프론트는 서버로 다음과 같은 값들을 넘겨준다. (모임 id, 참여할 닉네임)
2. 서버는 참여자 테이블에 요청으로 온 닉네임과 외래키로 모임 id를 설정한 후 튜플을 생성한다.
3. 이렇게 만들어진 참여자 id값을 이용하여 JWT 토큰을 생성한다.
4. Set-Cookie 헤더를 이용하여 브라우저의 쿠키에 JWT를 저장하게한다.
이때까진 아주 심플했다... 하지만 요구사항이 하나 더 늘었다. 사용자는 '여러' 모임에 참여할 수 있어야 한다는 것이었다. 이렇게 되면 위처럼 구현하면 문제가 발생하게 된다.
사용자가 첫번째 모임에 참여하고 있는 도중 다른 모임에 참여하게 된다면 ACCESS_TOKEN값이 해당 모임에만 유효한 토큰으로 변경될 것이고 그렇게 되면 사용자는 첫번째 모임에 다시 돌아가더라도 참여할 수 없게 된다.
해당 문제를 해결하기 위해 제일 처음 생각해 낸건 기존에 사용자와 모임이 1:N관계 였다면 이를 N:M관계로 만들어 관리하는 거였다.
한 사용자는 여러 모임에 참여할 수 있고 하나의 모임에는 여러 사용자가 있으니 사용자와 모임의 관계는 N:M이다 즉 사용자 - 사용자_모임 - 모임 과 같이 중간테이블을 하나두고 JWT 토큰을 만들때 사용자 id값을 이용하면 된다. 이렇게 설계한 다음 개발하기 시작하였다. 흐름은 다음과 같이 변하였다.
1. 사용자는 모임에 참여한다. 이때 백엔드로 요청을 보낸다. 프론트는 서버로 다음과 같은 값들을 넘겨준다. (모임 id, 참여할 닉네임)
2. 서버는 요청의 쿠키값에 JWT값이 있는지 확인한다. 이 경우 JWT 토큰이 쿠키에 있는 경우와 없는 경우로 나뉘게 된다.
[JWT 토큰이 쿠키에 있는 경우]
3. 이 경우에는 사용자가 다른 모임에 참여중인것을 뜻한다. 그러므로 JWT 토큰에서 사용자 id값을 추출한다.
4. 사용자_모임 중간 테이블에 추출한 사용자 id값과 요청으로 온 모임 id값을 이용해 튜플을 만든다.
5. 사용자 id값을 이용해 JWT 토큰을 생성한다.
6. Set-Cookie 헤더를 이용하여 브라우저의 쿠키에 JWT를 저장하게한다.
[JWT 토큰이 쿠키에 있는 경우]
3. 이 경우에는 사용자가 다른 모임에 참여중이지 않는 것을 뜻한다. 그러므로 새로운 사용자 튜플을 생성한다.
4. 위에서 생성한 사용자 id와 요청으로 온 모임 id값을 이용해 튜플을 만든다.
5. 사용자 id값을 이용해 JWT 토큰을 생성한다.
6. Set-Cookie 헤더를 이용하여 브라우저의 쿠키에 JWT를 저장하게한다.
새로운 사용자를 만드는 판단을 JWT토큰이 있는지 없는지로 하는 이유는 앞서 말했다싶이 우리 서비스에는 로그인이 없기 때문이다. 사용자가 이전에 모임을 참여했는지 여부는 쿠키에 JWT토큰 여부를 통해서만 알 수 있었다.
이렇게 한다면 사용자가 로그인 없이도 여러 모임에 참여할 수 있게된다. 하지만 위 방식에서는 몇가지 문제점이 있었다.
문제점
만약 사용자의 브라우저에 저장된 쿠키가 만료된다면? 즉, 브라우저에 저장되어 있던 access token이 브라우저에서 삭제된다면 이전 모임에 참여하고 있더라도 다른 모임에 참여시 쿠키에 access token값이 없어 새로운 사용자 튜플을 만들게 된다. 즉, 사용자 테이블의 컬럼이 서비스가 진행되면 될수록 불필요하게 증가하게 될 것이다. 그리고 이에 따라 사용되지 않는 사용자 튜플이 발생하게 된다. 즉, 데이터 베이스 테이블에 사용되지 않는 튜플이 계속해서 쌓이게 된다.
또한 이전에 참가한 모임에는 더이상 접근할 수 없게된다. 이는 두번째 모임에 참여할때 새로운 사용자가 생기고 해당 사용자 id로 새로운 토큰를 발급하므로 이전에 받은 토큰을 사용할 수 없기 때문에 두번째 모임에만 접근가능하고 첫번째 모임에는 접근이 불가능하게 되는 것이다.
이러한 문제점 때문에 기존의 인증 방식을 다시 개선해야했다. 다음은 개선한 방식이다.
개선
새로운 인증 방식에서는 과감하게 기존의 사용자 테이블을 없앴다. 만약 로그인이 있었다면 사용자 테이블이 필요했겠지만, 우리는 로그인 없이 운영되기 때문에 사용자 테이블을 만드는 것은 오히려 큰 오버헤드였다.
그렇다면 사용자 테이블 없이 어떻게 한 사용자가 여러 모임에 참여할 수 있을까?
사실 잘 생각해보면, 한 사용자가 여러 모임에 참여할 수는 있지만, 사용자가 참여 중인 모임 목록을 보여주는 페이지는 존재하지 않는다. 다시 말해, 이론적으로는 한 사용자가 여러 모임에 참여할 수 있지만, 실제로는 한 사용자는 하나의 모임에만 참여하면 되는 구조였다. 그래서 JWT 토큰을 모든 모임에서 하나로 관리하는 것이 아니라, 모임마다 다른 name값의 쿠키로 JWT 토큰을 발급하면 이 문제는 자연스럽게 해결된다.
이 방식을 통해 각 모임에 대해 독립적으로 JWT 토큰을 발급하게 되면, 한 사용자가 여러 모임에 참여하는 상황에서도 충돌 없이 관리가 가능해진다.
다음은 개선한 방식이다.
1. 사용자는 모임에 참여한다. 이때 백엔드로 요청을 보낸다. 프론트는 서버로 다음과 같은 값들을 넘겨준다. (모임 id, 참여할 닉네임)
2. 서버는 참여자 테이블에 요청으로 온 닉네임과 외래키로 모임 id를 설정한 후 튜플을 생성한다.
3. 이렇게 만들어진 참여자 id값을 이용하여 JWT 토큰을 생성한다.
4. Set-Cookie 헤더를 이용하여 브라우저의 쿠키에 JWT를 저장하게한다. 이때 Cookie의 name값을 ACCESS_TOKEN이 아닌 ACCESS_TOKEN_모임ID 로 한다.
이렇게 한후 사용자가 특정 모임에 관련된 조회, 생성, 수정등을 한다면 해당 모임 id값을 다음과 같이 보내게 된다.
/api/meetings/{meetingId}
서버는 meetingId값을 추출한후 ACCESS_TOKNE_{모임ID} 값을 가지는 쿠키가 있는지 없는지 확인하여 인증을 수행한다.
다음은 pathvariable로 오는 meetingId값을 이용해 쿠키를 찾는 코드이다.
위 코드와 같이 pathVariableExtrator class로 meetingId값을 가져온 후 해당 값을 이용하여 인증 쿠키를 찾아 반환한다. 이렇게 찾은 jwt를 이용하여 참자가 ID값을 추출하게 된다.
인증 과정에 대한 자세한 코드를 보고 싶다면 밑의 url을 참고하기 바란다.
dnd-11th-6-backend/src/main/java/com/dnd/snappy/controller/v1/auth/interceptor/MeetingParticipationInterceptor.java at develop
DND 6조 백엔드입니다. Contribute to dnd-side-project/dnd-11th-6-backend development by creating an account on GitHub.
github.com
마치며
로그인이 없는 사이트에서 사용자 인증을 어떻게 관리할지에 대해 많은 고민을 했고, 여러 시행착오도 겪었지만, 개인적으로는 이 문제를 단계별로 해결하면서 최선의 방법을 찾아냈다고 생각한다. 물론, 마지막으로 개선한 방법에서도 여전히 한계는 있다. 예를 들어, 사용자가 다른 브라우저로 접근하면 쿠키 값이 없어 인증이 안 되는 문제가 발생할 수 있다. 하지만 로그인 없는 환경에서는 이 방법이 최선이라고 생각한다.
생각해보면, 별거 아닌 기능처럼 보일 수도 있지만, 이 방법을 내 스스로 찾아내고, 한 번도 도전해보지 않은 방식을 구현해 서비스를 만들어냈다는 사실만으로도 충분히 만족스럽다. 앞으로 더 좋은 개발자가 되기 위해 여러 문제들이 찾아오겠지만, 이번처럼 언제나 그 자리에서 최선의 방법을 찾아낼 거라 믿는다.
'프로젝트' 카테고리의 다른 글
On-The-Fly 이미지 리사이징 도입기 (0) | 2024.09.20 |
---|---|
성능 테스트 자동화 환경 구축기 (2) (0) | 2024.09.15 |
성능 테스트 자동화 환경 구축기 (1) (0) | 2024.09.09 |
[트러블 슈팅] 채팅 서비스 (1) | 2023.08.09 |