[인증 작동 원리 정리] 서버 기반 인증 방식(server-side sessions) vs 토큰 기반 인증 방식(authentication tokens)

이온·2023년 10월 4일
0

기타

목록 보기
5/10
post-thumbnail

모든 HTTP 요청은 독립적?(stateless)이기 때문에 서버에 요청할 때마다 우리가 누군지 알려줘야한다.
보통 서버가 클라이언트 인증을 확인하는 방식은 대표적으로 쿠키, 세션, 토큰 3가지 방식이 있다. (쿠키+세션 & JWT 이렇게 분류 가능)

1️⃣ Cookie 인증

쿠키는 Key-Value 형식의 문자열로 구성되어 있고 클라이언트가 웹사이트를 방문할 때, 사이트가 사용하고 있는 서버를 통해 클라이언트의 브라우저에 설치되는 기록 정보 파일

과정 : 브라우저가 서버에 요청을 보내고, 응답 헤더에 set-cookie에 정보를 담는다 → 이후 브라우저는 요청을 보낼 때마다 매번 저장된 쿠키를 요청 헤더에 cookie에 담아 보낸다 → 서버는 그 쿠키 정보를 바탕으로 사용자를 식별한다

단점 : 보안 취약, 용량제한, 공유 불가, 네트워크 부하 심해짐

2️⃣ Session 인증

이러한 쿠키의 보안적인 이슈 때문에, 세션은 비밀번호 등 클라이언트의 민감한 인증 정보를 브라우저가 아닌 서버 측에 저장하고 관리한다. 서버의 메모리에 저장하기도 하고, 서버의 로컬 파일이나 데이터베이스에 저장하기도 한다. 서버에서 통제가 가능하다.

과정 : 유저가 로그인하면 서버는 그 정보를 DB에 저장된다(고유 식별자, 세션id부여) → 세션id를 쿠키에 담아 응답하고 브라우저에 저장 → 이후 브라우저는 서버에 요청을 보낼 때 쿠키랑 함께 오는 세션id를 확인 → 세션id를 DB에서 찾아 유저 확인 → 사용자 식별 → 식별자가 옳다면 엑세스 허가, 다르다면 거부 → 식별자를 가진 클라이언트만이 요청을 보낼 수 있게된다

단점 : 쿠키의 세션id 자체는 큰 정보가 없지만 그 아이디를 활용해 클라이언트인척 위장할 수 있다는 한계 존재. 서버에서 DB를 사용하기에 비용이 들고 요청이 많아지면 서버에 부하가 심해진다. 조회하는 과정에서 많은 오버헤드 발생.

사용자가 입력한 아이디와 비번이 맞다면 서버는 세션 DB에 유저 정보를 만든다. 해당 세션에는 유니크한 id가 있다. 그 id는 서버에서 쿠키를 통해 브라우저로 돌아오고 저장된다. 다른 페이지로 이동하면 브라우저는 세션 id를 쿠키에 담아 서버에 요청보내게 된다. (쿠키는 자동으로 보내지니까) 서버는 세션 id와 함께 오는 쿠키를 확인하고!!! 서버는 이 세션 id를 세션 DB를 확인한다. 이제 거기서 해당 id는 유저 누구누구의 것이라는 것을 알게 된다!! 바로 그제서야 서버는 우리가 누군지 알게 된다. 페이지를 이동하면 이 모든것을 반복하게 된다. 기억해야할건 중요한 유저 정보는 모두 서버에 있다는 것. 유저가 갖고있는건 세션 id일 뿐. 쿠키는 그저 세션 id를 전달하기 위한 매개체일 뿐이다.

🔸 서버사이드세션: 재정리

client ---request(with user credentials)---> server
server ---response(yes/no)---> client
간단하게 이런식의 인증 단계를 거치면 클라이언트는 보호된 라우트에 접근하거나 인증이 필요한 서버 내 다른 엔드포인트 또는 URL에 요청을 보낼 수 있게 된다. (예를들면 마이페이지, 프로필페이지 등) 예를들면 비번 변경 요청 은은 반드시 사용자가 인증되었을 떄만 허가, 수행 되어야한다. 따라서 그런 요청에는 추가 첨부 데이터(추가 권한, permission)가 필요하다는 것이다. 백엔드에게 이 사용자는 인증되었다고 알려주는 첨부 데이터 말이다. 왜냐면 API 요청을 보내는 것은 누구나할 수 있기 때문.
permission이 중요하다!! 왜냐면 서버에서 단순히 yes/no로만 응답하는게 아니기 때문이다. 그래서 인증에는 권한을 위한 크리덴셜 교환이 수반된다. 따라서 위조할 수 없는 증거가 필요. 그리고 연결을 암호화하는데 SSL을 사용하기에 도난 걱정은 필요없다.
그리고 클라이언트에서는 대체로 쿠키에 이 식별자를 저장한다. 크로스사이드 스크립팅 공격을 방지하기 위해 자바스크립트를 통해서는 쿠키에 접근할 수 없도록 구성할 수 있다. 그러면 나가는 요청에 첨부되었을떄 서버쪽에서만 읽을 수 있다. 자바스크립트를 통해 엑세스가 가능하다고 해도 어쨌든 사이트 간 스크립팅 공격은 막아야한다. 그 공격을 막는다면 인증권한은 안전할 것이다.

