김동욱2
247
2017-08-11 01:32:32.0 작성 2017-08-11 01:46:54.0 수정됨
7
5367

REST 아키텍쳐와 JWT, OAuth, 그리고 SPA


일반적인 웹 서버 외에 범용 API 서버를 구축 할 때 쓰이는 REST 아키텍쳐에 대해 공부합니다. 또한 API 서버의 사용자 인증에 쓰이는 OAuth 프로토콜에 대해 알아봅니다. 이후 간단한 REST API 서버와 SPA를 작성해봅니다.
REST API

REST(Representational State Transfer)는 클라이언트-서버 구조의 서비스에서, (일반적으로 HTTP 프로토콜을 기반으로하는) 서버를 구현 할 수 있는 아키텍쳐의 한 종류입니다. REST 아키텍쳐의 핵심적인 특징으로는 범용성, 리소스(데이터) 중심의 API 명세, 상태 없음(stateless)라고 볼 수 있겠습니다.

REST API의 범용성

REST API의 범용성

먼저 범용성에 대해서 생각해보면, 일반적인 웹 서비스의 백엔드 서버는 단순히 HTML 문서만을 응답해주기 때문에 웹 브라우저 클라이언트 전용의 서버라고 생각 할 수 있습니다. 하지만 REST API는 웹 브라우저를 포함해서 HTTP 통신이 가능한 모든 클라이언트 플랫폼을 타겟으로 합니다. 
이런 범용성을 갖추기 위해서 REST API의 HTTP Response Body는 HTML 보다는 JSON, XML 등 여러 플랫폼에서 사용하기 적절한 단순한 텍스트 포맷을 사용합니다.

REST API의 자기서술성

REST API의 자기서술성

또한 REST API는 HTTP Request Header 자체로 메시지의 목적이 뚜렷히 드러나도록 설계됩니다. 일반적인 웹 페이지의 Request는 GET /board/index.html 처럼 요청 자체로 그 메세지가 서버에 미치는 작용에 대해서 추측하기 힘듭니다. 
RESTful한 설계라면 GET /articles와 같이 동사 + 명사의 결합으로 리소스에 대해 서술적인 방식으로 API의 각 기능(End-Point)을 설계하게 됩니다.
이 때 서버에서 유용하는 자원들에 대해서 일반적으로 CRUD(Create, Read, Update, Delete) 기능을 갖출 수 있도록 API를 설계합니다. 
다중 플랫폼을 타겟으로, 데이터 제어 및 조회를 제공하는 서버에 REST 아키텍쳐를 도입하면 적절하겠습니다.

URL/MethodCreateReadUpdateDelete
POSTGETPUTDELETE
/usersUser를 생성전체 User를 조회전체 User를 일괄 수정전체 User를 일괄 삭제
/users/10-10번 User를 조회10번 User를 수정10번 User를 삭제
/users/10/posts10번 User의 Post를 생성10번 User의 모든 Post를 조회10번 User의 모든 Post를 일괄 수정10번 User의 모든 Post를 일괄 삭제

REST API 요청 예시

위처럼 REST API의 각 엔드포인트는 자원의 명사형(보통 복수형으로)에 자원의 고유 번호(PK)를 결합하고, HTTP Method를 동사로 결합하여 API 엔드포인트 자체가 자기서술적인 성격을 띄고 있습니다. users/10/posts 처럼 종속적인 자원에 대한 엔드포인트를 설계 할 수도 있겠습니다.

Response Status Code의미 및 용례
200일반적인 성공, Reponse Body에 조회, 생성 또는 수정한 데이터를 첨부
304자원을 조회 할 시, 이전과 변동 사항 없음 (클라이언트가 캐싱을 제공하는 경우)
400잘못된 요청, Response Body에 오류의 구체적인 정보를 첨부 할 수 있음
401인증이 필요함
403(인증이 되었으나) 권한 없음
404자원이 존재하지 않음
500서버 오류

REST API 응답 예시

REST API의 응답은 HTTP의 Response Status Code를 활용해서 HTTP Response 헤더 자체가 그 결과를 서술 할 수 있도록 합니다. 일반적인 웹 서버는 Status Code를 잘 활용하지 않지만, REST에서는 Status Code를 적절히 활용하여, 클라이언트 프로그램들이 응답에 적절히 반응 할 수 있도록 해줍니다. 물론 Response Body에 데이터가 첨부 될 때는, HTML 보다는 JSON이나 XML과 같은 단순한 텍스트 포맷을 이용합니다.

