티스토리 뷰

반응형

오늘은 Client와 Server 간의 API 사용을 위한 인증관련한 방안들에 대해 알아보도록 하겠습니다. 

인증, 꼭 필요한것인가?

우선 꼭 필요한지에 대해서 이야기하면, 개발 목적으로 자기 혼자 또는 팀내에서만 사용한다면 문제될 게 없지만 서버가 오픈되서 유효하지 않은 요청들이 감당없이 들어오게 되는 것을 막고, 해킹의 염려를 1차적으로 걸러내기 위해서 필요한 부분이기에 상용으로 운영한다면 필요하겠지요. 공공의 API를 지원하는 곳을 쉽게 볼 수 있지만 무료로 아무런 인증없이 요청되는 것의 정보는 제한 적이고, 과금을 한다던지 해서 별도 발급되는 api key값과 함께 요청하는 경우에는 좀더 양질의 API를 얻을 수 있겠죠? 이렇게 단순히 해킹이나, 대규모 트래픽을 막기위해 인증이 필요한 것도 있지만 자체적인 비즈니스 모델을 위해서도 인증은 필요합니다.

그럼 이러한 인증방식은 어떤 방법들이 있고 각각의 인증흐름은 어떻게 될까요?

서버 기반 인증 (Session, Cookie)

HTTP 프로토콜은 요청에 따른 응답을 받으면 연결이 끊어지고, 통신이 종료되면 어떠한 상태 정보도 남지 않게 됩니다.  Stateless 하다라고 말히지요. 따라서 로그인 후 다시 웹페이지에 접근하면 로그인 상태가 유자 되지 않기 때문에 이러한 문제를 해결하기 위해 사용하는 방법으로 세션과 쿠키를 사용할 수 있습니다.

인증 흐름

  1. 사용자가 로그인 한다.(로그인 정보를 서버로 요청)
  2. 서버는 Request 가 들어오면 DB를 쿼리하여 사용자를 검증하고 유효할 경우 사용자의 ID 값을 부여하여 세션 장소에 저장한 후, 이와 연결되는 세션 ID를 생성하여 Response Header에 포함시켜 반환 한다.
  3. 사용자는 서버에서 해당 세션 ID를 받아 쿠키에 저장을 한 후, 제한된 End Point(인증이 필요한 요청)에 접근 할 때마다 쿠키를 Request Header에 포함되어 보낸다.
  4. 서버에서는 쿠키를 받아 세션 저장소에서 검증한 후 요청에 해당하는 데이터를 반환한다.

특징

  • 쿠키(서버에 저장된 세션에 접근하기 위한 세션 ID)가 HTTP 요청 중 노출되어도 쿠키 자체에 중요한 정보는 담겨져 있지 않다. 하지만 쿠키 자체를 훔쳐 세션에 접근하여 중요한 정보를 빼낼 수 있는데, 이를 막기 위한 세션에 유효시간을 넣거나 HTTPS를 사용해 요청을 훔쳐도 그 안의 정보를 보기 힘들게 한다.
  • 쿠키를 통해 세션에 접근하면 세션 ID로 사용자를 구분할 수 있으므로 일일히 사용자 정보를 확인할 필요가 없다.
  • 서버에서 세션을 저장하기 때문에 사용자 수가 많아지면 서버의 부담이 늘어난다.
  • 또한, 서버 확장성이 나빠진다. 서버 사양 업그레이드뿐만 아니라 늘어나는 트래픽을 감당하기 위해 여러 프로세스를 돌리거나, 여러 대의 서버 컴퓨터를 추가하는 것이 어려워진다.
  • CORS : 쿠키는 단일 도메인 및 서브 도메인에서만 작동하도록 설계되어 여러 도메인에서 관리하기 번거롭다.

 

토큰 기반 인증

세션 기반 인증과는 다르게 서버가 사인한 토큰을 이용하여 수행하는 방식입니다. 세션 기반 인증의 stateful 서버는 클라이언트로부터 요청이 있을 때마다 클라이언트의 상태를 유지합니다. 사용자가 로그인을 하여 인증을 요청하면 stateful 서버는 인증에 성공하였을 때의 결과(세션)을 메모리 또는 데이터베이스에 유지하기 때문에 서버에 부하가 발생할 수 있습니다. 하자민 토큰 기반 인증은 stateful 서버와 반대적 개념인 stateless 서버를 사용하며 상태정보를 유지 하지 않습니다.

서버가 전달받은 토큰을 검증만 하면 되기 때문에 서버의 부담을 줄이고 서비스의 확정성을 높일 수 있습니다.