3️⃣ Token 인증

과정 : 서버는 로그인한 사용자들에게 토큰을 발급 → 클라이언트는 토큰을 쿠키나 스토리지에 저장한다 → 클라이언트가 서버에 요청을 보낼 때 헤더에 토큰을 함께 보내도록 하여 유효성 검사를 한다. 서버측에서는 복잡한 인증 과정 없이 토큰만으로 사용자를 인증 및 인가할 수 있는 것이다.

토큰은 app에서 가장 많이 사용 (쿠키와 세션은 웹에만 존재)

사용자의 id,비번이 맞다면 그냥 사인된 정보를 토큰으로 보냄. 이제 그 토큰이 유효한지만 서버는 요청마다 계속 체크해주면 됨.

장점 : 토큰은 클라이언트에 저장되어서 서버의 부담을 덜 수 있다. 또 서버와 독립적이라 어떠한 서버로 요청이 와도 상관없다.(무상태성Stateless, 확장성) 또 쿠키를 사용하지 않아도 되어서 보안성이 증가함. 또 서버 기반 인증 시스템의 문제점 중 하나인 CORS를 해결할 수 있음

단점 : 쿠키, 세션과 다르게 토큰 자제의 데이터 길이가 길어 요청이 많아질수록 네트워크 부하가 심해짐, 페이로드 자체는 암호화 되어있지 않아서 유저의 중요한 정보는 담을 수 없다, 토큰을 탈취당하면 대처가 어려움(사용기한제한설정)

쿠키는 브라우저에만 존재, ios나 안드로이드같은 네이티브 앱에는 없음
바로 이 경우 ‘token’을 사용!!! 서버에 토큰을 보내는 것.
토큰은 그냥 이상하게 생긴 string이다. 민증처럼 서버에게 보여줘야하는 것. 토큰을 서버에 보내고 서버는 세션 DB에서 해당 토큰과 일치하는 유저를 찾음.

세션을 사용할 때 중요한 건 바로 세션 DB! 현재 로그인한 유저들의 모든 세션 id를 DB에 저장한다. 즉 모든 요청이 들어올 때마다 서버는 쿸키를 받아서 세션 id를 보고 일치하는 유저를 DB에서 찾고 그 다음 작업을 수행할 수 있다. = 요청이 있을 떄마다 DB를 찾아야한다. 즉 유저가 늘어남에 따라 DB리소스가 더 필요. 이떄 JWT등장!

🔸 토큰기반인증 재정리

여기서는 서버가 어떤 식별자도 저장하지 않아도 된다. 대신 서버는 임의의 문자열이라고할 수 있는 토큰을 생성하고 서명한다. 데이터 패키지를 열 수 있는 임의의 문자열이다. 빠르게 다양한 데이터 조각을 가져와 서명하고 이 토큰이 클라이언트로 전송된다. 클라이언트는 해당 토큰을 저장하고 나가는 요청에 다시 첨부해 서버에 엑세스를 허가해도 된다고 알린다. 그 토큰을 데이터베이스에 저장해두지 않았더라도 서버는 해당 토큰에 어떻게 서명했는지 기억하고 있다. 그래서 서버는 이 토큰이 자기가 만든건지 아닌지 확일할 수 있다.

🔸 SPA with 토큰 인증방식