REST API 서버 예시 (Express.js)
app.get('/users', (req,res)=>{
	res.json(db.users);
});

app.get('/users/:id', (req,res)=>{
	let user = db.users.find(user => user.id == req.params.id);
	if (user) {
		res.json(user);
	} else {
		res.status(404).end();
	}
});

app.post('/users', ...);

app.delete('/users/:id', ...);

...
API Key를 이용한 인증

REST (State Transfer)의 마지막 특성으로는 Stateless를 들 수 있습니다. 상태가 없다함은 하나의 요청이 그 자체로 고립되고 완전하여, 요청하는 유저의 상태(인증 등)를 세션 등으로 서버에서 관리하지 않으며, 요청 데이터 그 자체만을 해석해 응답한다는 의미입니다.

즉, 서버에서 클라이언트의 상태를 관리하지 않아야합니다. State Transfer는 요청/응답 간에 상태 관리가 필요한 경우에, 메세지 자체에 그 상태를 포함하여 통신한다는 의미로 볼 수 있겠습니다.

REST API의 Stateless

REST API의 Stateless

전통적인 웹에서는 클라이언트의 상태를 관리하기 위해서 쿠키-세션을 기반으로 클라이언트의 상태를 추적, 관리하는 것이 일반적입니다. 하지만 범용성, 나아가 확장성(Scalability)을 기조로 설계된 REST 아키텍쳐는 서버의 Stateless 구조를 강조합니다.

왜냐하면 세션을 기반으로 클라이언트를 추적하는 것은 서버 쪽에 지속적인 비용을 초래하기 때문에 대규모 서비스로의 확장에 걸림돌이 될 수 있기 때문입니다. 또한 범용성을 위해서 (웹 브라우저를 위한) Cookie 기반의 세션을 이용하지 않도록 유도한다고 볼 수도 있겠습니다.

자, 그렇다면 상태에 대한 유지 및 관리는 클라이언트 측에 맡긴다고 쳐도, 서버 측에서는 어떻게 클라이언트의 신원을 확인 할 수가 있을까요?

REST API KEY

REST API KEY

가장 기초적인 방법으로는, 서버측에 미리 추측하기 힘든 무작위 문자열(API KEY)을 생성해두고, 클라이언트가 API를 호출 할 때마다 HTTP 헤더의 특정 필드 또는 쿼리스트링을 통해 전달하는 방법이 있습니다. 이를 통해 서버측은 메세지에 포함된 KEY를 통해서 클라이언트의 신원과 권한을 확인 할 수 있습니다.

물론 이 방식에선 메세지가 감청되거나 API KEY가 노출되었을 때, 메시지 위조 등의 공격 받기 쉽습니다. 따라서 민감하거나 보안이 필요한 데이터를 다룰 때는 메세지 전문을 암호화(HTTPS 등으로) 할 필요가 있습니다.

API KEY 방식의 예시
function getRecentPhotos(callback) {
	var API_URL = "https://api.flickr.com/services/rest/?method=flickr.photos.getRecent&api_key=46a247f7ad0611a92fa3bf67a931c5db&format=json&nojsoncallback=1";
	// cf. RESTful End-point로는 url 구조가 적합하지 않습니다.

	$.get(API_URL).done(function(data){
		var rawPhotos = data.photos.photo;
		// ... 중략
		callback(photos);
	});
}
OAuth 프로토콜을 이용한 인증
OAuth

OAuth

위 API KEY 방식은 간단하고 합리적이지만, 서비스 제공자(Service Provider), 즉 API 제공자가 인터넷에서 서비스를 공개적으로 확장하기에는 충분하지 않습니다. 예를 들어 페이스북이 당사의 인지도 및 서비스 이용을 증대시키기 위해서 일부 API를 공개한다고 해도, 페이스북 친구 목록을 활용해 특정 기능을 제공하려는 서비스 소비자(Service Consumer)에게 아무런 제한 없이 API KEY만으로 고객의 개인정보 등에 대한 API에 대한 접근 권한을 허가 할 수는 없습니다.