인증흐름

  1. 사용자가 로그인 한다.(로그인 정보를 서버로 요청)
  2. 서버는 request가 들어오면 사용자를 검증하고 유효할 경우 정상적으로 발급된 토큰임을 증명하는 signature를 갖는 토큰을 클라이언트에 반환한다.
  3. 클라이언트는 토큰을 저장하고 서버 요청시 해당 토큰을 request header에 담아 서버에 전달한다.
  4. 서버는 토큰을 검증한 후, 요청에 응답한다.

특징

  • Stateful 서버는 클라이언트에게서 요청을 받을 때마다 클라이언트의 상태를 계속해서 유지하고 이 정보를 서비스 제공에 이용한다. 그 예로는 세션을 유지하는 웹서버가 있다. 로그인하면 세션에 로근인이 되었다고 저장을 해 두고 서비스를 제공할 때에 그 데이터를 사용한다. 세션은 서버의 메모리나 데이터베이스에 저장한다.
  • Stateless 서버는 상태를 유지하지 않는다. 서버는 클라이언트 측에서 들어오는 요청만으로 작업을 처리한다. 클라이언트와 서버의 연결고리라 없기 때문에 서버의 확장성이 높아진다.
  • 대표적인 예로 OAuth가 있다. 페이스북, 구글 같은 소셜 계정들을 이용하여 다른 웹 서비스에서도 로그인할 수 있다.
  • 세션 기반 인증을 사용하면 쿠키 매니저를 따로 관리해줘야 하지만 토큰을 사용하면 웹 요청 API 헤더에 넣어서 사용해주면 되기 때문에 더 이상 쿠키 매니저를 사용할 필요가 없어진다.
  • 사이트간 요청 위조 방지 : 사용자가 사이트를 벗어나도 이미 쿠키가 사용자 정보를 가지고 있기 때문에 공격자에게 노출될 수 있다. 공격자가 임의로 다른 URL로 유도하여 비밀번호를 바꾸거나 회원 탈퇴를 할 경우 쿠키가 있기 때문에 서버는 요청을 신뢰하고 작업을 수행하게 된다. 이러한 문제를 해결하기 위해 탈퇴 시 비밀 번호를 한번 더 요구하거나 토큰과 같은 Credential을 포함시킬 수 있다.
  • 토큰 기반 인증에서는 헤더 내에 토큰이 포함되어 사이트간 요청 위조를 방지 할 수 있게 된다.
  • CORS : 쿠키는 단일 도메인 및 서브 도메인에서만 작동하도록 설계되어 있기 때문에 여러 도메인에서 관리하기 어렵다. 하지만 토큰 기반 인증은 토큰만 유효하다면 어디서든 작동할 수 있다.
  • 여러 개의 서버에서 한 세션이 첫 번째 서버에 생성되었다고 가정했을 때, 새로운 요청이 발생하고 그 요청이 다른 서버에 전달되면 해당 서버에는 세션 정보가 없을 것이기 때문에 Unauthorized 응답을 받을 것이다. Stick 세션(같은 서버에 세션을 계속 연결시키는 방식)을 사용하여 해결할 수 있지만 토큰 기반 인증에서 요청은 모든 서버가 가로채기 때문에 자연스럽게 이러한 문제가 해결된다.

문제점

  • Stateless 한 토큰의 특성 때문에 토큰을 강제로 만료시킬 수 없는 문제가 있다. 토큰이 공격자에게 탈취되었다고 가정하면, 공격자는 토큰이 만료될 때까지 서버에 요청을 할 수 있다. 위와 같은 상황을 해결하기 위해서 토큰의 만료 주기를 너무 짧게 하면 수시로 로그인을 다시 하게 되어 사용자가 불편해지고, 그렇다고 사용자 편의를 위해 만료 주기를 길게 하면 토큰이 탈취당했을 때 피해가 커지게 된다.
  • 토큰 인증 방식의 단점을 보완하기 위해 보통은 토큰의 타입을 리프레쉬 토큰과 엑세스 토큰으로 나누어 사용하는 방식을 택한다. 이 방식은 서비스를 요청하는 데 사용하는 만료 주기가 짧은 액세스 토큰과 엑세스 토큰을 재발급받을 수 있는 보완이 철저하고 만료주기가 긴 리프레쉬 토큰을 사용한다. 엑세스 토큰은 탈취당해도 보안이 철저한 리프레쉬 토큰을 탈취하지 못하면 공격할 수 있는 시간은 얼마되지 않기 때문에 피해가 적어진다.

토큰 저장 위치