SPA을 구축할 때 많은 사람들이 Next.js를 이용 해 작업하게 된다. 그리고 세션보다 토큰 인증을 사용하게 된다. 페이지는 직접 제공되고 반드시 서버에 도달하지 않아도 로직으로 채워진다. 물론 Nextjs로 작업할 때도 getServerSideProps를 사용하는 페이지를 구축할 수 있으니 페이지가 제공될 때 마다 서버에서 처리하는 요청도 물론 있다. 하지만 미리 생성된 페이지도 많이 있다. 그리고 일단 사용자가 웹사이트에 있으면 많은 페이지를 백엔드에서 가져올 수 없고 대신 프론트엔드 자바스크립트를 통해 동적으로 로드 및 생성된다. 첫 페이지가 로드된 후 웹사이트를 구성하는 많은 페이지가 싱글 페이지 어플리케이션을 가지기 때문이다. 그리고 자바스크립트와 리액트가 인계받아 웹사이트를 처리한다. 우리가 방문하는 모든 페이지마다 요청을 보내는 것은 아니다. getServerSideProps를 모든 페이지에서 사용하진 않기 때문이다. 따라서 서버는 우리가 보내는 요청 전부를 보지 못한다. 그러므로 서버가 우리의 인증 여부를 직접 확인할 수 없는 상태에서 페이지가 로드된다. 또한 싱글페이지 애플리케이션에 사용되는 백엔드 API는 일반적으로 무상태(stateless)이다. 연결된 개별 클라이언트들을 따로 신경 쓰지 않는다. 연결된 클라이언트를 전부 추적하지 않는다. 대신 API가 거의 자체적으로 작업을 수행할 수 있는 것이다. 그리고 인증된 클라이언트에 권한을 전달해서 보호된 리소스에 대한 엑세스를 나중에 요청할 수 있다. API 자체는 연결된 클라이언트에 대한 어떠한 추가 정보도 저장하지 않는다. 이게 싱글 페이지 애플리케이션을 구축하는 방법이기 때문에 서버는 페이지에서 일어나는 모든 요청이나 작업에 일일이 관여하지 않는다. 왜냐하면 프론트엔드의 자바스크립트로 처리하고 SPA에 연결되는 무상태성 API도 있기 때문이다. 이러한 사실을 보면 일종의 분리된(detached) 프론트, 백엔드 조합을 가진다고 할 수도 있다. 가끔은 서로 말을 걸지만 페이지에서 진행되는 모든 작업에 대해 시시콜콜 얘기하지 않는다. 그렇다보니 서버는 인증된 클라이언트에 대한 정보를 저장하지 않는다. 대신 클라이언트가 본인이 인증되었음을 증명하는 독립적인 권한을 얻어야한다. 그래서 토큰(tokens, JWT)을 사용하는 것이다. 구체적으로는 JSON 웹 토큰이라는 개념을 사용한다. 가장 일반적인 인증 토큰이라 볼 수 있다.

JWT

