REST API, OAuth, SPA

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


REST API

REST API의 범용성REST API의 범용성

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

응답 포맷

먼저 범용성에 대해서 생각해보면, 지금까지 작성했던 웹 서비스의 백엔드 서버는 단순히 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의 각 엔드포인트는 자원의 명사형(보통 복수형으로)에 자원의 고유 번호(PK)를 결합하고, HTTP Method를 동사로 결합하여 API 엔드포인트 자체가 자기서술적인 성격을 띄고 있습니다. users/10/posts/1/comments/20 처럼 종속적인 자원에 대한 엔드포인트를 설계 할 수도 있겠습니다.

응답 코드의 활용

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

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

REST API 서버 예시

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', ...);

...

REST API의 인증

REST의 마지막 특성으로는 Stateless를 들 수 있습니다. 상태가 없다함은 하나의 요청이 그 자체로 고립되고 완전하여, 로그인 등과 같은 이전의 요청에 영향을 받지 않으며, 항상 같은 결과를 낸다는 의미입니다.

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

REST API의 StatelessREST API의 Stateless

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

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

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

API Key를 통한 인증

REST API KEYREST 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";

    $.get(API_URL).done(function(data){
        var rawPhotos = data.photos.photo;
        // ... 중략
        callback(photos);
    });
}

OAuth 프로토콜을 통한 인증

OAuthOAuth

위 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)

UI와 데이터 소스의 분리

고전적인 프로그램 아키텍쳐는 모놀리식 아키텍쳐(Monolithic Architecture), 즉 일체형 아키텍쳐를 따르는 경우가 많습니다. 이는 UI와 데이터 계층, 통신 계층이 모두 일체형으로 작성된 구조를 말합니다. 이전에 작성했던 웹 서버들이 대표적입니다. 일체형 아키텍쳐에서 나아가서, REST 아키텍쳐는 UI 계층을 분리하여 클라이언트 프로그램의 범용성을 높힐 수 있었습니다.

Monolithic vs Micro Service ArchitectureMonolithic vs Micro Service Architecture

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

SPA의 특징

SPA 흐름도SPA 흐름도

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

  • 최초 응답 이후 페이지의 이동이 없음, 모든 로직은 최초의 html 문서에 삽입된 스크립트에서 시작
  • URL이 페이지의 상태를 표현하지 않음, 이 때문에 앱의 상태에 따라 URL을 가상으로 변경하기도 함
  • 유저 이벤트 등에 따라서 DOM이 동적으로 생성, 삭제, 수정
  • 데이터를 제어하기 위해서 API 서버에 AJAX로 소통

SPA 예제 (연락처 앱)

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

SPA (연락처 앱)SPA (연락처 앱)

스펙

  • 백엔드
    • {name, phone, email}을 갖는 연락처 정보를 모델링
    • 연락처 모델의 CRUD를 수행하는 REST API를 구현
    • REST 서버와 Model은 다른 모듈로 분리
    • html, css, js 등 정적 파일을 응답 할 수 있음 (/ 요청에 대해서 SPA의 메인 html 파일로 응답)
  • 프론트엔드
    • REST API와 통신하는(인증은 생략) SPA을 하나의 html문서로 작성
    • SPA에는 연락처 생성, 조회(검색창에 값이 바뀌는 대로 실시간 검색), 수정, 삭제 기능

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

목차
4. 웹 백엔드
5. 데이터베이스
저자

김동욱

개발 경력 약 10년, 전문 분야는 웹 및 각종 응용 소프트웨어 플랫폼입니다. Codeflow를 운영하고 있습니다.

2018년 04월 10일 업데이트

지원되지 않는 웹 브라우저거나 예기치 않은 오류가 발생했습니다.