이렇게 고객, 서비스 제공자, 서비스 소비자, 3자가 얽힌 관계에서 API를 제공하기 위해서 OAuth (Open Authentication)이라는 프로토콜이 자리잡았습니다.

Facebook OAuth

Facebook OAuth

  1. 서비스 소비자(Consumer)는 서비스 제공자(Provider)가 제공하는 고객(User)의 특정 정보에 대한 API를 이용하기 위해서 Provider에게 User의 위임장(Request Token)을 생성해주길 요청합니다.
  2. Provider가 Consumer에게 위임장을 생성해주면, Consumer는 다시 User에게 제공 받은 위임장을 확인해주길 요청 (Provider에 로그인하고, Consumer가 요구하는 권한을 인가하는 등)합니다.
  3. User가 위임장을 확인 및 수락하면, Provider는 Consumer에게 위임에 대한 확인서(Access Token)를 발급해 줍니다.
  4. 이후 Consumer는 그 확인서가 유효한 동안, 위임 받은 권한을 이용해 Provider의 API를 호출합니다.
OAuth 프로토콜

OAuth 프로토콜

또한 OAuth 프로토콜은 자사의 API를 공개하는 것 외에도, 같은 방식으로 자사의 인증 로직을 어플리케이션 서버에서 분리하여, 별도의 인증 서버를 구축하거나, 또 나아가서 SSO(Single Sign On)과 같은 통합 인증 서비스를 구축하는 데 이용되기도 합니다.

JWT (JSON Web Token)

마지막으로 위에서 다룬 API KEY 방식과 Oauth의 Access Token 의 구현방식에 대해서 생각해보겠습니다. 서버는 API 호출 요청에 대해서 API KEY나 Token(다를바 없이 암호화된 무작위 문자열)이 유효한지를 확인 할 필요가 있습니다. 이는 서버에서 클라이언트의 상태(토큰의 유효성)를 관리하게끔 하며, 또 API를 호출 할 때마다 그 토큰에 해당하는 유저의 상태를 열람(DB 등에서)하는 비용을 초래 합니다.

JWT 흐름도

JWT 흐름도

이 상황의 근본적인 이유는 토큰 자체가 무의미한 문자열로 구성되어있기 때문입니다. 여기서 무의미한 토큰을 의미(유저의 상태를 포함한)가 있는 토큰으로 구성한다면, API 서버 쪽의 비용을 절감하면서 Stateless한 아키텍쳐를 구성 할 수 있습니다. JWT (JSON Web Token)는 유저의 상태(고유 번호, 권한, 토큰 만료 일자 등을 포함)를 JSON 포맷으로 구성하고, 이 텍스트를 다시 특정 알고리즘(Base 64)에 따라 일련의 문자열로 인코딩한 토큰을 의미합니다.

JWT 구성

JWT 구성

JWT는 클라이언트가 본인의 상태를 서버에게 전달해주는 방식이기에 토큰 위변조 문제가 있습니다. 서버는 토큰의 위변조를 방지하기 위해서, 토큰의 뒷 부분에 토큰의 내용과 특정 암호(secret key)를 기반으로 Signature 문자열을 붙혀서 토큰을 생성하게 됩니다. 이를 통해서 서버는 클라이언트에게 상태 관리를 위임하면서도, 토큰의 위변조를 방지 할 수 있습니다.

SPA (Single Page Application)

고전적인 프로그램 아키텍쳐는 모놀리식 아키텍쳐(Monolithic Architecture), 즉 일체형 아키텍쳐를 따르는 경우가 많습니다. 이는 UI와 데이터 계층, 통신 계층이 모두 일체형으로 작성된 구조를 말합니다. 우리가 이전에 작성했던 웹 서버들이 대표적입니다.

일체형 아키텍쳐에서 나아가서, REST 아키텍쳐는 UI 계층을 분리하여 클라이언트 프로그램의 범용성을 높힙니다.

Monolithic vs Micro Service Architecture

Monolithic vs Micro Service Architecture

REST에서 더 나아가서, 대형 시스템의 개발에서는 하나의 응용프로그램을 계층별로 세분화하고 여러 프로그램으로 분리하여, 협업 및 배포의 효율성을 높히고, 확장성 있는 구조를 띄는 마이크로 서비스 아키텍쳐(Micro Service Architecture)를 지향하기도 합니다.