인증에 필요한 정보들을 암호화시킨 JSON 토큰을 의미한다. 토큰 인증 방식과 비슷한 과정. 그리고 JWT 기반 인증은 JWT 토큰(Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별하는 방식이다. 헤더, 페이로드, 서명으로 되어있다.

장점 : 데이터 위 변조 막을 수 있다, 별도의 저장소 필요없다, JWT자체에 모든 정보를 갖고있다, 서버에서 저장하지 않아 무상태가 되어 서버 확장성이 우수함, 토큰 기반 다른 로그인 시스템에 접근 및 권한 공유 가능, 모바일 환경에서도 잘 동작한다.

단점 : 토큰 자체에 정보가 있어 위험, 정보가 많아질수록 길어져 네트워크에 부하, 페이로드 자체는 암호화된것이 아니라 주용 정보를 넣으면 안됨, 토큰 자체를 탈취당하면 대처 어려움(만료시간 꼭 설정)
정보가 많아질수록 토큰의 길이가 늘어나 네트워크에 부하를 줄 수 있음. 임의로 삭제하기가 불가능하기에 꼭 만료시간을 넣어줘야한다.

JWT는 정보를 갖고있는 토큰 ! 이것으로 인증 처리를 하면 세션 DB는 필요없고 서버는 유저인증을 위해 많은 일을 하지않아도 됨.
사용자가 입력한 아이디와 비번이 맞다면 서버는 DB에 따로 생성하지 않음!(세션과의 차이) 대신 서버는 유저의 id를 가져다가, 예를들면 사인 알고리즘을 이용해 ‘사인’을 한다. 그리고 해단 ‘사인된 정보’를 string형태로 보낸다. 쿠키는 공간 제약에 있어 짧지만 JWT는 제약이 없어서 엄청 길다. // 로그인을 했을 때 DB를 건드리는 대신 정보를 사인하고 전달하는게 끝. 이제 페이지 이동시마다 요청을 서버에 보낼 때는 token 혹은 사인된 정보를 보낸다. 서버는 토큰을 받으면 해당 사인이 유효한지 체크하고(조작된 토큰이 아닌지 확인) 서버는 우리를 유저로 인증할것.
근데 염두할 것은 JWT는 암호화되지 않음. 누구나 열어서 컨텐츠를 확인할 수 있다.

JWT를 저장하기 위한 장소는 로컬 스토리지나 쿠키가 있다.

쿠키에 저장하면 쿠키는 http only라서 자바스크립트에서 접근이 불가능하다는 장점이 있지만 요청을 위조할 수 있다는 단점이 있다(XSS공격에 상대적으로 안전, CSRF공격에 약하다.) → 가장 좋은 방법으로는 refersh token을 사용하는 방법이 있다.

🔸 JWT 재정리

JSON 웹 토큰은 3개의 주요 블록으로 구성된다.

첫번째로 Issuer Data, 발급자 데이터가 있다. 토큰이 생성될 때 서버에 의해 토큰에 자동으로 추가되는 데이터이다. 일반적으로 우리 스스로 개발자로 설정하지 않지만 토큰을 생성할 때 이용한 서드파티 패키지에 의해 미리 구성되는 메타데이터이다.

둘째로 토큰에 Custom Data(user data), 커스텀 데이터를 추가할 수도 있다. 사용자 정보같은 것이다.

그리고 가장 중요한 세번째, 비밀 키(Secret signing key)를 서버에 설정한다. 클라이언트는 절대 그 키를 볼 수 없다. 해당 키가 있어야만 서버가 인정하는 유효 토큰을 생성할 수 있다. 서버만이 그 키를 알고 있다.

그리고 서드 파티 패키지를 사용해 이런 조각들을 하나로 결합하고 이 모든 데이터를 포하하는 임의의 문자열을 만들어내고 해당 키로 서명해서 토큰을 생성한다. 이것이 바로 아주 긴 문자열인 JSON web token이다. 여기서 서명은 암호화를 뜻하는 것이 아니다. JWT는 암호회되지 않는다. 키가 없어도 열어서 내부의 키를 읽을 수 있다. 키는 주어진 서버가 그 토큰을 생성했다는 사실만 증명한다. 즉, 키를 몰라도 토큰 내용을 읽을 수 있다. 물론 키는 토큰에 포함되지 않는다. 그러니 토큰을 열어도 키를 읽을 수는 없다! 그리고 토큰은 클라이언트 사이드 브라우저에 저장되어 서버의 보호된 리소스에 대한 요청에 첨부된다. 두개의 보호된 API 라우트에. 예를들어 비번을 바꾸려고 한다면, 나가는 요청에는 예전 비번과 새 비번 뿐 아니라 해당 토큰도 포함되어야 한다. 그리고 그 토큰은 서버에서 유효성 검사를 받는다. 나만 알고 있는 서명 키가 있는데 이걸로 토큰을 만들 수 있는지? 검사한다. 그게 가능하다면 서버는 유효한 요청인 것을 알게된다. 아니라면 무효가 되어 엑세스가 거부된다. 그래서 키를 모르고선 토큰을 생성할 수 없는 것이다. 토큰을 생성할 수 있긴하지만 유효하지 않은 토큰이 되는 것이다. 이렇게 보안 메커니즘이 돌아간다.

📍한마디로!!
세션에선 서버가 그냥 세션 id만 주면 됨, 세션에 대한 모든 정보는 세션 DB에 저장되어있다. 페이지를 요청하면 서버는 세션id를 DB에서 찾으면 됨.
JWT에서는 서버는 유저를 인증하는데 필요한 정보를 토큰에 저장하기에 우리에게 해당 토큰을 준다. 페이지를 요청하면 서버는 해당 토큰이 유효한지만 검증함. DB를 거칠 필요가 없다,

📍 장/단점 요약

세션에서는 서버는 로그인된 유저의 모든 정보를 저장. 해당 정보를 이용하면 새로운 기능들을 추가할 수 있다. 예를들면 해당 유저를 쫓아내고싶다면 그냥 세션을 삭제하면 끝. 혹은 인스타처럼 로그인된 모든 디바이스를 보여주는데 원하지 않는 디바이스에서 강제 로그아웃을 할 수 있다. 아님 넷플처럼 계정 공유 숫자를 제할할 수 있다. 이 모든게 가능한 이유는 서버는 누가 로그인했는지 저장했고 세션 DB가 있기때문. 하지만 이를 위해 DB를 사고 유지해야함. 주로 DB는 redis를 사용.

JWT를 사용하면 생성된 토큰을 추적하지 않음. 서버가 아는 것은 토큰이 유효한지 아닌지 뿐이다. 그래서 디비를 따로 살 필요가 없다. 하지만 그렇기때문에 강제 로그아웃기능 불가. 왜냐면 해당 코튼이 만료되기 전까지는 유효하니까. DB없이 데이터에 사인하고 유저에게 보내고 해당 데이터를 돌려받을때 유효성을 검증할 수 있는 모든 과정이 가능하다. 얘를들면 qr체크인은 JWT가 들어간 qr코드이다. 세션이나 디비없이 빠르게 유저 인증하기 좋다. 하지만 유저가 많아지거나 계정을 더 잘 관리하고싶다면 그떄 세션으로 옮기는 방법도 좋다.

참고) 유튜브 노마드 코더, https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-JWTjson-web-token-%EB%9E%80-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC

profile
👩🏻‍💻

0개의 댓글