서버가 토큰을 발급해주면, 브라우저에 사용자/서버 간에 토큰이 전달되는 방식은 크게 두가지로 나뉩니다. 첫 번째는 로그인 성공시 서버가 응답 정보에 토큰을 넣어서 전달하도록 하고, 해당 값을 웹 스토리지에 넣고 다음부터 웹 요청을 할 때마다 http 헤더 값에 넣어서 요청하는 방법입니다. 이 방법은 구현하기 쉽고 하나의 도메인에 제한되지 않는다는 장점이 있지만 XSS 해킹 공격을 통하여 해커의 악성 스크립트에 노출이 되는 경우 매우 쉽게 토큰이 탈취될 수 있다. 그냥 local storage에 접근하면 바로 토큰에 접근할 수 있기 때문입니다.

이에 대한 대안으로 두 번째 방식은 토큰을 쿠키에 넣는 것입니다. 쿠키를 사용한다고 해서 세션을 관리하는 것은 아니고, 그저 쿠키를 정보 전송수단으로 사용할 뿐인데요, 이 과정에서 서버측은 응답을 하면서 크키를 설정해 줄때 httpOnly 값을 활성화를 해주면 네트워크 통신 사에서만 해당 쿠키가 붙게됩니다. 따라서 브라우저상에서는 자바스크립트로 토큰 값에 접근하는 것이 불가능해집니다. 이 방법의 단점은 쿠키가 한정된 도메인에서만 사용이 된다는 점인데, 이 문제는 토큰이 필요해질 때 현재 쿠키에 있는 토큰을 사용하여 새 토큰을 문자열로 받아올 수 있게 하는 API를 구현하여 해결하면 됩니다.

또 다른 단점은 XSS의 위험에서 완벽히 해방되는 대신 사이트간 위조의 공격 위험성이 생긴다는 점인데, 계정 정보를 탈취하는 것은 아니지만 스크립트를 통해 사이트의 외부에서 사이트의 API를 사용하는 것처럼 모방하는 것입니다. 또는 사이트 내부에서 스크립트가 실행되어 원하지 않는 작업이 수행되게 할 수 도 있습니다. 예를 들면 유저도 모르는 사이 탈퇴/덧글을 자동으로 작성/포스트를 자동으로 작성/회원정보 변경 이러한 사이트간 위조 공격은 http 요청 체크, 그리고 사이트간 위조 토큰의 사용을 통하여 방지할 수 있습니다.

JSON Web Token(JWT)

JSON Web Token은 인증 헤더 내에서 사용되는 토큰 포맷입니다. 토큰은 Base64로 인코딩한 String으로 이루어져 있습니다. 이 토큰은 두 개의 시스템끼리 안정한 방법으로 통신할 수 있도록 설계하는 것을 도와줍니다. JWT의 장점은 계정 서버와 API가 분리되어 있을 때, API 서버가 계정 서버에게 토큰의 유효성 여부를 물어보지 않고도 스스로 판단 할 수 있다는 점입니다.

Access Token은 단순하게 접근하는 Access Token 만이 아니라 권한, 인증에 대한 토큰을 말합니다. Refresh Token은 Access Token과 똑같은 형태의 JWT이고 Access Token의 탈취 문제를 해결하기 위해 발급되는 토근입니다. 처음에 로그인을 완료했을 때 Access Token과 동시에 발급되는 Refresh Token은 긴 유효기간을 가지면서 Access Token이 만료됐을 때 새로 발급해주는 열쇠가 됩니다.

JWT는 토큰 자체가 의미를 갖는 Claim 기반의 토큰으로 권한과 인증의 역할을 가질 수 있습니다. Claim은 사용자에 대한 프로퍼티나 속성을 의마하는데요, 즉 JWT는 JSON 객체에 요구사항을 작성하고, 어떠한 암호화 방식을 사용해서 문자열로 인코딩을 한 후 HTTP header에 추가함으로서 사용자 인증을 요청합니다. 서버에서는 이 토큰을 확인 뒤 디코딩하여 사용자를 인증하게 됩니다.

특징

  • 세션 쿠키는 별도의 저장소의 관리가 필요하나 JWT는 서버 입장에서 요청을 받았을 때 발급한 후 검증만 하면 되기 때문에 추가 저장소가 필요없다.
  • A 서버로 접속했다가 B 서버로 요청을 했다고 해도 문제 생길 것이 없다. 확장이 쉽다.
  • 토큰 기반으로 하는 다른 인증 시스템에 접근이 가능하다. 따라서 페이스북 로그인, 구글 로그인 등은 모두 토큰을 기반으로 인증을 한다. 이에 선택적으로 이름이나 이메일 등을 받을 수 있는 권한도 받을 수 있다.
  • 무결성 : HMAC 기법, JWT Token의 Secret 키를 하나라도 입력을 하게 되면 Signature 영역의 글자가 바로 바퀴는 것을 알 수 있다. 이처럼 JWTO Token은 변조가 되었을 때, 바로 알아차릴 수 가 있다.
  • 권한을 부여하기 위한 데이터가 JWT 안에 모두 담겨 있다. OAuth 처럼 인증 서버에서 토큰에 대한 정보를 찾을 필요가 없다. 정보가 담긴 데이터를 암호화해서 HTTP 헤더에 추가시킨다. 하지만 누군가가 토큰을 탈취한다면 그 토큰을 이용해서 권한을 수행 할 수가 있다. 그래서 토큰의 유효시간을 설정할 수 있으며 탈취될 가능성을 줄이기 위해서 유효시간을 짧게 해주는 것이 좋다.