아키텍쳐에 대한 자세한 내용은 더 이상 다루지 않고, REST API 서버와 대응되는 웹 응용프로그램을 하나 작성해보도록 하겠습니다.

SPA 흐름도

SPA 흐름도

SPA(Single Page Application)는 REST API와 대응되는 JavaScript로 작성된 웹 프로그램을 말합니다. 고전적인 웹과는 아래의 차이점을 보입니다.

  • 페이지의 이동이 없음, 모든 로직은 최초의 html 문서에서 시작
  • URL이 페이지의 상태를 표현하지 않음, 이 때문에 앱의 상태에 따라 URL을 가상으로 변경하기도 함
  • 유저 이벤트 등에 따라서 DOM이 동적으로 생성, 삭제, 수정되는 일이 빈번
  • 데이터를 제어하기 위해서 REST API 서버에 ajax를 통해서 소통
SPA (연락처 앱)

SPA (연락처 앱)

SPA의 설계를 도와주는 다양한 JavaScript 프레임워크(Angular.js, Ember.js, Redux, Vue.js, 등)가 있습니다. 이런 프레임워크의 선택에 대해서는 추후 고민해보도록 하고 당장은 jQuery만을 이용해서 아래 스펙을 만족하는 연락처 SPA를 작성해보겠습니다.

  1. {name, phone, email}을 갖는 연락처 정보를 모델링
  2. 연락처 모델의 CRUD를 수행하는 REST API를 구현
  3. REST 서버와 Model은 다른 모듈(파일)로 분리
  4. REST API와 통신하는(인증은 생략) SPA을 하나의 html문서로 작성
  5. SPA에는 연락처 생성, 조회(검색창에 값이 바뀌는 대로 실시간 검색), 수정, 삭제 기능

데모 프로그램 및 소스코드를 확인 하실 수 있습니다. 백엔드는 인증 과정을 생략하고 Data 부분만 모델링하였고, 프론트는 코드량의 부담을 줄이기 위해서 모델링 과정을 생략하고 절차지향적으로 단순히 설계하였습니다. 다음 챕터에서 본격적으로 완성된 웹 서비스를 개발해보기 전에, 개별적으로 코딩 연습 및 웹에 대한 이해도를 높히기 위해서 본 프로젝트를 완수하는 것이 큰 도움이 되겠습니다.

19
18
  • 댓글 7

  • 동목경
    49
    2017-08-11 04:45:01.0

    잘봤습니다. 정말 정리를 잘하시네요

    0
  • ghkdwls30
    1k
    2017-08-11 08:55:32.0
    잘봤습니다. 자주 이런 글 올려주세요!
    0
  • 안드초보에용
    21
    2017-08-12 22:22:05.0

    최고입니다

    0
  • 삿신
    282
    2017-08-15 15:38:20.0

    좋은글 감사합니다.

    전부터 궁금했는데..OAuth 프로토콜을 이용한 인증에서 Access Token를 가로챈다면

    해당 Access Token의 유저에 대한 정보에 접근을 제3자가 할 수 있게 되는건가요?

    0
  • 김동욱2
    247
    2017-08-16 06:50:56.0 작성 2017-08-16 06:51:59.0 수정됨

    네 물론입니다. 때문에 다양한 보안 요소를 추가할 수 있겠습니다.

    예를 들어서 액세스 토큰의 만료 주기를 짧게 정해서 액세스 토큰을 주기적으로 갱신하도록 한다던가. nonce라 불리는 요청의 고유 값을 두어 동일한 요청을 반복 위조하는 것을 방지한다던가. 급작스럽게 특이한 위치에서 액세스 토큰을 이용한 요청이 오면 파기를 시킨다던가..


    물론 이 이전에 https 위에다 oauth를 구현해서 메세지 전문을 암호화 할 필요가 있습니다!

    0
  • 삿신
    282
    2017-08-16 10:40:42.0

    그렇군요.

    답변 감사드립니다!

    0
  • libedi
    245
    2017-08-17 11:14:55.0

    아주 이해하기 쉽게 잘 설명해주셨네요~

    좋은 글 감사합니다~

    0
  • 로그인을 하시면 댓글을 등록할 수 있습니다.