단점

이미 발급된 JWT에 대해서는 돌이킬 수 없습니다. 세션/쿠키의 경우 악의적 이용시 세션을 지우면 되지만 JWT는 한번 발급되면 유효기간이 완료될 때까지 계속 사용이 가능해서 악의적인 사용자는 유효시간이 지나기 전까지 정보들을 훔쳐갈 수 있습니다. 이에 대한 해결책으로 기존의 Access Token의 유효시간을 짧게 하고 Refresh Token이라는 새로운 토큰을 발급합니다.

Payload 정보가 제한적입니다. Payload는 따로 암호화되지 않기 때문에 디코딩하면 누구나 정보를 확인할 수 있는데요, 세션/쿠키 방식에서는 유저의 정보가 전부 서버의 저장소에 안전하게 보관되어 있지만, JWT에서 유저의 중요한 정보들은 Payload에 넣을 수 없습니다. 그리고 세션/쿠키 방식에 비해 JWT의 길이는 길기 때문에 인증이 필요한 요청이 많아 질수록 서버의 자원낭비가 발생하게 됩니다.

인증 흐름

  1. 사용자가 로그인하다
  2. 서버는 요청을 확인하고 사용자를 검증한 후, 사용자의 고유한 ID 값을 부여하고 기타 정보와 함께 payload를 작성하여 암호화할 Secret Key를 통해 Asccess Token을 발급하여 클라이언트로 반환한다.
  3. 이후 사용자가 JWT가 요구되는 API를 요청할 때 클라이언트가 Authorization Header에 Access Token을 담아서 보낸다.
  4. 서버는 Access Token의 Signature를 secret key를 통해 복호화하여 검증한 후, payload를 디코딩하여 사용자 정보를 확인해 요청한 데이터를 반환한다.

Access token + Refresh token process

  1. 사용자가 로그인한다.
  2. 서버는 요청을 확인하고 사용자를 검증한 후, Access Token, Refresh Token을 발급한다.(일반적으로 회원 DB에 Refresh token을 저장한다)
  3. 사용자는 Refresh token은 안전한 저장소에 저장 후, Access token을 헤더에 실어 요청을 보낸다.
  4. Access token을 검증하여 이에 맞는 데이터를 보낸다.
  5. 시간이 지나 Access Token이 만료된 후 사용자가 요청을 보내면, 서버는 Access Token이 만료됨을 확인하고 권한없음 신호로 보낸다.
  6. 사용자는 Refresh token과 Access Token을 함께 서버로 보낸다.
  7. 서버는 받은 Access Token이 조작되지 않았는지 확인한 후, Refresh Token과 사용자의 DB에 저장되어 있던 Refresh Token을 비교한다. Token이 동일하고 유효기간이 지나지 않았다면 새로운 Access Token을 발급해준다.
  8. 사용자는 새로운 Access Token을 헤더에 실어 다시 API 요청을 진행한다.

지금까지 API 를 사용하기 위한 인증방식에 대해서 알아보았는데요, 어떤 방식이 가장 좋은 방식이고 모든 경우에 이 방식이 최고다하는 것은 없습니다. 다만, 자신이 만들고자 하는 서버와 제공하는 API의 성격에 따라서 인증방식을 세팅해보시면 좋을 것인데, Firebase의 Authentication 등 흔히 많이 쓰는 방식은 JWT 방식이 아닐까 싶습니다. 개발사는 자신들의 비즈니스 로직에 맞기고 전형적인 인증모듈의 경우는 이러한 인증서버를 별도로 확인하면 개발속도가 훨씬 높아테니까요. 물론 Token Manager를 직접만들어 이러한 토큰 관리를 직접 서버에서 포함해서 하게 하는 것도 보안 측면에서는 이점이 있으니, 전반적인 내용에 대해서 봐주시고, 다음에는 이러한 인증방식 중 하나를 결합해서 서버를 만들어 보는 시간을 갖도록 하겠습니다.

반응형
댓글