블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

REST API의 이해와 설계

아키텍쳐 /대용량 아키텍쳐 | 2016.04.05 22:33 | Posted by 조대협

REST API 이해와 설계


조대협 (http://bcho.tistory.com)


REST API에 대한 이해와 디자인 방법 그리고 보안방법에 대해서 설명합니다.




대용량 아키텍쳐와 성능 튜닝 책의 4장을 정리한 내용인데, 강의나 스터디등에 자유롭게 사용하셔도 됩니다.  (출처 명기 필요)




저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

 

REST API를 이용하여, 날씨를 조회하는 간단한 애플리케이션 만들기

 

조대협 (http://bcho.tistor


네트워크를 통한  REST API 호출 방법을 알아보기 위해서, 간단하게, 위도와 경도를 이용하여 온도를 조회해오는 간단한 애플리케이션을 만들어보자

이 애플리케이션은 경도와 위도를 EditText 뷰를 통해서 입력 받아서 GETWEATHER라는 버튼을 누르면 네트워크를 통하여 REST API를 호출 하여, 날씨 정보를 받아오고, 해당 위치의 온도(화씨) 출력해주는 애플리케이션이다.

 



 

날씨 API 는 http://www.openweathermap.org/ 에서 제공하는데, 사용법이 매우 간단하고, 별도의 API인증 절차가 필요하지 않기 때문에 쉽게 사용할 수 있다. 사용 방법은 다음과 같이 쿼리 스트링으로 위도와 경도를 넘겨주면 JSON  형태로 지정된 위치의 날씨 정보를 리턴해준다.

 

http://api.openweathermap.org/data/2.5/weather?lat=37&lon=127

 

아래는 리턴되는 JSON 샘플



이 리턴되는 값중에서 main.temp 에 해당하는 값을 얻어서 출력할 것이다.

 

날씨값을 저장하는 클래스 작성

 

package com.example.terry.simpleweather.client;

/**

 * Created by terry on 2015. 8. 27..

 */

public class Weather {
    int lat;
    int ion;
    int temprature;
    int cloudy;
    String city;

    public void setLat(int lat){ this.lat = lat;}
    public void setIon(int ion){ this.ion = ion;}
    public void setTemprature(int t){ this.temprature = t;}
    public void setCloudy(int cloudy){ this.cloudy = cloudy;}
    public void setCity(String city){ this.city = city;}

    public int getLat(){ return lat;}
    public int getIon() { return ion;}
    public int getTemprature() { return temprature;}
    public int getCloudy() { return cloudy; }
    public String getCity() { return city; }
}

 

다음으로 REST API를 호출하는 OpenWeatherAPIClient.java 코드를 다음과 같이 작성한다.

 

package com.example.terry.simpleweather.client;



import org.json.JSONException;

import org.json.JSONObject;


import java.io.BufferedInputStream;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.net.HttpURLConnection;

import java.net.MalformedURLException;

import java.net.URL;


/**

 * Created by terry on 2015. 8. 27..

 * 목표

 * 1. AsyncTask와 HTTPURLConnection을 이용한 간단한 HTTP 호출 만들기

 * 2. 리턴된 JSON을 파싱하는 방법을 통하여, JSON 객체 다루는 법 습득하기

 * 3. Phone Location (GPS) API 사용 방법 파악하기

 *

 * 참고 자료 : http://developer.android.com/training/basics/network-ops/connecting.html

 * */

public class OpenWeatherAPIClient {

    final static String openWeatherURL = "http://api.openweathermap.org/data/2.5/weather";

    public Weather getWeather(int lat,int lon){

        Weather w = new Weather();

        String urlString = openWeatherURL + "?lat="+lat+"&lon="+lon;


        try {

            // call API by using HTTPURLConnection

            URL url = new URL(urlString);

            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

//            urlConnection.setConnectTimeout(CONNECTION_TIMEOUT);

//            urlConnection.setReadTimeout(DATARETRIEVAL_TIMEOUT);


            InputStream in = new BufferedInputStream(urlConnection.getInputStream());

            JSONObject json = new JSONObject(getStringFromInputStream(in));


            // parse JSON

            w = parseJSON(json);

            w.setIon(lon);

            w.setLat(lat);


        }catch(MalformedURLException e){

            System.err.println("Malformed URL");

            e.printStackTrace();

            return null;


        }catch(JSONException e) {

            System.err.println("JSON parsing error");

            e.printStackTrace();

            return null;

        }catch(IOException e){

            System.err.println("URL Connection failed");

            e.printStackTrace();

            return null;

        }


        // set Weather Object


        return w;

    }


    private Weather parseJSON(JSONObject json) throws JSONException {

        Weather w = new Weather();

        w.setTemprature(json.getJSONObject("main").getInt("temp"));

        w.setCity(json.getString("name"));

        //w.setCloudy();


        return w;

    }


    private static String getStringFromInputStream(InputStream is) {


        BufferedReader br = null;

        StringBuilder sb = new StringBuilder();


        String line;

        try {


            br = new BufferedReader(new InputStreamReader(is));

            while ((line = br.readLine()) != null) {

                sb.append(line);

            }


        } catch (IOException e) {

            e.printStackTrace();

        } finally {

            if (br != null) {

                try {

                    br.close();

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

        }


        return sb.toString();


    }

}

 

 

코드를 하나씩 뜯어보면

안드로이드에서 HTTP 호출을 하기 위해서는 HttpURLConnection이라는 클래스를 사용한다. URL이라는 클래스에 API를 호출하고자 하는 url주소를 지정하여 생성한후, url.openConnection()을 이용해서, HTTP Connection을 열고 호출한다.

URL url = new URL(urlString);

HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

 

다음으로, 리턴되어 오는 문자열을 읽어서 JSON형태로 파싱을 해야 하는데,

InputStream in = new BufferedInputStream(urlConnection.getInputStream());

먼저 위와 같이 urlConnection으로 부터 getInputStream() 메서드를 통해서 InputStream을 리턴받고,

JSONObject json = new JSONObject(getStringFromInputStream(in));

getStringFromInput(InputStream xx)이라는 메서드를 이용하여 inputStream을 String으로 변환한후에 JSONObject로 변환한다. (여기서 getStringFromInput은 미리 정해진 메서드가 아니고 위의 소스코드 처럼 InputStream 을 String으로 변환하기 위해서 여기서 지정된 코드들이다.)

다음으로 해당 JSON을 파싱해서, main.temp 값을 읽은후 Weather class에 넣어서 리턴을 한다.

w = parseJSON(json);

w.setIon(lon);

w.setLat(lat);

에서 parseJSON이라는 메서드를 호출하는데, parseJSON은 다음과 같다.


private Weather parseJSON(JSONObject json) throws JSONException {

    Weather w = new Weather();

    w.setTemprature(json.getJSONObject("main").getInt("temp"));

    w.setCity(json.getString("name"));

    //w.setCloudy();


    return w;

}


위의 메서드에서는 json에서 먼저 getJSONObject(“main”)을 이용하여 “main” json 문서를 얻고, 그 다음 그 아래 있는 “temp”의 값을 읽어서 Weather 객체에 세팅한다.

 

여기까지 구현을 하면 REST API를 http를 이용하여 호출하고, 리턴으로 온 JSON 문자열을 파싱하여 Weather 객체에 세팅을해서 리턴하는 부분까지 완료가 되었다.

 

그다음 그러면 이 클래스의 메서드를 어떻게 호출하는가? 네트워크 통신은 IO작업으로 시간이 다소 걸리기 때문에, 안드로이드에서는 일반적으로 메서드를 호출하는 방식으로는 불가능하고, 반드시 비동기식으로 호출해야 하는데, 이를 위해서는 AsyncTask라는 클래스를 상속받아서 비동기 클래스를 구현한후, 그 안에서 호출한다.

다음은 OpenWeatherAPITask.java 클래스이다.

 

public class OpenWeatherAPITask extends AsyncTask<Integer, Void, Weather> {

    @Override

    public Weather doInBackground(Integer... params) {

        OpenWeatherAPIClient client = new OpenWeatherAPIClient();


        int lat = params[0];

        int lon = params[1];

        // API 호출

        Weather w = client.getWeather(lat,lon);


        //System.out.println("Weather : "+w.getTemprature());


        // 작업 후 리

        return w;

    }

}

 

위와 같이 AsyncTask를 상속받았으며, 호출은 doInBackground(..)메서드를 구현하여, 그 메서드 안에서 호출을 한다.

위의 코드에서 볼 수 있듯이, doInBackground에서 앞서 작성한 OpenWeatherAPIClient의 객체를 생성한후에, 인자로 넘어온 lat,lon값을 받아서, getWeahter를 통하여, 호출하였다.

 

이제 API를 호출할 준비가 모두 끝났다.

이제 UI를 만들어야 하는데, res/layout/activity_main.xml은 다음과 같다.

 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"

    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"

    android:paddingTop="@dimen/activity_vertical_margin"

    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">




    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:textAppearance="?android:attr/textAppearanceLarge"

        android:text="Temperature"

        android:id="@+id/tem"

        android:layout_below="@+id/tvLongtitude"

        android:layout_alignParentStart="true"

        android:layout_marginTop="46dp" />


    <Button

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="getWeather"

        android:id="@+id/getWeatherBtn"

        android:onClick="getWeather"

        android:layout_alignBottom="@+id/tem"

        android:layout_alignParentEnd="true" />


    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:textAppearance="?android:attr/textAppearanceMedium"

        android:text="latitude"

        android:id="@+id/tvLatitude"

        android:layout_marginTop="27dp"

        android:layout_alignParentTop="true"

        android:layout_alignParentStart="true" />


    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:textAppearance="?android:attr/textAppearanceMedium"

        android:text="longtitude"

        android:id="@+id/tvLongtitude"

        android:layout_marginRight="62dp"

        android:layout_marginTop="30dp"

        android:layout_below="@+id/lat"

        android:layout_alignParentStart="true" />


    <EditText

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:id="@+id/lat"

        android:width="100dp"

        android:layout_marginRight="62dp"

        android:layout_alignBottom="@+id/tvLatitude"

        android:layout_toEndOf="@+id/tvLatitude"

        android:layout_marginStart="36dp" />


    <EditText

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:width="100dp"

        android:id="@+id/lon"

        android:layout_marginRight="62dp"

        android:layout_alignBottom="@+id/tvLongtitude"

        android:layout_alignStart="@+id/lat" />


</RelativeLayout>

 

화면 디자인이 끝났으면, 이제 MainActivity에서 버튼을 누르면 API를 호출하도록 구현해보자.

 

package com.example.terry.simpleweather;


import android.support.v7.app.ActionBarActivity;

import android.os.Bundle;

import android.view.Menu;

import android.view.MenuItem;

import android.view.View;

import android.widget.EditText;

import android.widget.TextView;


import com.example.terry.simpleweather.client.OpenWeatherAPITask;

import com.example.terry.simpleweather.client.Weather;


import java.util.concurrent.ExecutionException;



public class MainActivity extends ActionBarActivity {


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

    }


    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

        // Inflate the menu; this adds items to the action bar if it is present.

        getMenuInflater().inflate(R.menu.menu_main, menu);

        return true;

    }


    @Override

    public boolean onOptionsItemSelected(MenuItem item) {

        // Handle action bar item clicks here. The action bar will

        // automatically handle clicks on the Home/Up button, so long

        // as you specify a parent activity in AndroidManifest.xml.

        int id = item.getItemId();


        //noinspection SimplifiableIfStatement

        if (id == R.id.action_settings) {

            return true;

        }


        return super.onOptionsItemSelected(item);

    }


    // MapView 참고 http://seuny.tistory.com/14

    public void getWeather(View view)

    {

        EditText tvLon = (EditText) findViewById(R.id.lon);

        String strLon = tvLon.getText().toString();

        int lon = Integer.parseInt(strLon);


        EditText tvLat = (EditText) findViewById(R.id.lat);

        String strLat = tvLat.getText().toString();

        int lat = Integer.parseInt(strLat);



        // 날씨를 읽어오는 API 호출

        OpenWeatherAPITask t= new OpenWeatherAPITask();

        try {

            Weather w = t.execute(lon,lat).get();


            System.out.println("Temp :"+w.getTemprature());


            TextView tem = (TextView)findViewById(R.id.tem);

            String temperature = String.valueOf(w.getTemprature());


            tem.setText(temperature);

            //w.getTemprature());



        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

    }

}

 

 

위에서 getWeather(View )부분에서 호출을하게 되는데, 먼저 앞에서 AsyncTask를 이용해서 만들었던 OpenWeatherAPITask 객체 t를 생성한 후에, t.execute로 호출을하면 된다.

실행 결과는 t.execute()에 대해서 .get()을 호출함으로써 얻을 수 있다.

 

이때 주의할점이 안드로이드는 네트워크 호출과 같은 리소스에 대한 접근을 통제하고 있는데, 이러한 통제를 풀려면, AnrdoidManifest.xml에 다음과 같이 INTERNET 과 NETWORK 접근을 허용하는 내용을 추가해줘야 한다.

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

 

다음은 위의 내용을 추가한 AndroidManifest.xml이다.

 

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.example.terry.simpleweather" >

    <uses-permission android:name="android.permission.INTERNET" />

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />


    <application

        android:allowBackup="true"

        android:icon="@mipmap/ic_launcher"

        android:label="@string/app_name"

        android:theme="@style/AppTheme" >

        <activity

            android:name=".MainActivity"

            android:label="@string/app_name" >

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />


                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>


    </application>


</manifest>

 

참고 : http://developer.android.com/training/basics/network-ops/connecting.html




9월 15일 추가 내용

  • t.execute().get 보다는  onPostExecute 를 통해서 리턴 값을 받는 것이 더 일반적인 패턴
  • AsyncTask를 사용하기 보다는 근래에는 Retrofit이라는 프레임웍을 사용하는게 더 효율적이며, 성능도 2배이상 빠름.
    관련 튜토리얼 : http://gun0912.tistory.com/30
    아래는 성능 비교 자료


 

저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

JWT(JSON Web Token)을 이용한 API 인증 - #1 개념 소개

조대협 (http://bcho.tistory.com)


REST API에 대한 보안과 인증이 화두가 되면서 많이 언급되는 것이 OAuth인데, 근래에 들어서 화두가 되고 있는 것이 JWT (JSON Web Token)이라는 표준이다.


Claim기반 토큰의 개념


OAuth에 의해서 발급되는 access_token은 random string으로 토큰 자체에는 특별한 정보를 가지고 있지 않는 일반적인 스트링 형태 이다. 아래는 페이스북에서 발급된 access_token의 형태로 일반적인 문자열 형태임을 확인할 수 있다.



<그림1.  Facebook의 Oauth에서 사용하는 일반적인 스트링 기반 토큰 예제>

 

 API나 서비스를 제공하는 서버 입장에서 그 access_token을 통해서 사용자에 연관된 권한(예를 들어 scope같은 것)을 식별한 뒤 권한을 허용해주는 구조이다.

즉 서비스를 제공하는 입장에서는 토큰을 가지고 그 토큰과 연관된 정보를 서버쪽에서 찾아야 한다. (사용자 ID나 권한등).

JWT는 Claim 기반이라는 방식을 사용하는데, Claim이라는 사용자에 대한 프로퍼티나 속성을 이야기 한다. 토큰자체가 정보를 가지고 있는 방식인데, JWT는 이 Claim을 JSON을 이용해서 정의한다.

다음은 Claim을 JSON으로 서술한 예이다.이 JSON 자체를 토큰으로 사용하는 것이 Claim 기반의 토큰 방식이다.

{

  "id":"terry"

  ,"role":["admin","user"]

  ,"company":"pepsi"

}

<코드 1. JSON으로 Claim을 기술한 토큰의 형태 >

자 그렇다면, 이러한 Claim 방식의 토큰은 무엇이 좋을까? 이 토큰을 이용해서 요청을 받는 서버나 서비스 입장에서는 이 서비스를 호출한 사용자에 대한 추가 정보는 이미 토큰안에 다 들어가 있기 때문에 다른 곳에서 가져올 필요가 없다는 것이다.

“사용자 관리” 라는 API 서비스가 있다고 가정하다.

 이 API는 “관리자(admin)” 권한을 가지고 있는 사용자만이 접근이 가능하며, “관리자” 권한을 가지고 있는 사용자는 그 관리자가 속해 있는 “회사(company)”의 사용자 정보만 관리할 수 있다. 라고 정의하자

이  시나리오에 대해서 일반적인 스트링 기반의 토큰과 JWT와 같은 Claim 기반의 토큰이 어떤 차이를 가질 수 있는 지 알아보도록 하자.


OAuth 토큰의 경우



<그림 2. String 토큰에 의한 API 권한 인증 흐름>

 

1.    API 클라이언트가 Authorization Server (토큰 발급서버)로 토큰을 요청한다.

이때, 토큰 발급을 요청하는 사용자의 계정과 비밀번호를 넘기고, 이와 함께 토큰의 권한(용도)을 요청한다. 여기서는 일반 사용자 권한(enduser)과 관리자 권한(admin)을 같이 요청하였다.

2.    토큰 생성 요청을 받은 Authorization Server는 사용자 계정을 확인한 후, 이 사용자에게 요청된 권한을 부여해도 되는지 계정 시스템등에 물어본 후, 사용자에게 해당 토큰을 발급이 가능하면 토큰을 발급하고, 토큰에 대한 정보를 내부(토큰 저장소)에 저장해놓는다.

3.    이렇게 생성된 토큰은 API 클라이언트로 저장된다.

4.    API 클라이언트는 API를 호출할때 이 토큰을 이용해서 Resource Server(API 서버)에 있는 API를 호출한다.

5.    이때 호출되는 API는 관리자 권한을 가지고 있어야 사용할 수 있기 때문에, Resource Server가 토큰 저장소에서 토큰에 관련된 사용자 계정, 권한 등의 정보를 가지고 온다. 이 토큰에 (관리자)admin 권한이 부여되어 있기 때문에, API 호출을 허용한다. 위에 정의한 시나리오에서는 그 사용자가 속한 “회사”의 사용자 정보만 조회할 수 있다. 라는 전제 조건을 가지고 있기 때문에, API 서버는 추가로 사용자 데이타 베이스에서 이 사용자가 속한 “회사” 정보를 찾아와야한다.

6.    API서버는 응답을 보낸다.


JWT와 같은 Claim 기반의 토큰 흐름을 보자

 



<그림 3. Claim 기반의 토큰을 이용한 API 권한 인증 흐름 >

 

1.    토큰을 생성 요청하는 방식은 동일하다.  마찬가지로 사용자를 인증한다음에, 토큰을 생성한다.

2.    다른 점은 생성된 토큰에 관련된 정보를 별도로 저장하지 않는다는 것이다. 토큰에 연관되는 사용자 정보나 권한등을 토큰 자체에 넣어서 저장한다.

3.    API를 호출하는 방식도 동일하다.

4.    Resource Server (API 서버)는 토큰 내에 들어 있는 사용자 정보를 가지고 권한 인가 처리를 하고 결과를 리턴한다.

결과적으로 차이점은 토큰을 생성하는 단계에서는 생성된 토큰을 별도로 서버에서 유지할 필요가 없으며

토큰을 사용하는 API 서버 입장에서는 API 요청을 검증하기 위해서 토큰을 가지고 사용자 정보를 별도로 계정 시스템 등에서 조회할 필요가 없다는 것이다.


참고 : 다른 Claim 기반 토큰은?


그러면 이러한 Claim 기반의 토큰이 JSON이 처음일까? 이미 이전에, XML기반의 SAML 2.0이 이와 비슷한 개념을 가지고 있다. Assertion이라는 개념으로 XML안에 이러한 Claim 정보를 넣어서 넘길 수 있었으나, 문제점은 전체적인 사이즈가 너무 크고, 구조가 복잡하여 쉽게 접근이 어려웠다. 더군다가 크기가 크기 때문에 API와 같이 자주 호출해야 하는 경우에는 HTTP 헤더등에 실어서 보내기가 어렵고, 파싱에 대한 오버해드가 크기 때문에 적절하지 않았다. (주로 다른 사이트나 시스템간의 SSO에서 상호 사용자 인증등을 위해서 사용된다. 무겁기는 하지만 표준화가 잘되어 있기 때문에 사용자 인증 시나리오에서는 현재에도 많이 사용된다.)

JWT는 이JSON Claim을 BASE64로 인코딩하여HTTP Header에 쉽게 넣을 수 있으며, JSON 기반이기 때문에 파싱과 사용이 쉽다.

결과적으로 Claim 기반의 토큰은 토큰 자체가 정보를 담음으로써, 토큰을 가지고 서비스나 API 접근을 제어할 때 별도의 작업이 서버에서 필요하지 않으며, 토큰 자체를 서버에서 관리할 필요가 없기 때문에 구현이 상대적으로 단순해진다.


JWT에 대한 소개


Claim 기반의 토큰에 대한 개념을 대략적으로 이해했다면, 그러면 실제로 JWT가 어떻게 구성되는지에 대해서 살펴보도록 하자.


Claim (메세지) 정의

JWT는 Claim을 JSON형태로 표현하는 것인데, JSON은 “\n”등 개행문자가 있기 때문에, REST API 호출시 HTTP Header등에 넣기가 매우 불편하다. 그래서, JWT에서는 이 Claim JSON 문자열을 BASE64 인코딩을 통해서 하나의 문자열로 변환한다.

{

  "id":"terry"

  ,"role":["admin","user"]

  ,"company":"pepsi"

}

<코드 2. JSON 기반의Claim 예제>

문자열을 BASE64 인코딩 한 결과

ew0KICAiaWQiOiJ0ZXJyeSINCiAgLCJyb2xlIjpbImFkbWluIiwidXNlciJdDQogICwiY29tcGFueSI6InBlcHNpIg0KfQ0K

<코드 3. JSON 기반의 Claim 코드 2를 BASE64 인코딩 한 결과>


변조 방지

위의 Claim 기반의 토큰을 봤으면, 첫번째 들 수 있는 의문이 토큰을 받은 다음에 누군가 토큰을 변조해서 사용한다면 어떻게 막느냐? 이다. 이렇게 메세지가 변조 되지 않았음을 증명하는 것을 무결성(Integrity)라고 하는데, 무결성을 보장하는 방법중 많이 사용되는 방법이 서명(Signature)이나 HMAC 사용하는 방식이다. 즉 원본 메세지에서 해쉬값을 추출한 후, 이를 비밀 키를 이용해서 복호화 시켜서 토큰의 뒤에 붙인다. 이게 HMAC방식인데,  누군가 이 메세지를 변조를 했다면,변조된 메세지에서 생성한 해쉬값과 토큰뒤에 붙어 있는 HMAC값이 다르기 때문에 메세지가 변조되었음을 알 수 있다. 다른 누군가가 메세지를 변조한후에, 새롭게 HMAC값을 만들어내려고 하더라도, HAMC은 앞의 비밀키를 이용해서 복호화 되었기 때문에, 이 비밀키를 알 수 없는 이상 HMAC을 만들어 낼 수 없다.


※ HMAC에 대한 자세한 설명은http://bcho.tistory.com/807 를 참고하기 바란다.

그래서 앞의 JSON 메세지에 대해서 SHA-256이라는 알고리즘을 이용해서 비밀키를 “secret” 이라고 하고, HMAC을 생성하면 결과는 다음과 같다.

i22mRxfSB5gt0rLbtrogxbKj5aZmpYh7lA82HO1Di0E

<코드 4. 코드 2의 JSON기반 Claim에 대해서, SHA1-256으로 생성한 HMAC>

서명 생성 방식

그러면 무결성 보장을 위해서 사용할 수 있는 알고리즘이 SHA1-256 HMAC 뿐일까? 보안요건에 따라서 SHA1-256,384,512. 그리고 공인 인증서 (Ceritification)을 이용한 RS256 등등 다양한 서명 방식을 지원한다. 그렇다면 JWT 토큰이 어떤 방식으로 서명이 되어 있는지는 어떻게 알 수 있을까?

그래서 JWT 토큰의 맨 앞부분에는 서명에 어떤 알고리즘을 사용했는지를 JSON형태로 정의한후, 이 JSON을 다시 BASE64 방식으로 인코딩한 문자열을 붙인다

{"alg":"HS256","typ":"JWT"}

<코드 5. JSON으로 서명 생성 방식은 SHA1-256으로 정의한 예>

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

<코드 6. 위의 코드 5 JSON 문자열을 BASE64 인코딩한 결과>

 

전체 메세지 포맷


위에서 설명한, 서명 방식, JSON 기반의 Claim,그리고 서명(Signature)까지 포함된 전체적인 JWT 토큰의 구조를 보면 다음과 같다.

{서명 방식을 정의한 JSON을 BASE64 인코딩}.{JSON Claim을 BASE64 인코딩}.{JSON Claim에 대한 서명}

이를 정리해서 그림으로 서술해 보면 다음과 같다.


<그림. JWT 토큰 구조>

그리고 결과로 나온, JWT 토큰은

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ew0KICAiaWQiOiJ0ZXJyeSINCiAgLCJyb2xlIjpbImFkbWluIiwidXNlciJdDQogICwiY29tcGFueSI6InBlcHNpIg0KfQ0K.i22mRxfSB5gt0rLbtrogxbKj5aZmpYh7lA82HO1Di0E

가 된다.


2편에서 다룰 내용

  • 유출 방지(암호화)

  • 상세 스펙

  • 구현예제


 

 

 

 

저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

Spring boot 살펴본 느낌

프로그래밍/Spring & Maven | 2014.11.25 09:05 | Posted by 조대협

Spring boot 살펴본 느낌


요즘 Spring Boot가 하도 유행이길래,잠깐 REST API Expose하는 시나리오와 DB 연동 시나리오등 몇가지 일반적으로 웹 개발하는데 사용되는 시나리오들을 살펴 보았다.

전체적인 느낌은 node.js나 ruby on rails와 같은 모던 웹 기술의 생산성을 따라잡기 위해서 무단히 노력했다는 점. 그래서 코드 생산성은 유사하거나 약간 열세? 정도 일거 같은 느낌인데, 왠지 개운하지 않은 뒷맛이 남는다고나 할까? 컨셉적으로 복잡하고 정교한 프로그래밍을 위한 프레임웍을 이용해서 단순성을 부여하다 보니, 빠지거나 아니면 디폴트 설정으로 인해서 위험한 부분이 있을 것 같은 느낌인 든다.


일단 코드 생성 부분을 보면

Maven 프로젝트를 이용해서 생성한 후에, 


JSON 객체와

package hello;


public class Greeting {

long id;

String content;

public Greeting(long id,String content){

this.id = id;

this.content = content;

}

public long getId(){

return id;

}

public String getContent(){

return content;

}

}



그리고 이 객체를 Get 하는 간단한 REST API를 구현해보면


package hello;


import java.util.concurrent.atomic.AtomicLong;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.RestController;


@RestController

public class GreetingController {


    private static final String template = "Hello, %s!";

    private final AtomicLong counter = new AtomicLong();


    @RequestMapping("/greeting")

    public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name) {

        return new Greeting(counter.incrementAndGet(),

                            String.format(template, name));

    }

}


이 정도 수준이다. 

처음 시작하는 수준에서, 문서만 보고 이렇게 구현하고 개념을 잡는데 까지 약 20여분이 걸렸으니, node.js나 python등의 여타 모던웹 언어와 크게 진입은 다르지 않다.


실행도, 복잡한 톰캣 설치나 배포 등이 필요 없이, jar 파일을 java -jar로 실행만 하면 spring boot가 tomcat이나 jetty를 임베드한 상태로 기동이 된다.


마치 node.js에서 코드 작성하고, node 소스.js를 기동하면 되는 것과 같은 편리함이다.

기존의 복잡한 XML 설정도 과감하게 생략할 수 있으며, 운영에서 필요한 모니터링과 같은 API등을 Actuator라는 이름으로 제공한다. (Hearbeat 체크, 모니터링 메트릭스 API등)


무엇보다 장점은, 기존의 자바와 스프링 프레임웍에 제공하는 프레임웍들을 그대로 사용할 수 있다는 것이다.

Spring Batch,Spring integratio이나 Spring XD등의 프레임웍을 그대로 사용이 가능하다. 그리고 이런 프레임웍을 이용해서 웹 기반의 애플리케이션 뿐만 아니라, Command Line에서 기동할 수 있는 일반적인 애플리케이션도 개발이 가능하다.(배치 데몬 등)


약간 신경이 쓰이는 부분은

데이타 베이스 엑세스 부분도 다른 일반 프레임웍 (Hibernate,MyBatis 등)을 당연히 사용할 수 있겠지만, Spring-JPA-Data로 사이트에서는 은근(?) 가이드를 하고 있다. Spring-JPA-Data가 은근 스프링에서 밀고 있는 느낌인데, SQL을 JPA에다가 한계층 더 올려서 Data 레벨까지 추상화 한 개념은 아무래도 아직까지는 개운하지는 않다. 요즘들어 많이 쓰이고는 있지만, Hibernate 조차도 복잡한 테이블 관계등을 가지는 경우 여전히 성능이나 메모리 문제를 야기 하는데, 잘 이해하고 쓰면 모르겠지만 Data 레벨을 추상화된 프레임웍이라니 아직은 걱정이 되는게 사실이다.


그리고, node.js = express, python = django, ruby = rails와 같이 모던웹 기술들은 널리 사용되는 웹 프레임웍과 템플릿 엔진이 있는데, Spring boot는 이 부분이 아직 부족해 보인다. (없다는 게 아니라, 그만큼 저변이 확대된 생산성이 높은 웹 프레임웍 엔진, 템플릿 엔진이 없다는 것이다.)

thymeleaf (http://www.thymeleaf.org/whoisusingthymeleaf.html) 템플릿 엔진이 많이 언급되고 있는 것은 같은데, 아직까지 메이져는 아닌거 같고, grails도 아직까지는 다른 스크립트 언어에 비해서 웹에서는 강세가 낮기 때문에 다소 지켜봐야할 부분이 아닌가도 싶다.

그렇지만, 이 부분은 모던 웹에서는 풀 자바스크립트 클라이언트 (angular.js)등을 사용하는 추세가 많아 짐에 따라 큰 장애가 되지는 않을 것으로 보인다.


오히려, API나 백엔드를 빠르게 만들 수 있는 생산성에 의해서 MSA (Micro Service Architecture)와 맞물려서, 백엔드 서버의 API 서버로 포지셔닝할 거 같고, 기존의 node나 ruby와 같은 타 스크립트에 기술을 아직 배우지 못한 자바 개발자들이 이쪽으로 넘어가지 않을까 싶다. 

그리고 JTA 연동을 통한 분산 트렌젝션이나 큐를 이용한 비동기 처리등은 기존의 스크립트 언어들이 가지지 못한 장점이기 때문에, 엔터프라이즈 수준의 프레임웍을 제공하는 것은 인정할만 장점이다.

시간내서 제대로 한번 정리해서 볼 필요는 있는듯.


저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'프로그래밍 > Spring & Maven' 카테고리의 다른 글

Spring boot 살펴본 느낌  (2) 2014.11.25
Spring Batch (DB2File)  (0) 2013.06.27
Spring Batch 개념 정리  (0) 2013.06.27
SpringBatch HelloWorld  (0) 2013.06.27
Eclipse에서 Spring Maven 개발환경 설정하기  (0) 2013.03.19
Maven 스터디  (0) 2013.03.19

Thrift에 대해서 잘 설명해놓은 글 http://thrift-tutorial.readthedocs.org/en/latest/thrift-stack.html

Thrift

RPC 지원

프로그래밍 언어 커버리지가 넓음

TLS 기반의 Secure 프로토콜 지원

Map,List 지원


PB

IDL 정의가 조금 더 직관적

"Serialize/Deserialize 전용 (Message 정의용) :그래서 다른 Transport 프로토콜이 필요함"-다른 Protocol을 통해 Tunneling 함 (HTTP,AMQP,심지어 Thrifit까지,Netty의 경우 확장 플러그인이 있음)

Map,List등을 지원하지 않음


저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

REST API의 이해와 설계


#3 API 보안


REST API 보안 

API 보안에 대해서는 백번,천번을 강조해도 과함이 없다. 근래에 대부분의 서비스 시스템들은 API를 기반으로 통신을 한다.

앱과 서버간의 통신 또는 자바스크립트 웹 클라이언트 와 서버간의 통신등 대부분의 통신이 이 API들을 이용해서 이루어지기 때문에, 한번 보안이 뚫려 버리면 개인 정보가 탈취되는 것 뿐만 아니라 많은 큰 문제를 야기할 수 있다.


REST API 보안 관점

API는 보안 포인트에 따라서 여러가지 보안 관점이 존재하는데, 크게 아래와 같이 5가지 정도로 볼 수 있다.


인증 (Authentication)

인증은 누가 서비스를 사용하는지를 확인하는 절차이다.

쉽게 생각하면 웹 사이트에 사용자 아이디와 비밀 번호를 넣어서, 사용자를 확인하는 과정이 인증이다.

API도 마찬가지로 API를 호출하는 대상 (단말이 되었건, 다른 서버가 되었건, 사용자가 되었건)을 확인하는 절차가 필요하고 이를 API 인증이라고 한다. 


인가 (Authorization)

인가는 해당 리소스에 대해서, 사용자가 그 리소스를 사용할 권한이 있는지 체크하는 권한 체크 과정이다.

예를 들어 /users라는 리소스가 있을 때, 일반 사용자 권한으로는 내 사용자 정보만 볼 수 있지만, 관리자 권한으로는 다른 사용자 정보를 볼 수 있는 것과 같은 권한의 차이를 의미한다.


네트워크 레벨 암호화

인증과 인가 과정이 끝나서 API를 호출하게 되면, 네트워크를 통해서 데이터가 왔다갔다 하는데, 해커등이 중간에 이 네트워크 통신을 낚아 채서(감청) 데이터를 볼 수 없게 할 필요가 있다.

이를 네트워크 프로토콜단에서 처리하는 것을 네트워크 레벨의 암호화라고 하는데, HTTP에서의 네트워크 레벨 암호화는 일반적으로 HTTPS 기반의 보안 프로토콜을 사용한다.


메시지 무결성 보장

메시지 무결성이란, 메시지가 중간에 해커와 같은 외부 요인에 의해서 변조가 되지 않게 방지하는 것을 이야기 한다.

무결성을 보장하기 위해서 많이 사용하는 방식은 메시지에 대한 Signature를 생성해서 메시지와 같이 보낸 후에 검증하는 방식으로, 예를 들어 메시지 문자열이 있을 때, 이 문자열에 대한 해쉬코드를 생성해서, 문자열과 함께 보낸 후, 수신쪽에서 받은 문자열과 이 받은 문자열로 생성한 해쉬 코드를 문자열과 함께 온 해쉬코드와 비교하는 방법이 있다. 만약에 문자열이 중간에 변조되었으면, 원래 문자열과 함께 전송된 해쉬코드와 맞지 않기 때문에 메시지가 중간에 변조가되었는지 확인할 수 있다.

메시지의 무결성의 경우, 앞에서 언급한 네트워크 레벨의 암호화를 완벽하게 사용한다면 외부적인 요인(해커)등에 의해서 메시지를 해석 당할 염려가 없기 때문에 사용할 필요가 없다.


메시지 본문 암호화

네트워크 레벨의 암호화를 사용할 수 없거나, 또는 네트워크 레벨의 암호화를 신뢰할 수 없는 상황의 경우 추가적으로 메시지 자체를 암호화 하는 방법을 사용한다. 이는 애플리케이션 단에서 구현하는데, 전체 메시지를 암호화 하는 방법과 특정 필드만 암호화 하는 방법 두가지로 접근할 수 있다.

전체 메시지를 암호화할 경우, 암호화에 소요되는 비용이 클 뿐더라 중간에 API Gateway등을 통해서 메시지를 열어보고 메시지 기반으로 라우팅 변환하는 작업등이 어렵기 때문에 일반적으로 전체를 암호화 하기 보다는 보안이 필요한 특정 필드만 암호화 하는 방법을 사용한다.


그러면 지금부터 각 보안 관점에 대해서 조금 더 구체적으로 살펴보도록 하자. 


인증 (Authentication)

API 에 대한 인증은 여러가지 방법이 있으며 각 방식에 따라 보안 수준과 구현 난이도가 다르기 때문에, 각 방식의 장단점을 잘 이해하여 서비스 수준에 맞는 적절한 API 인증 방식을 선택하도록 할 필요가 있다.


API Key 방식

가장 기초적인 방법은 API Key를 이용하는 방법이다. API Key란 특정 사용자만 알 수 있는 일종의 문자열이다. API를 사용하고자 할 때, 개발자는 API 제공사의 포탈 페이등 등에서 API Key를 발급 받고, API를 호출할 때 API Key를 메시지 안에 넣어 호출한다. 서버는 메시지 안에서 API Key를 읽어 이 API가 누가 호출한 API인지를 인증하는 흐름이다.

모든 클라이언트들이 같은 API Key를 공유하기 때문에 한번 API Key가 노출이 되면 전체 API가 뚫려 버리는 문제가 있기 때문에 높은 보안 인증을 요구 하는 경우에는 권장하지 않는다.


API Token 방식

다른 방식으로는 API Token을 발급하는 방식이 있는데, 사용자 ID,PASSWD등으로 사용자를 인증한 후에, 그 사용자가 API 호출에 사용할 기간이 유효한 API Token을 발급해서 API Token으로 사용자를 인증하는 방식이다.

매번 API 호출시 사용자 ID,PASSWD를 보내지 않고, API Token을 사용하는 이유는 사용자 PASSWD는 주기적으로 바뀔 수 있기 때문이고, 매번 네트워크를 통해서 사용자 ID와 PASSWD를 보내는 것은 보안적으로 사용자 계정 정보를 탈취 당할 가능성이 높기 때문에 API Token을 별도로 발급해서 사용하는 것이다.

API Token을 탈취 당하면 API를 호출할 수 는 있지만, 반대로 사용자 ID와 PASSWD는 탈취 당하지 않는다. 사용자PASSWD를 탈취당하면 일반적으로 사용자들은 다른 서비스에도 같은 PASSWD를 사용하는 경우가 많기 때문에 연쇄적으로 다른 서비스에 대해서도 공격을 당할 수 있는 가능성이 높아지기 때문이다. 예를 들어서 매번 호출시마다 사용자 ID,PASSWD를 보내서 페이스북의 계정과 비밀번호를 탈취 당한 경우, 해커가 이 계정과 비밀 번호를 이용해서 GMail이나 트위터와 같은 다른 서비스까지 해킹 할 수 있기 때문에, 이러한 가능성을 최소화하기 위함이다.

 


흐름을 설명하면 위의 그림과 같다.

1. API Client가 사용자 ID,PASSWD를 보내서 API호출을 위한 API Token을 요청한다.

2. API 인증 서버는 사용자 ID,PASSWD를 가지고, 사용자 정보를 바탕으로 사용자를 인증한다.

3. 인증된 사용자에 대해서 API Token을 발급한다. (유효 기간을 가지고 있다.)

4. API Client는 이 API Token으로 API를 호출한다. API Server는 API Token이 유효한지를 API Token 관리 서버에 문의하고, API Token이 유효하면 API 호출을 받아 들인다.

이 인증 방식에는 여러가지 다양한 변종이 존재하는데

먼저 1단계의 사용자 인증 단계에서는 보안 수준에 따라서 여러가지 방식을 사용할 수 있다.


HTTP Basic Auth 

※ 상세 : http://en.wikipedia.org/wiki/Basic_access_authentication

가장 기본적이고 단순한 형태의 인증 방식으로 사용자 ID와 PASSWD를 HTTP Header에 Base64 인코딩 형태로 넣어서 인증을 요청한다.

예를 들어 사용자 ID가 terry이고 PASSWD가 hello world일 때, 다음과 같이 HTTP 헤더에 “terry:hello world”라는 문자열을 Base64 인코딩을해서 “Authorization”이라는 이름의 헤더로 서버에 전송하여 인증을 요청한다.

Authorization: Basic VGVycnk6aGVsbG8gd29ybGQ=

중간에 패킷을 가로채서 이 헤더를 Base64로 디코딩하면 사용자 ID와 PASSWD가 그대로 노출되기 때문에 반드시 HTTPS 프로토콜을 사용해야 한다.


Digest access Authentication

상세 : http://en.wikipedia.org/wiki/Digest_access_authentication

HTTP Basic Auth가 Base64 형태로 PASSWD를 실어서 보내는 단점을 보강하여 나온 인증 프로토콜이 Digest access Authentication 이라는 방법으로, 기본 원리는 클라이언트가 인증을 요청할 때, 클라이언트가 서버로부터 nonce 라는 일종의 난수값을 받은 후에, (서버와 클라이언트는 이 난수 값을 서로 알고 있음), 사용자 ID와 PASSWD를 이 난수값을 이용해서 HASH화하여 서버로 전송하는 방식이다.

이 경우에는 직접 ID와 PASSWD가 평문 형태로 날아가지 않기 때문에, 해커가 중간에 PASSWD를 탈취할 수 없고, 설령 HASH 알고리즘을 알고 있다고 하더라도, HASH된 값에서 반대로 PASSWD를 추출하기가 어렵기 때문에, Basic Auth 방식에 비해서 향상된 보안을 제공한다. 전체적인 흐름을 보자


 

1. 클라이언트가 서버에 특정리소스 /car/index.html 을 요청한다.


2. 서버는 해당 세션에 대한 nonce값을 생성하여 저장한 후에, 클라이언트에 리턴한다. 이때 realm을 같이 리턴하는데, realm은 인증의 범위로, 예를 들어 하나의 웹 서버에 car.war, market.war가 각각 http://myweb/car , http://myweb/market 이라는 URL로 배포가 되었다고 하면, 이 웹사이트는 각각 애플리케이션 car.war와 market.war에 대해서 서로 다른 인증realm을 갖는다. 

※ 해당 session에 대해서 nonce 값을 유지 저장해야 하기 때문에, 서버 쪽에서는 상태 유지에 대한 부담이 생긴다. HTTP Session을 사용하거나 또는 서버간에 공유 메모리(memcached나 redis등)을 넣어서 서버간에 상태 정보를 유지할 수 있는 설계가 필요하다.


3. 클라이언트는 앞에서 서버로부터 받은 realm과 nonce값으로 Hash 값을 생성하는데, 

HA1 = MD5(사용자이름:realm:비밀번호)

HA2 = MD5(HTTP method:HTTP URL)

response hash = MD5(HA1:nonce:HA2)

를 통해서 response hash 값을 생성한다.

예를 들어서 /car/index.html 페이지를 접근하려고 했다고 하자, 서버에서 nonce값을 dcd98b7102dd2f0e8b11d0f600bfb0c093를 리턴하였고, realm은 car_realm@myweb.com 이라고 하자. 그리고 사용자 이름이 terry, 비밀 번호가 hello world하면

HA1 = MD5(terry:car_realm@myweb.com:hello world)로 7f052c45acf53fa508741fcf68b5c860 값이 생성되고

HA2 = MD5(GET:/car/index.html) 으로 0c9f8cf299f5fc5c38d5a68198f27247 값이 생성된다.

Response Hash는MD5(7f052c45acf53fa508741fcf68b5c860: dcd98b7102dd2f0e8b11d0f600bfb0c093:0c9f8cf299f5fc5c38d5a68198f27247) 로 결과는 95b0497f435dcc9019c335253791762f 된다.

클라이언트는 사용자 이름인 “terry”와 앞서 받은 nonce값인 dcd98b7102dd2f0e8b11d0f600bfb0c093와 계산된 hash값인 95b0497f435dcc9019c335253791762f 값을 서버에게 전송한다.


4. 서버는 먼저 3에서 전달된 nonce값이 이 세션을 위해서 서버에 저장된 nonce 값과 같은지 비교를 한후, 전달된 사용자 이름인 terry와nonce값 그리고 서버에 저장된 사용자 비밀 번호를 이용해서 3번과 같은 방식으로 response hash 값을 계산하여 클라이언트에서 전달된 hash값과 같은지 비교를 하고 같으면 해당 리소스를 (/car/index.html 파일)을 리턴한다. 


간단한 기본 메커니즘만 설명한것이며, 사실 digest access authentication은 qop (quality of protection)이라는 레벨에 따라서 여러가지 변종(추가적인 보안)을 지원한다. 언뜻 보면 복잡해서 보안 레벨이 높아보이지만 사실 Hash 알고리즘으로 MD5를 사용하는데, 이 MD5는 보안 레벨이 낮기 때문에 미정부 보안 인증 규격인 FIPS인증 (http://csrc.nist.gov/publications/fips/fips140-2/fips1402annexa.pdf) 에서 인증하고 있지 않다. FIPS 인증에서는 최소한 SHA-1,SHA1-244,SHA1-256 이상의 해쉬 알고리즘을 사용하도록 권장하고 있다.

MD5 해쉬의 경우에는 특히나 Dictionary Attack에 취약한데, Dictionary Attack이란, Hash된 값과 원래 값을 Dictionary (사전) 데이터 베이스로 유지해놓고, Hash 값으로 원본 메시지를 검색하는 방식인데, 실제로 http://en.wikipedia.org/wiki/Digest_access_authentication 설명에서 예제로 든

  • HA1 = MD5( "Mufasa:testrealm@host.com:Circle Of Life" )  = 939e7578ed9e3c518a452acee763bce9

의 MD5 해쉬 값인 939e7578ed9e3c518a452acee763bce9 값을 가지고, MD5 Dictionary 사이트인 http://md5.gromweb.com/?md5=939e7578ed9e3c518a452acee763bce9 에서 검색해보면, Hash 값으로 원본 메시지인 Mufasa:testrealm@host.com:Circle Of Life 값이 Decrypt 되는 것을 확인할 수 있다.

 


그래서 반드시 추가적인 보안 (HTTPS) 로직등을 겸비해서 사용하기를 바라며, 더 높은 보안 레벨이 필요한 경우 다른 인증 메커니즘을 사용하는 것이 좋다. 

FIPS 인증 수준의 보안 인증 프로토콜로는 SHA-1 알고리즘을 사용하는 SRP6a 등이 있다. 높은 수준의 보안이 필요할 경우에는 아래 링크를 참고하기 바란다. http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol


클라이언트 인증 추가

추가적인 보안 강화를 위해서 사용자 인증 뿐만 아니라, 클라이언트 인증 방식을 추가할 수 있다. 페이스북의 경우 API Token을 발급 받기 위해서, 사용자 ID,PASSWD 뿐만 아니라 client Id와 Client Secret이라는 것을 같이 입력 받도록 하는데,

Client Id는 특정 앱에 대한 등록 Id이고, Client Secret은 특정 앱에 대한 비밀 번호로 페이스북 개바자 포털에서 앱을 등록하면 앱 별로 발급 되는 일종의 비밀 번호이다.

 


그림. 페이스북 개발자 포탈에서 등록된 client Id(appId)와 client secret(App Secret)을 확인하는 화면

API Token을 발급 받을 때, Client Id와 Client Secret 을 이용하여, 클라이언트 앱을 인증하고 사용자 ID와 PASSWD를 추가로 받아서 사용자를 인증하여 API access Token을 발급한다.


제3자 인증 방식

3자 인증 방식은 페이스북이나 트위터와 같은 API 서비스 제공자들이 파트너 애플리케이션에 많이 적용하는 방법으로 만약 내가 My Server Application라는 서비를 Facebook 계정을 이용하여 인증을 하는 경우이다.

이때 중요한 점은 서비스 My Server Application에 대해서 해당 사용자가 페이스북 사용자임을 인증을 해주지만, 서비스 My Server Application는 사용자의 비밀번호를 받지 않고, 페이스북이 사용자를 인증하고 서비스 My Server Application에게 알려주는 방식이다. 즉 파트너 서비스에 페이스북 사용자의 비밀번호가 노출되지 않는 방식이다.

전체적인 흐름을 보면 다음과 같다.

 


1. 먼저 페이스북의 Developer Portal에 접속을 하여, 페이스북 인증을 사용하고자 하는 애플리케이션 정보를 등록한다. (서비스 명, 서비스 URL,그리고 인증이 성공했을 때 인증 성공 정보를 받을 CallBack URL)

2. 페이스북 Developer Portal은 등록된 정보를 기준으로 해당 애플리케이션에 대한 client_id와 client_secret을 발급한다. 이 값은 앞에서 설명한 클라이언트 인증에 사용된다.

3. 다음으로, 개발하고자 하는 애플리케이션에, 이 client_id와 client_secret등을 넣고, 페이스북 인증 페이지 정보등을 넣어서 애플리케이션을 개발한다.

애플리케이션이 개발되서 실행이 되면, 아래와 같은 흐름에 따라서 사용자 인증을 수행하게 된다.

 


1. 웹브라우져에서 사용자가 My Server Application 서비스를 접근하려고 요청한다.

2. My Server Application은 사용자가 인증이되어 있지 않기 때문에, 페이스북 로그인 페이지 URL을 HTTP Redirection으로 URL을 브라우져에게 보낸다. 이때 이 URL에 페이스북에 이 로그인 요청이 My Server Application에 대한 사용자 인증 요청임을 알려주기 위해서, client_id등의 추가 정보와 함께, 페이스북의 정보 접근 권한 (사용자 정보, 그룹 정보등)을 scope라는 필드를 통해서 요청한다.

3. 브라우져는 페이스북 로그인 페이지로 이동하여, 2단계에서 받은 추가적인 정보와 함께 로그인을 요청한다.

4. 페이스북은 사용자에게 로그인 창을 보낸다.

5. 사용자는 로그인창에 ID/PASSWD를 입력한다.

6. 페이스북은 사용자를 인증하고, 인증 관련 정보과 함께 브라우져로 전달하면서, My Server Application의 로그인 완료 페이지로 Redirection을 요청한다.

7. My Server Application을 6에서 온 인증 관련 정보를 받는다.

8. My Server Application은 이 정보를 가지고, 페이스북에, 이 사용자가 제대로 인증을 받은 사용자인지를 문의한다.

9. 페이스북은 해당 정보를 보고 사용자가 제대로 인증된 사용자임을 확인해주고, API Access Token을 발급한다.

10.  My Server Application은 9에서 받은 API Access Token으로 페이스북 API 서비스에 접근한다.


앞에서 설명했듯이, 이러한 방식은 자사가 아닌 파트너 서비스에게 자사 서비스 사용자의 인증을 거쳐서 API의 접근 권한을 전달하는 방식이다.

이러한 인증 방식의 대표적인 구현체는 OAuth 2.0으로, 이와 같은 제3자 인증뿐만 아니라, 직접 자사의 애플리케이션을 인증하기 위해서, 클라이언트로부터 직접 ID/PASSWD를 입력 받는 등.

클라이언트 타입(웹,서버,모바일 애플리케이션)에 대한 다양한 시나리오를 제공한다.

※ OAuth 2.0에 대한 자세한 설명은 PACKT 출판사의 OAuth 2.0 Identity and Access Management Patterns (by Martin Spasovski) 책을 참고하기를 추천한다.

이러한 3자 인증 방식은 일반적인 서비스에서는 필요하지 않지만, 자사의 API를 파트너등 외부 시스템에 제공하면서 사용자의 ID/PASSWD를 보호하는데는 필요한 서비스이기 때문에, API 를 외부에 적용하는 경우에는 고려를 해야 한다.


IP White List을 이용한 터널링

만약에 API를 호출하는 클라이언트의 API가 일정하다면 사용할 수 있는 손쉬운 방법인데, 서버간의 통신이나 타사 서버와 자사 서버간의 통신 같은 경우에, API 서버는 특정 API URL에 대해서 들어오는 IP 주소를 White List로 유지하는 방법이 있다.

API 서버 앞단에, HAProxy나 Apache와 같은 웹서버를 배치하여서 특정 URL로 들어올 수 있는 IP List를 제한 하거나, 아니면 전체 API가 특정 서버와의 통신에만 사용된다면 아예, 하드웨어 방화벽 자체에 들어올 수 있는 IP List를 제한할 수 있다.

설정만으로 가능한 방법이기 때문에, 서버간의 통신이 있는 경우에는 적용하는 것을 권장한다.


Bi-diretional Certification (Mutual SSL)

가장 높은 수준의 인증 방식을 제공할 수 있는 개념으로, 보통 HTTPS 통신을 사용할 때 서버에 공인 인증서를 놓고 단방향으로 SSL을 제공한다.

반면 Bi-Directional Certification (양방향 인증서 방식) 방식은 클라이언트에도 인증서를 놓고 양방향으로 SSL을 제공하면서, API 호출에 대한 인증을 클라이언트의 인증서를 이용 하는 방식이다. 

구현 방법이 가장 복잡한 방식이기는 하지만, 공인 기관에서 발행된 인증서를 사용한다면 API를 호출하는 쪽의 신원을 확실하게 할 수 있고, 메시지까지 암호화되기 때문에, 가장 높은 수준의 인증을 제공한다. 이런 인증 방식은 일반 서비스에서는 사용되지 않으며, 높은 인증 수준을 제공하는 몇몇 서비스나 특정 서버 간 통신에 사용하는 것이 좋다.


권한 인가 (Authorization)

인증이 끝나면 다음 단계는 권한에 대한 인증, 즉 인가 (Authorization) 과정이 필요하다.

사용자가 인증을 받고 로그인을 했다해더라도 해당 API를 호출 할 수 있는 권한이 있는 가를 체크해야 한다.

예를 들어 “일반 사용자 A가 로그인 했을 때, 다른 사용자를 삭제하는 것은 사용자 A가 관리자 권한을 가지고 있고, 이 요청이 웹 관리 콘솔을 통해서 들어온 경우에만 허용한다.” 와 같은 경우이다. 사용자가 인증(Authentication)을 통해서 시스템 내의 사용자 임을 확인 받았지만, API 호출을 하기 위해서 적절한 권한이 있는지를 검증해야 한다.


API 인가 방식

권한 인가(Authorization)방식에는 여러가지 방식이 있는데, 대표적인 방식 몇가지만 보면

가장 일반적인 권한 인증 방식은 사용자의 역할(ROLE)을 기반으로 하는 RBAC (Role Based Access Control)이라는 방식이 있다.

이 방식은 정해진 ROLE에 권한을 연결해놓고, 이 ROLE을 가지고 있는 사용자게 해당 권한을 부여하는 것이다. 

예를 들어

  • 일반 관리자 - 사용자 관리, 게시물 관리, 회원 가입 승인
  • 마스터 관리자 - 까페 게시판 게시판 관리, 메뉴 관리, 사용자 관리, 게시물 관리, 회원 가입 승인

와 같은 권한을 만든후, 

Terry에 "마스터 관리자"라는 ROLE을 부여하면, 사용자 Terry는 "까페 게시판 게시판 관리, 메뉴 관리, 사용자 관리, 게시물 관리, 회원 가입 승인" 등의 권한을 가지게 된다.

이렇게 권한 부여의 대상이 되는 사용자나 그룹을 Object라고 하고, 각 개별 권한을 Permission이라고 정의하며, 사용자의 역할을 Role이라고 정의한다. RBAC는 이 Role에 권한을 맵핑 한 다음 Object에 이 Role을 부여 하는 방식으로 많은 권한 인가는 사용자 역할을 기반으로 하기 때문에, 사용하기가 용이하다.

 


다른 권한 인증 모델로는 ACL (Access Control List)라는 방식이 있다.

RBAC 방식이 권한을 ROLE이라는 중간 매개체를 통해서 사용자에게 부여하는데 반해서, ACL 방식은 사용자(또는 그룹과 같은 권한의 부여 대상) 에게 직접 권한을 부여하는 방식이다.

사용자 Terry 에 직접 "까페 게시판 게시판 관리, 메뉴 관리, 사용자 관리, 게시물 관리, 회원 가입 승인" 권한을 부여 하는 방식이 ACL의 대표적인 예이다.



 

이러한 API 권한 인가 체크는 인증 (Authentication)이 끝나 후에, 인가에 사용된 api accesstoken을 이용하여 사용자 정보를 조회하고, 사용자 정보에 연관된 권한 정보 (Permission이나 Role정보)를 받아서 이 권한 정보를 기반으로 API 사용 권한을 인가하는 방법을 사용한다.  사용자 정보 조회 "HTTP GET /users/{id}"라는 API 가 있다고 가정하자.

이 API의 권한은 일반 사용자의 경우 자신의 id에 대해서만 사용자 정보 조회가 가능하고, (자신의 정보만), 만약에 관리자의 경우에는 다른 사용자의 id도 조회가 가능하도록 차등하여 권한을 부여할 수 있다.

이러한 권한 검증은 API access token으로 사용자를 찾은 후, 사용자에게 assign 된 ROLE이나 Access Control을 받아서 API 인증을 처리할 수 있다.


API 권한 인가 처리 위치

API에 대한 권한 인가 처리는 여러가지 계층에서 처리할 수 있다.

권한 인가는 API를 호출 하는 쪽인 클라이언트, API를 실행하는 API 서버쪽, 그리고 API 에 대한 중간 길목 역할을 하는 gateway 3군데서 처리할 수 있으며 근래에는 API 서버쪽에서 처리하는 것이 가장 일반적이다.


클라이언트에 의한 API 권한 인가 처리

API를 호출 하는 클라이언트 쪽에서 사용자의 권한에 따라서 API를 호출하는 방식인데, 이 방식의 경우 클라이언트가 신뢰할 수 있는 경우에만 사용할 수 있다.

이 방식은 기존에, 웹 UX 로직이 서버에 배치되어 있는 형태 (Struts나 Spring MVC와 같은 웹 레이어가 있는 경우)에 주로 사용했다.

위의 사용자 API를 예를 들어보면 웹 애플리케이션에서, 사용자 로그인 정보(세션 정보와 같은)를 보고 사용자 권한을 조회한 후에, API를 호출 하는 방식이다.

사용자 세션에, 사용자 ID와 ROLE을 본 후에, ROLE이 일반 사용자일 경우, 세션내의 사용자 ID와 조회하고자 하는 사용자 ID가 일치하는 경우에만 API를 호출 하는 방식이다.

이러한 구조를 사용할 경우 모바일 디바이스 등에 제공하는 API는 사용자 ROLE을 갖는 API와 같이 별도의 권한 인가가 필요 없는 API를 호출 하는 구조를 갖는다.

이 구조를 그림으로 표현해보면 다음과 같다.

Mobile Client는 일반 사용자만 사용한다고 가정하고, 웹 애플리케이션은 일반 사용자와 관리자 모두 사용한다고 했을 때, 일반 사용자의 Mobile Client를 위한 API Server를 별도로 배치하고, 사용자 인증(Authentication)만 되면 모든 API 호출을 허용하도록 한다. Mobile Client에 대한 API는 권한 인증에 대한 개념이 없기 때문에, 인증 처리만 하면 되고, 웹 애플리케이션의 경우에는 일반 사용자냐, 관리자냐에 따라서 권한 인가가 필요하기 때문에 아래 그림과 같이 Web Application에서, API를 호출하기 전에 사용자의 id와 권한에 따라서 API 호출 여부를 결정하는 API 권한 인가(Authorization) 처리를 하게 한다.

 


Gateway에 의한 권한 인가 처리

이러한 권한 인가는 모바일 클라이언트, 자바스크립트 기반의 웹 클라이언트등 다양한 클라이언트가 지원됨에 따라 점차 서버쪽으로 이동하기 시작했는데, 특히 자바 스크립트 클라이언트의 경우 클라이언트에서 권한에 대한 인가는 의미가 없기 때문에 어쩔 수 없이 서버 쪽에서 권한 인가 처리를 할 수 밖에 없게 된다. 만약에 자바 스크립트에 권한 인가 로직을 넣을 경우, 자바 스크립트의 경우 브라우져의 디버거등으로 코드 수정이 가능하기 때문에 권한 처리 로직을 위회할 수 도 있고 또한 API 포맷만 안다면 직접 API를 서버로 호출해서 권한 인가 없이 API를 사용할 수 있다.

서버에서 권한을 처리하는 방법은 API 호출의 길목이 되는 gateway나 API 비지니스 로직 두군데서 처리가 가능한데, API gateway에 의한 권한 처리는 구현이 쉽지 않기 때문에,API 서버에서 권한 처리를 하는 것이 일반적이다.

아래 그림은 API gateway에서 권한 인가를 처리하는 방법인데, API 호출이 들어오면, API access Token을 사용자 정보와 권한 정보로 API token management 정보를 이용해서 변환 한 후에, 접근하고자 하는 API에 대해서 권한 인가 처리를 한다.

이는 API 별로 API를 접근하고자 하는데 필요한 권한을 체크해야 하는데, HTTP GET /users/{id}의 API를 예로 들어보면, 이 URL에 대한 API를 호출하기 위해서는 일반 사용자 권한을 가지고 있는 사용자의 경우에는 호출하는 사용자 id와 URL상의 {id}가 일치할 때 호출을 허용하고, 같지 않을 때는 호출을 불허해야 한다.

만약 사용자가 관리자 권한을 가지고 있을 경우에는 호출하는 사용자 id와 URL상의 {id}가 일치하지 않더라도 호출을 허용해야 한다.

 


그러나 이러한 api gateway에서의 권한 인가는 쉽지가 않은데, 위의 /users/{id} API의 경우에는 사용자 id가 URL에 들어가 있기 때문에, API access token과 맵핑되는 사용자 ID와 그에 대한 권한을 통해서 API 접근 권한을 통제할 수 있지만, API에 따라서 사용자 id나 권한 인증에 필요한 정보가 HTTP Body에 json 형태나 HTTP Header 등에 들어가 있는 경우, 일일이 메세지 포맷에 따라서 별도의 권한 통제 로직을 gateway 단에서 구현해야 하는 부담이 있고, 권한 통제를 위해서 HTTP 메세지 전체를 일일이 파싱해야 하는 오버로드가 발생하기 때문에, 공통 필드등으로 API 권한 처리를 하지 않는 경우에는 사용하기가 어려운 부분이다.


서버에 의한 API 권한 인가 처리

그래서 가장 일반적이고 보편적인 방법은 API 요청을 처리하는 API 서버의 비지니스 로직단에서 권한 처리를 하는 방식이다.

이 방식은 앞에서 언급한 api gateway 방식과 비교했을때, 각 비지니스 로직에서 API 메세지를 각각 파싱하기 때문에, API 별로 권한 인가 로직을 구현하기가 용이 하다.

이 경우에는 권한 인가에 필요한 필드들을 api gateway에서 변환해서 API 서버로 전달해줌으로써 구현을 간략하게 할 수 있는데,

아래 그림과 같이 API 클라이언트가 api access token을 이용해서 API를 호출했을 경우, api gateway가 이 access token을 권한 인가에 필요한 사용자 id, role등으로 변환해서 API 서버에 전달해주게 되면, 각 비지니스 로직은 API 권한 인가에 필요한 사용자 정보등을 별도로 데이타 베이스를 뒤지지 않고 이 헤더의 내용만을 이용해서 API 권한 인가 처리를 할 수 있게 된다.

 


네트워크 (전송) 레벨 암호화

가장 기본적이고 필수적인 REST API 보안 방법은 네트워크 전송 프로토콜에서 HTTPS 보안 프로토콜을 사용하는 방법이다. HTTPS 프로토콜만 사용한다 하더라도, 메시지 자체를 암호화 해서 전송하기 때문에, 해킹으로 인한 메시지 누출 위협을 해소화 할 수 있다.

그런데, HTTPS를 사용하더라도, 이러한 메시지를 낚아채거나 변조하는 방법이 있는데, 이러한 해킹 방법을 Man-in-The-Middle-Attack 이라고 한다.

정상적인 HTTPS 통신의 경우, 다음과 같이 서버에서 제공하는 인증서 A를 이용하여 API 클라이언트와 서버 상화간에 암호화된 신뢰된 네트워크 연결을 만든다.

 


Man in the middle attack의 경우에는 신뢰된 연결을 만들려고 할 때, 해커가 API 클라이언트와 서버 사이에 끼어 들어온다.

 


다음은 그림과 같이 신뢰된 연결을 만들기 위해서 서버가 인증서 A를 클라이언트에게 내릴 때 해커가 이 인증서가 아닌 인증서 B를 클라이언트에 내리고, 인증서 B를 이용해서 API Client와 Hacker간에 HTTPS SSL 연결을 만든다. 그리고는 서버에게서 받은 인증서 A를 이용해서 해커와 API 서버간의 HTTP SSL 서버를 만든다.

이렇게 되면, 해커는 중간에서 API 클라이언트와 서버 사이에 메시지를 모두 열어 보고 변조도 가능하게 된다.

종종 대기업이나 공공 기관에서 웹브라우져를 사용하다 보면,인증서가 바뀌었다는 메시지를 볼 수 가 있는데 (특히 파이어폭스 브라우져를 사용하면 인증서 변경을 잘 잡아낸다.) 이는 회사의 보안 정책상 HTTPS 프로토콜의 내용을 보고, 이를 감사 하기 위한 목적으로 사용된다.


이런 Man in middle attack을 방지 하는 방법은 여러가지 방식이 있지만, 가장 손쉬운 방법은 공인된 인증서를 사용하고 인증서를 체크하는 것이다. 

해커가 인증서를 바꿔 치려면, 인증서를 발급해야 하는데, 공인 인증서는 Verisign과 같은 기간에서 인증서에 대한 공인 인증을 해준다. 즉, 이 인증서를 발급한 사람이 누구이고 이에 대한 신원 정보를 가지고 있다. 이를 공인 인증서라고 하는데, 공인 인증서는 인증 기관의 Signature로 싸인이 되어 있다. (공인 인증기관이 인증했다는 정보가 암호화 되서 들어간다.)

만약 해커가 공인 인증서를 사용하려면 인증 기관에 가서 개인 정보를 등록해야 하기 때문에, 공인 인증서를 사용하기 어렵고 보통 자체 발급한 비공인 인증서를 사용하기 때문에, 이를 이용해서 체크가 가능하고, 특히 인증서안에는 인증서를 발급한 기관의 정보와 인증서에 대한 고유 Serial 번호가 들어가 있기 때문에, 클라이언트에서 이 값을 체크해서 내가 발급하고 인증 받은 공인 인증서인지를 체크하도록 하면 된다.

아래는 자바의 keytool 유틸리티를 이용해서 자체발급한 인증서의 정보를 프린트해본 내용이다.

(※ keytool -printcert -file CERT.RSA)

Owner: CN=Android Debug, O=Android, C=US

Issuer: CN=Android Debug, O=Android, C=US

Serial number: 6b14b6db

Valid from: Mon Nov 19 09:58:00 KST 2012 until: Wed Nov 12 09:58:00 KST 2042

Certificate fingerprints:

         MD5:  78:69:7F:D5:BD:D7:B7:47:AD:11:6A:D2:F6:83:D7:CB

         SHA1: 44:14:35:A5:C5:28:77:A4:C4:DD:CA:80:26:02:68:A1:84:2E:BD:15

Issuer가 인증서를 발급한 기관의 이름이며, Serial number는 이 인증서에 대한 고유 번호이다. 맨 아래에 있는 fingerprints는 인증서에 대한 해쉬 값으로, 만약에 인증서가 변조되면 이 해쉬값이 달라지기 때문에, 인증서 변조를 확인할 수 있다.


메시지 본문 암호화

다음으로는 간단하게 암호화가 필요한 특정 필드만 애플리케이션단에서 암호화하여 보내는 방법이 있다. 

메시지를 암호화 하여 통신하기 위해서는 클라이언트와 서버가 암호화 키를 가져야 하는데, 암호화 키는 크게, 대칭키와, 비대칭키 알고리즘 두가지가 있다.

비대칭키 알고리즘은, 암호화를 시키는 키와, 암호를 푸는 복호화 키가 다른 경우로, 암호화 시키는 키를 보통 Public Key (공개키)라고 하고, 암호화를 푸는 키를 Private Key(비밀키)라고 한다. 이 공개키는 암호화는 할 수 있지만 반대로 암호화된 메시지를 풀 수 가 없기 때문에 누출이 되더라도 안전하다. (해커가 중간에서 공개키를 낚아 챈다고 하더라도, 이 키로는 암호화된 메시지를 복호화 할 수 없다.)

그래서, 처음에 클라이언트가 서버에 인증이 된 경우, 클라이언트에게 이 공개키를 내린 후에, 향후 메시지를 이 공개키를 통해서 암호화를 하게 하면, 이 암호화된 메시지는 비밀키를 가지고 있는 서버만이 풀 수 있어서 안전하게 서버로 메세지를 암호화 해서 보낼 수 있다.

대표적인 비대칭키 알고리즘으로는 RSA등이 있으며, 우리가 익숙한 HTTPS의 경우에도 이 RSA 알고리즘을 사용한다. RSA 알고리즘을 사용하는 비대칭키 암호화 로직과 라이브러리들은 공개된 것이 많으니 참고해서 사용하도록 한다.

비대칭키 알고리즘의 경우 클라이언트에서 서버로 보내는 단방향 메시지에 대해서는 암호화하여 사용할 수 있지만, 반대로 서버에서 클라이언트로 내려오는 응답 메시지등에는 적용하기가 어렵다. 아니면 클라이언트가 서버에 등록될 때, 위와 반대 방법으로 클라이언트에서 비공개키와 공개키 쌍을 생성한 후에, 서버로 공개키를 보내서 향후 서버에서 클라이언트로의 통신을 그 공개키를 사용하도록 해도 된다. 이경우, 클라이언트 ? 서버, 그리고 서버? 클라이언트간의 키 쌍 두개를 관리해야 하기 때문에 복잡할 수 있는데, 이런 경우에는 대칭키 알고리즘을 고려해볼 수 있다.

대칭키 알고리즘은 암호화와 복호화키가 같은 알고리즘이다.

이 경우 API 클라이언트와 서버가 같은 키를 알고 있어야 하는데, 키를 네트워크를 통해서 보낼 경우 중간에 해커에 의해서 낚아채질 염려가 있기 때문에, 양쪽에 안전하게 키를 전송하는 방법이 필요한데, 다음과 같은 방법을 사용할 수 있다.

1. 서버에서 공개키KA1와 비공개키KA2 쌍을 생성한다.

2. 클라이언트에게 공개키 KA1을 네트워크를 통해서 내려보낸다.

3. 클라이언트는 새로운 비공개 대칭키 KB를 생성한후 KA1을 이용해서 암호화하여 서버로 전송한다.

4. 서버는 전송된 암호화 메시지를 KA2로 복화화 하여, 그 안에 있는 비공개키 KB를 꺼낸다.

5. 향후 클라이언트와 서버는 상호 API통신시 비공개 대칭키 KB를 이용하여 암호화와 복호화를 진행한다.

대칭 키도 여러가지 종류가 있는데, 보안과 성능 측면에서 차이가 있다.

http://www.javamex.com/tutorials/cryptography/ciphers.shtml를 참고하면, 대칭키 기반의 암호화 알고리즘 속도를 비교해놓은 것이 있다. 일반적으로 AES256을 사용하면 빠른 암호화 속도와 높은 보안성을 보장받을 수 있다.(아래, 대칭키 기반의 암호화 알고리즘 속도 비교)

 


메시지 무결성 보장

무결성이란 서버 입장에서 API 호출을 받았을 때 이 호출이 신뢰할 수 있는 호출인지 아닌지를 구별하는 방법이다. 즉 해커가 중간에서 메시지를 가로챈 후, 내용을 변조하여 서버에 보냈을 때, 내용이 변조되었는지 여부를 판단하는 방법인데, 일반적으로 HMAC을 이용한 방식이 널리 사용된다. 어떤 원리로 작동하는지 살펴보도록 하자.

먼저 Rest API를 호출하는 클라이언트와 서버 간에는 대칭키 기반의 암호화 키 ‘Key’를 가지고 있다고 전제하자. 이 키는 클라이언트와 서버 양쪽이 알고 있는 대칭키로, API access Token을 사용할 수 도 있고, 앞의 메시지 본문 암호화에서 나온 방법을 이용해서 서로 대칭키를 교환하여 사용할 수 도 있다..

 


1. 먼저 클라이언트는 호출하고자 하는 REST API의 URL을 앞에서 정의한 Key를 이용하여 HMAC 알고리즘을 사용하여 Hash 값을 추출한다.

중요: 여기서는 편의상 URL을 가지고 HMAC 해시를 생성하였했는데, 전체 메시지에 대한 무결성을 보장하려면 URL이 아니라 메시지 전문 자체에 대해서 대해 Hash를 추출해야 한다.

2. API를 호출할 때, 메시지(또는 URL)에 추출한 HMAC을 포함해서 호출한다.

3. 서버는 호출된 URL을 보고 HMAC을 제외한 나머지 URL을 미리 정의된 Key를 이용해서, HMAC 알고리즘으로 Hash 값을 추출한다.

4. 서버는 3에서 생성된 HMAC 값과 API 호출 시 같이 넘어온 HMAC 값을 비교해서, 값이 같으면 이 호출을 유효한 호출이라고 판단한다.

만약에 만약 해커가 메시지를 중간에서 가로채어 변조하였했을 경우, 서버에서 Hash를 생성하면 변조된 메시지에 대한 Hash가 생성되기 때문에 클라이언트에서 변조 전에 보낸 Hash 값과 다르게 된다. 이를 통해서 통해 메시지가 변조되었는지 여부를 판단할 수 있다.

그런데, 만약에 만약 메시지를 변경하지 않고 Hacker가 동일한 요청을 계속 보낸다면? 메시지를 변조하지 않았기 때문에 서버는 이를 유효한 호출로 인식할 수 있다. 이를 replay attack이라고 하는데 이를 방지하기 위해서는위해서는 time stamp를 사용하는 방법이 있다.

이 방법은 HMAC을 생성할 때, 메시지를 이용해서만 Hash 값을 생성하는 것이 아니라 timestamp를 포함하여 메시지를 생성하는 것이다.

  • HMAC (Key, (메시지 데이터+timestamp) )

그리고 API를 호출할 때, timestamp 값을 같이 실어 보낸다.

http://service.myapi.com/restapiservice?xxxxx&hmac={hashvalue}&timestamp={호출시간}

이렇게 하면 서버는 메시지가 호출된 시간을 알 수 있고, 호출된 시간 +-10분(아니면 개발자가 정한 시간폭)만큼의 호출만 정상적인 호출로 인식하고 시간이 지난 호출의 메시지는 비정상적인 호출로 무시하면 된다.


* 참고 : : Hacker가 timestamp URL등 등을 통해서 통해 볼 수 있다고 하더라도, Key 값을 모르기 때문에 timestamp를 변조할 수 없다. timestamp를 변조할 경우에는 원본 Hash가 원본 timestamp로 생성되었기 때문에, timestamp가 변조된 경우 hash 값이 맞지 않게 된다.

HMAC을 구현하는 해시 알고리즘에는 MD5, SHA1등 등이 있는데, 보통 SHA-1 256bit 알고리즘을 널리 사용한다. 

HMAC 기반의 REST Hash 구현 방법은

http://www.thebuzzmedia.com/designing-a-secure-rest-api-without-oauth-authentication/

에 설명이 있으니 참고하기 바란다.

또한 HMAC 알고리즘 구현에 대해서는 위키(http://en.wikipedia.org/wiki/HMAC)를 보면 각 프로그래밍 언어별로 예제 링크가 있으므로 참고하기 바란다.

지금까지 간단하게나마 API 보안 방식에 대해서 살펴보았다.보안에 대해서 이야기 하자면 한도 끝도 없겠지만, API보안에서는 최소한 HTTPS를 이용한 네트워크 보안과 함께, API Token등의 인증 방식을 반드시 사용하기를 권장한다.

 보안 처리는 하지 않아도, API의 작동이나 사용에는 문제가 없다. 그러나 보안이라는 것은 한번 뚫려버리면 많은 정보가 누출이 되는 것은 물론이고 시스템이 심각한 손상까지 입을 수 있기 때문에,  시간이 걸리더라도 반드시 신경써서 설계 및 구현하는 것을 권장한다.


자바스크립트 클라이언트 지원


근래에 들어서 자바스크립트 기술이 발전하면서 SPA (Sigle Page Application)이 유행하기 시작했는데, SPA란 브라우져에서 페이지간의 이동없이 자바스크립트를 이용해서 동적으로 페이지를 변경할 수 있는 방식이다.

페이지 reloading이 없기 때문에 반응성이 좋아서 많이 사용되는데, SPA 의 경우 서버와의 통신을 자바스크립가 직접 XMLHTTPRequest 객체를 이용해서 API 호출을 바로 하는 형태이다.

이러한 변화는 API 보안 부분에도 새로운 요구사항을 가지고 왔는데, 자바스크립트 클라이언트는 기존의 모바일이나 웹 애플리케이션, 서버등과 다른 기술적인 특성을 가지고 있기 때문이다.

자바스크립트 클라이언트는 코드 자체가 노출된다. 자바스크립트 코드는 브라우져로 로딩되서 수행되기 때문에 사용자 또는 해커가 클라이언트 코드를 볼 수 있다. 그래서 보안 로직등이 들어가 있다고 하더라도 로직 자체는 탈취 당할 수 있다.

아울러 자바스크립트는 실행중에 브라우져의 디버거를 이용해서 변수 값을 보거나 또는 변수값을 변경하거나 비즈니스 로직을 변경하는 등의 행위가 가능하다.

그래서 일반적인 API 보안과는 다른 접근이 필요하다.


Same Origin Policy에 대한 처리

먼저 자바스크립트의 API에 대한 호출은 same origin policy(동일 출처 정책)의 제약을 받는다. Same origin policy란, 자바스크립트와 같이 웹 브라우져에서 동작하는 프로그래밍 언어에서, 웹 브라우져에서 동작하는 프로그램은 해당 프로그램이 로딩된 위치에 있는 리소스만 접근이 가능하다. (http냐 https냐 와 같은 프로토콜과 호출 포트도 정확하게 일치해야 한다.)

아래 그림과 같이 웹사이트 sitea.com에서 자바스크립트를 로딩한 후에, 이 스크립트에서 api.my.com에 있는 API를 XMLHTTPRequest를 통해서 호출하고자 하면, Sane origin policy에 의해서 호출 에러가 난다.

 


이를 해결하는 방법으로는 인프라 측면에서 Proxy를 사용하는 방법이나 또는 JSONP와 CORS (Cross Origin Resource Sharing)이라는 방법이 있는데, 여기서는 많이 사용되는 CORS에 대해서 소개하고자 한다.


Proxy를 이용하는 방식

Proxy를 이용하는 방식은 간단하다. Same origin policy의 문제는 API 서버와 Javascript가 호스팅 되는 서버의 URL이 다르기 때문에 발생하는 문제인데, 이를 앞단에 Reverse Proxy등을 넣어서, 전체 URL을 같게 만들어 주면 된다.

앞의 상황과 같은 일들이 있다고 가정할 때, sitea.com과 api.my.com 앞에 reverse proxy를 넣고, reverse proxy의 주소를 http://mysite.com 으로 세팅한다.

그리고 mysite.com/javascript로 들어오는 요청은 sitea.com으로 라우팅 하고, mysite.com/의 다른 URL로 들어오는 요청은 api.my.com으로 라우팅한다.

 


이러한 구조가 되면, javascript 가 로딩된 사이트도 mysite.com이 되고, javascript에서 호출하고자 하는 api URL도 mysite.com이 되기 때문에, Same Origin Policy에 위배되지 않는다.

이 방식은 단순하지만, 자사의 웹사이트를 서비스 하는 경우에만 가능하다. (타사의 사이트를 Reverse Proxy뒤에 놓기는 쉽지 않다.) 그래서 자사의 서비스용 API를 만드는데는 괜찮지만, 파트너사나 일반 개발자에게 자바스크립트용 REST API를 오픈하는 경우에는 적절하지 않다.


특정 사이트에 대한 접근 허용 방식

CORS 방식중,이 방식은 가장 간단하고 쉬운 방식으로 API 서버의 설정에서 모든 소스에서 들어오는 API 호출을 허용하도록 하는 것이다. api.my.com 이라는 API 서비스를 제공할 때, 이 API를 어느 사이트에서라도 로딩된 자바스크립트라도 호출이 가능하게 하는 것이다.

이는 HTTP로 API를 호출하였을 경우 HTTP Response에 응답을 주면서 HTTP Header에 Request Origin (요청을 처리해줄 수 있는 출처)를 명시 하는 방식이다.

api.my.com에서 응답 헤더에 

  • Access-Control-Allow-Origin: sitea.com

와 같이 명시해주면 sitea.com에 의해서 로딩된 자바스크립트 클라이언트 요청에 대해서만 api.my.com가 요청을 처리해준다.

만약에 다음과 * 로 해주면, request origin에 상관 없이 사이트에서 로딩된 자바스크립트 요청에 대해서 처리를 해준다.

  • Access-Control-Allow-Origin: *

Pre-flight를 이용한 세세한 CORS 통제

REST 리소스 (URL)당 섬세한 CORS 통제가 필요한 경우에는 Pre-flight 호출이라는 것을 이용할 수 있다. 이 방식은 REST 리소스를 호출하기 전에, 웹 브라우져가 HTTP OPTIONS 요청을 보내면 해당 REST Resource에 대해서 가능한 CORS 정보를 보내준다. (접근이 허용된 사이트, 접근이 허용된 메서드 등)

웹브라우져에서는 XMLHTTPRequest를 특정 URL로 요청하기 전에 먼저 HTTP Options를 호출한다.

그러면 서버는 해당 URL을 접근할 수 있는 Origin URL과 HTTP Method를 리턴해준다. 이를 pre-flight 호출이라고 하는데, 이 정보를 기반으로 브라우져는 해당 URL에 XMLHTTPRequest를 보낼 수 있다.

아래 그림을 보자, 브라우져는 http://javascriptclient.com에서 로딩된 자바스크립트로 REST 호출을 하려고 한다.

이를 위해서 HTTP OPTION 메서드로 아래 첫번째 화살표와 같이 /myresource URL에 대해서 pre-flight 호출을 보낸다. 여기에는 Origin Site URL과 허가를 요청하는 HTTP 메서드등을 명시한다. 

서버는 이 URL에 대한 접근 권한을 리턴하는데, 두번째 화살표와 같이 CORS접근이 가능한 Origin 사이트를 http://javascriptclient.com으로 리턴하고 사용할 수 있는 메서드는 POST,GET,OPTIONS 3개로 정의해서 리턴한다. 그리고 이 pre-flight 호출은 Access-Control-Max-Age에 정의된 1728000초 동안 유효하다.  (한번 pre-flight 호출을 하고 나면 이 시간 동안은 다시 pre-flight 호출을 할 필요가 없다.)

 


이러한 CORS 설정은 API 호출 코드에서 직접 구현할 수 도 있지만, 그 보다는 앞단에서 로드 밸런서 역할을 하는 HA Proxy나 nginx와 같은 reverse proxy에서 설정을 통해서 간단하게 처리가 가능하다. 만약에 API단에서 구현이 필요하다하더라도 HTTP Header를 직접 건드리지 말고, Spring 등의 프레임웍에서 이미 CORS 구현을 지원하고 있으니 프레임웍을 통해서 간단하게 구현하는 것을 권장한다.


API access Token에 대한 인증 처리

앞서서 언급하였듯이 자바스크립트 클라이언트는 모바일 앱이나, 서버와 같은 다른 API 클라이언트와 비교해서 api access token을 안전하게 저장할 수 있는 방법이 없기 때문에, 이 API access token에 대해서 다른 관리 방식이 필요하다.

몇가지 추가적인 방식을 사용하는데, 내용은 다음과 같다.


api access token을 Secure Cookie를 통해서 주고 받는다.

api access token을 서버에서 발급하여 자바스크립트 클라이언트로 리턴할 때, HTTP body에 리턴하는 것이 아니라 Secure Cookie에 넣어서 리턴한다. 

※ Secure Cookie : https://www.owasp.org/index.php/SecureFlag

Secure Cookie는 일반 HTTP 프로토콜을 통해서는 전송이 불가능하고 항상 HTTPS를 통해서만 전송이 가능하다. 같은 API 서버로도 일반 HTTP 호출을 할 경우 api access token이 Cookie를 통해서 전달되지 않기 때문에, 네트워크를 통해서 access token을 탈취하는 것은 불가능하다.

여기에 HTTP_ONLY라는 옵션을 쿠키에 추가하는데, 이 옵션을 적용하게 되면, Cookie를 자바스크립트를 통해서 읽거나 조작할 수 없다. 단지 브라우져가 서버로 요청을 보낼 때, 브라우져에 의해서 자동으로 Cookie가 전송된다. 

※ HTTP ONLY 옵션 https://www.owasp.org/index.php/HttpOnly#What_is_HttpOnly.3F

이 두 가지 방법을 쓰면 최소한 자바스크립트 소스코드 분석이나 네트워크 프로토콜 감청을 통한 api access token을 방어할 수 있다


api access token은 해당 세션에서만 유효하도록 한다.

여기에 몇 가지 추가적인 방어 기재를 추가하도록 하는데, 마치 HTTP Session과 같이 특정 IP와 시간내에서만 api access token이 유효하도록 하는 방식이다.

Access token을 발급할 때, access token을 요청한 클라이언트의 IP와 클라이언트의 Origin 을 같이 저장해놓고, 발급할때 유효시간(Expire time)을 정해놓는다. (20 분 등으로).

다음 access token을 이용해서 API가 호출 될 때 마다 IP와 Origin을 확인하고, acess token이 유효시간 (Expire time)시간 내면 이 유효시간을 다시 연장해준다.(+20분을 다시 추가해준다.) 만약에 브라우져에서 일정 시간동안 (20분) API를 호출하지 않았으면 API access token은 폐기되고 다시 access token을 발급 받도록 한다.

이 두 가지 흐름을 도식화해 보면 다음 그림과 같다.

 


모든 통신을 HTTPS를 이용한다.

1. 자바스크립트 클라이언트가 user id와 password를 보내서 사용자 인증과 함께, API access token을 요청한다. HTTPS를 사용한다하더라도 Man in middle attack에 의해서 password가 노출 될 수 있기 때문에, 앞에서 언급한 Digest access Authentication 등의 인증 메커니즘을 활용하여 가급적이면 password를 직접 보내지 않고 인증을 하는 것이 좋다.

2. 서버에서 사용자 인증이 끝나면 api access token을 발급하고 이를 내부 token store에 저장한다. (앞에서 설명한 origin url, ip, expire time등을 저장한다.). 이 필드들은 웹 자바스크립트를 위한 필드로 설명을 위해서 이 필들만 그림에 정의했지만, 실제 시스템 디자인은 웹 클라이언트용과 일반 서버/모바일 앱등을 위한 api access token 정보도 같이 저장해서 두가지 타입을 access token에 대해서 지원하도록 하는 것이 좋다.

3. 생성된 토큰은 Secure Cookie와 HTTP Only 옵션을 통해서 브라우져에게로 전달된다.

4. 브라우져의 자바스크립트 클라이언트에서는 API를 호출할 때 이 api access token이 secure cookie를 통해서 자동으로 서버에 전송되고, 서버는 이 api access token을 통해서 접근 인증 처리를 하고 api server로 요청을 전달하여 처리하도록 한다.


지금까지 간략하게 나마 REST의 개념에서부터 설계 방식 그리고 보안 측면에 대해서 알아보았다.

적절한 표준이나 가이드가 적은 사항이기 때문에 무엇이 딱 맞다고 말할 수 는 없기 때문에 더더욱 설계등이 어려울 수 있으니, 많은 자료들을 참고해보기 바란다.

특히 아무리 좋은 표준이 있다하더라도 팀이 이를 이해하고 사용하지 못하면 표준은 그냥 문서 덩어리일 뿐이다. 표준과 가이드 정립뿐만 아니라 팀에 대한 교육과 표준 준수에 대한 감사 활동등도 고려 해야 한다.

그리고 항상 강조하지만 보안에 대한 부분은 귀찮더라도 항상 빼먹지 말고 구현하도록 하자.


  • REST API 이해와 설계 - #1 개념 잡기 http://bcho.tistory.com/953
  • REST API 이해와 설계 - #2 디자인 가이드  http://bcho.tistory.com/954
  • REST API 이해와 설계 - #3 보안 가이드  http://bcho.tistory.com/955


저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

REST API 이해와 설계 

#2 API 설계 가이드


REST API 디자인 가이드

그러면 REST의 특성을 이해하고 나쁜 안티패턴을 회피해서 REST API 디자인은 어떻게 해야 할까? 짧지만 여기에 몇가지 디자인 방식에 대해서 소개 한다.


REST URI는 심플하고 직관적으로 만들자

REST API를 URI만 보고도, 직관적으로 이해할 수 있어야 한다 URL을 길게 만드는것 보다, 최대 2 depth 정도로 간단하게 만드는 것이 이해하기 편하다.

  • /dogs
  • /dogs/1234

URI에 리소스명은 동사보다는 명사를 사용한다.

REST API는 리소스에 대해서 행동을 정의하는 형태를 사용한다. 예를 들어서

  • POST /dogs

는 /dogs라는 리소스를 생성하라는 의미로, URL은 HTTP Method에 의해 CRUD (생성,읽기,수정,삭제)의 대상이 되는 개체(명사)라야 한다.

잘못된 예들을 보면

  • HTTP Post : /getDogs
  • HTTP Post : /setDogsOwner

위의 예제는 행위를 HTTP Post로 정의하지 않고, get/set 등의 행위를 URL에 붙인 경우인데, 좋지 않은 예 이다. 이보다는

  • HTTP Get : /dogs
  • HTTP Post : /dogs/{puppy}/owner/{terry}

를 사용하는 것이 좋다. 그리고 가급적이면 의미상 단수형 명사(/dog)보다는 복수형 명사(/dogs)를 사용하는 것이 의미상 표현하기가 더 좋다.

일반적으로 권고되는 디자인은 다음과 같다.

리소스

POST

GET

PUT

DELETE

create

read

update

delete

/dogs

새로운 dogs 등록

dogs 목록을 리턴

Bulk 여러 dogs 정보를 업데이트

모든 dogs 정보를 삭제

/dogs/baduk

에러

baduk 이라는 이름의 dogs 정보를 리턴

baduk이라는 이름의 dogs 정보를 업데이트

baduk 이라는 이름의 dogs 정보를 삭제


리소스간의 관계를 표현하는 방법

REST 리소스간에는 서로 연관관계가 있을 수 있다. 예를 들어 사용자가 소유하고 있는 디바이스 목록이나 사용자가 가지고 있는 강아지들 등이 예가 될 수 가 있는데, 사용자-디바이스 또는 사용자-강아지등과 같은 각각의 리소스간의 관계를 표현하는 방법은 여러가지가 있다.


Option 1. 서브 리소스로 표현하는 방법

예를 들어 사용자가 가지고 있는 핸드폰 디바이스 목록을 표현해보면

  • /”리소스명”/”리소스 id”/”관계가 있는 다른 리소스명” 형태 
  • HTTP Get : /users/{userid}/devices
    예) /users/terry/devices

과 같이 /terry라는 사용자가 가지고 있는 디바이스 목록을 리턴하는 방법이 있고


Option 2. 서브 리소스에 관계를 명시 하는 방법

만약에 관계의 명이 복잡하다면 관계명을 명시적으로 표현하는 방법이 있다. 예를 들어 사용자가 “좋아하는” 디바이스 목록을 표현해보면

  • HTTP Get : /users/{userid}/likes/devices
    예) /users/terry/likes/devices

는 terry라는 사용자가 좋아하는 디바이스 목록을 리턴하는 방식이다.

Option 1,2 어떤 형태를 사용하더라도 문제는 없지만, Option 1의 경우 일반적으로 소유 “has”의 관계를 묵시적으로 표현할 때 좋으며, Option 2의 경우에는 관계의 명이 애매하거나 구체적인 표현이 필요할 때 사용한다. 


에러처리

에러처리의 기본은 HTTP Response Code를 사용한 후, Response body에 error detail을 서술하는 것이 좋다.

대표적인 API 서비스들이 어떤 HTTP Response Code를 사용하는지를 살펴보면 다음과 같다.

구글의 gdata 서비스의 경우 10개, 넷플릭스의 경우 9개, Digg의 경우 8개의 Response Code를 사용한다.  (정보 출처:  http://info.apigee.com/Portals/62317/docs/web%20api.pdf)

Google GData

200 201 304 400 401 403 404 409 410 500


Netflix

200 201 304 400 401 403 404 412 500


Digg

200 400 401 403 404 410 500 503

여러 개의 response code를 사용하면 명시적이긴 하지만, 코드 관리가 어렵기 때문에 아래와 같이 몇가지 response code만을 사용하는 것을 권장한다.

  • 200 성공
  • 400 Bad Request - field validation 실패시
  • 401 Unauthorized - API 인증,인가 실패
  • 404 Not found ? 해당 리소스가 없음
  • 500 Internal Server Error - 서버 에러

추가적인 HTTP response code에 대한 사용이 필요하면 http response code 정의 http://en.wikipedia.org/wiki/Http_error_codes 문서를 참고하기 바란다.

다음으로 에러에는 에러 내용에 대한 디테일 내용을 http body에 정의해서, 상세한 에러의 원인을 전달하는 것이 디버깅에 유리하다.

Twillo의 Error Message 형식의 경우

  • HTTP Status Code : 401
  • {“status”:”401”,”message”:”Authenticate”,”code”:200003,”more info”:”http://www.twillo.com/docs/errors/20003"}

와 같이 표현하는데, 에러 코드 번호와 해당 에러 코드 번호에 대한 Error dictionary link를 제공한다.

비단 API 뿐 아니라, 잘 정의된 소프트웨어 제품의 경우에는 별도의 Error 번호 에 대한 Dictionary 를 제공하는데, Oracle의 WebLogic의 경우에도http://docs.oracle.com/cd/E24329_01/doc.1211/e26117/chapter_bea_messages.htm#sthref7 와 같이 Error 번호, 이에 대한 자세한 설명과, 조치 방법등을 설명한다. 이는 개발자나 Trouble Shooting하는 사람에게 많은 정보를 제공해서, 조금 더 디버깅을 손쉽게 한다. (가급적이면 Error Code 번호를 제공하는 것이 좋다.)

다음으로 에러 발생시에, 선택적으로 에러에 대한 스택 정보를 포함 시킬 수 있다.

에러메세지에서 Error Stack 정보를 출력하는 것은 대단히 위험한 일이다. 내부적인 코드 구조와 프레임웍 구조를 외부에 노출함으로써, 해커들에게, 해킹을 할 수 있는 정보를 제공하기 때문이다. 일반적인 서비스 구조에서는 아래와 같은 에러 스택정보를 API 에러 메세지에 포함 시키지 않는 것이 바람직 하다.

log4j:ERROR setFile(null,true) call failed.

java.io.FileNotFoundException: stacktrace.log (Permission denied)

at java.io.FileOutputStream.openAppend(Native Method)

at java.io.FileOutputStream.(FileOutputStream.java:177)

at java.io.FileOutputStream.(FileOutputStream.java:102)

at org.apache.log4j.FileAppender.setFile(FileAppender.java:290)

at org.apache.log4j.FileAppender.activateOptions(FileAppender.java:164)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

그렇지만, 내부 개발중이거나 디버깅 시에는 매우 유용한데, API 서비스를 개발시, 서버의 모드를 production과 dev 모드로 분리해서, 옵션에 따라 dev 모드등으로 기동시, REST API의 에러 응답 메세지에 에러 스택 정보를 포함해서 리턴하도록 하면, 디버깅에 매우 유용하게 사용할 수 있다.


API 버전 관리

API 정의에서 중요한 것중의 하나는 버전 관리이다. 이미 배포된 API 의 경우에는 계속해서 서비스를 제공하면서,새로운 기능이 들어간 새로운 API를 배포할때는 하위 호환성을 보장하면서 서비스를 제공해야 하기 때문에, 같은 API라도 버전에 따라서 다른 기능을 제공하도록 하는 것이 필요하다.

API의 버전을 정의하는 방법에는 여러가지가 있는데,

  • Facebook ?v=2.0
  • salesforce.com /services/data/v20.0/sobjects/Account

필자의 경우에는

  • {servicename}/{version}/{REST URL}
  • example) api.server.com/account/v2.0/groups

형태로 정의 하는 것을 권장한다.

이는 서비스의 배포 모델과 관계가 있는데, 자바 애플리케이션의 경우, account.v1.0.war, account.v2.0.war와 같이 다른 war로 각각 배포하여 버전별로 배포 바이너리를 관리할 수 있고, 앞단에 서비스 명을 별도의 URL로 떼어 놓는 것은 향후 서비스가 확장되었을 경우에, account 서비스만 별도의 서버로 분리해서 배포하는 경우를 생각할 수 있다.

외부로 제공되는 URL은 api.server.com/account/v2.0/groups로 하나의 서버를 가르키지만, 내부적으로, HAProxy등의 reverse proxy를 이용해서 이런 URL을 맵핑할 수 있는데, api.server.com/account/v2.0/groups를 내부적으로 account.server.com/v2.0/groups 로 맵핑 하도록 하면, 외부에 노출되는 URL 변경이 없이 향후 확장되었을때 서버를 물리적으로 분리해내기가 편리하다.


페이징

큰 사이즈의 리스트 형태의 응답을 처리하기 위해서는 페이징 처리와 partial response 처리가 필요하다.

리턴되는 리스트 내용이 1,000,000개인데, 이를 하나의 HTTP Response로 처리하는 것은 서버 성능, 네트워크 비용도 문제지만 무엇보다 비현실적이다. 그래서, 페이징을 고려하는 것이 중요하다.

페이징을 처리하기 위해서는 여러가지 디자인이 있다.

예를 들어 100번째 레코드부터 125번째 레코드까지 받는 API를 정의하면

Facebook API 스타일 : /record?offset=100&limit=25

  • Twitter API 스타일 : /record?page=5&rpp=25 (RPP는 Record per page로 페이지당 레코드수로 RPP=25이면 페이지 5는 100~125 레코드가 된다.)
  • LikedIn API 스타일 : /record?start=50&count=25
  • 구현 관점에서 보면 , 페이스북 API가 조금 더 직관적이기 때문에, 페이스북 스타일을 사용하는 것을 권장한다.
  • record?offset=100&limit=25

100번째 레코드에서부터 25개의 레코드를 출력한다.


Partial Response 처리

리소스에 대한 응답 메세지에 대해서 굳이 모든 필드를 포함할 필요가 없는 케이스가 있다.

 예를 들어 페이스북 FEED의 경우에는 사용자 ID, 이름, 글 내용, 날짜, 좋아요 카운트, 댓글, 사용자 사진등등 여러가지 정보를 갖는데, API를 요청하는 Client의 용도에 따라 선별적으로 몇가지 필드만이 필요한 경우가 있다. 필드를 제한하는 것은 전체 응답의 양을 줄여서 네트워크 대역폭(특히 모바일에서) 절약할 수 있고, 응답 메세지를 간소화하여 파싱등을 간략화할 수 있다.

그래서 몇몇 잘 디자인된, REST API의 경우 이러한 Partial Response 기능을 제공하는데, 주요 서비스들을 비교해보면 다음과 같다.

  • Linked in : /people:(id,first-name,last-name,industry)
  • Facebook : /terry/friends?fields=id,name
  • Google : ?fields=title,media:group(media:thumnail)

Linked in 스타일의 경우 가독성은 높지만 :()로 구별하기 때문에, HTTP 프레임웍으로 파싱하기가 어렵다. 전체를 하나의 URL로 인식하고, :( 부분을 별도의 Parameter로 구별하지 않기 때문이다.

Facebook과 Google은 비슷한 접근 방법을 사용하는데, 특히 Google의 스타일은 더 재미있는데, group(media:thumnail) 와 같이 JSON의 Sub-Object 개념을 지원한다.

Partial Response는 Facebook 스타일이 구현하기가 간단하기 때문에, 필자의 경우는 Facebook 스타일의 partial response를 사용하는 것을 권장한다.


검색 (전역검색과 지역검색)

검색은 일반적으로 HTTP GET에서 Query String에 검색 조건을 정의하는 경우가 일반적인데, 이 경우 검색조건이 다른 Query String과 섞여 버릴 수 있다. 예를 들어 name=cho이고, region=seoul인 사용자를 검색하는 검색을 Query String만 사용하게 되면 다음과 같이 표현할 수 있다.

  • /users?name=cho&region=seoul

그런데, 여기에 페이징 처리를 추가하게 되면

  • /users?name=cho&region=seoul&offset=20&limit=10

페이징 처리에 정의된 offset과 limit가 검색 조건인지 아니면 페이징 조건인지 분간이 안간다. 그래서, 쿼리 조건은 하나의 Query String으로 정의하는 것이 좋은데

  • /user?q=name%3Dcho,region%3Dseoul&offset=20&limit=10

이런식으로 검색 조건을 URLEncode를 써서 “q=name%3Dcho,region%3D=seoul” 처럼 (실제로는 q= name=cho,region=seoul )표현하고 Deleminator를 , 등을 사용하게 되면 검색 조건은 다른 Query 스트링과 분리된다.

물론 이 검색 조건은 서버에 의해서 토큰 단위로 파싱되어야 한다.

다음으로는 검색의 범위에 대해서 고려할 필요가 있는데, 전역 검색은 전체 리소스에 대한 검색을, 리소스에 대한 검색은 특정 리소스에 대한 검색을 정의한다.

예를 들어 시스템에 user,dogs,cars와 같은 리소스가 정의되어 있을때,id=’terry’인 리소스에 대한 전역 검색은

  • /search?q=id%3Dterry

와 같은 식으로 정의할 수 있다. /search와 같은 전역 검색 URI를 사용하는 것이다.

반대로 특정 리소스안에서만의 검색은

  • /users?q=id%3Dterry

와 같이 리소스명에 쿼리 조건을 붙이는 식으로 표현이 가능하다.


HATEOS를 이용한 링크 처리

HATEOS는 Hypermedia as the engine of application state의 약어로, 하이퍼미디어의 특징을 이용하여 HTTP Response에 다음 Action이나 관계되는 리소스에 대한 HTTP Link를 함께 리턴하는 것이다.

예를 들어 앞서 설명한 페이징 처리의 경우, 리턴시, 전후페이지에 대한 링크를 제공한다거나

{  

   [  

      {  

         "id":"user1",

         "name":"terry"

      },

      {  

         "id":"user2",

         "name":"carry"

      }

   ],

   "links":[  

      {  

         "rel":"pre_page",

         "href":"http://xxx/users?offset=6&limit=5"

      },

      {  

         "rel":"next_page",

         "href":"http://xxx/users?offset=11&limit=5"

      }

   ]

}

와 같이 표현하거나

연관된 리소스에 대한 디테일한 링크를 표시 하는 것등에 이용할 수 있다.

{  

   "id":"terry",

   "links":[  

      {  

         "rel":"friends",

         "href":"http://xxx/users/terry/friends"

      }

   ]

}

HATEOAS를 API에 적용하게 되면, Self-Descriptive 특성이 증대되어 API에 대한 가독성이 증가되는 장점을 가지고 있기는 하지만, 응답 메세지가 다른 리소스 URI에 대한 의존성을 가지기 때문에, 구현이 다소 까다롭다는 단점이 있다.

요즘은 Spring과 같은 프레임웍에서 프레임웍 차원에서 HATEOAS를 지원하고 있으니 참고하기 바란다.http://spring.io/understanding/HATEOAS


단일 API 엔드포인트 활용

API 서버가 물리적으로 분리된 여러개의 서버에서 동작하고 있을때, user.apiserver.com, car.apiserver.com과 같이 API 서비스마다 URL이 분리되어 있으면 개발자가 사용하기 불편하다. 매번 다른 서버로 연결을 해야하거니와 중간에 방화벽이라도 있으면, 일일이 방화벽을 해제해야 한다.

API 서비스는 물리적으로 서버가 분리되어 있더라도 단일 URL을 사용하는 것이 좋은데, 방법은 HAProxy나 nginx와 같은 reverse proxy를 사용하는 방법이 있다. HAProxy를 앞에 새우고 api.apiserver.com이라는 단일 URL을 구축한후에

HAProxy 설정에서

  • api.apiserver.com/user는 user.apiserver.com 로 라우팅하게 하고
  • api.apiserver.com/car 는 car.apiserver.com으로 라우팅 하도록 구현하면 된다.

이렇게 할 경우 향후 뒷단에 API 서버들이 확장이 되더라도 API를 사용하는 클라이언트 입장에서는 단일 엔드포인트를 보면 되고, 관리 관점에서도 단일 엔드포인트를 통해서 부하 분산 및 로그를 통한 Audit(감사)등을 할 수 있기 때문에 편리하며, API 에 대한 라우팅을 reverse proxy를 이용해서 함으로써 조금 더 유연한 운용이 가능하다.


REST에 문제점


그렇다면 이렇게 많은 장점이 있는 REST는 만능인가? REST역시 몇가지 취약점과 단점을 가지고 있다.


JSON+HTTP 를 쓰면 REST인가?

REST에 대한 잘못된 이해중의 하나가, HTTP + JSON만 쓰면 REST라고 부르는 경우인데, 앞의 안티패턴에서도 언급하였듯이, REST 아키텍쳐를 제대로 사용하는 것은, 리소스를 제대로 정의하고 이에대한 CRUD를 HTTP 메서드인 POST/PUT/GET/DELETE에 대해서 맞춰 사용하며, 에러코드에 대해서 HTTP Response code를 사용하는 등, REST에 대한 속성을 제대로 이해하고 디자인해야 제대로된 REST 스타일이라고 볼 수 있다.

수년전 뿐만 아니라 지금에도 이러한 안티패턴이 적용된 REST API 형태가 많이 있기 때문에, 제대로된 REST 사상의 이해 후에, REST를 사용하도록 해야 한다.


표준 규약이 없다

REST는 표준이 없다. 그래서 관리가 어렵다. 

SOAP 기반의 웹 서비스와 같이 메시지 구조를 정의하는 WSDL도 없고, UDDI와 같은 서비스 관리체계도 없다. WS-I이나 WS-*와 같은 메시지 규약도 없다. 

REST가 최근 부각되는 이유 자체가 WebService의 복잡성과 표준의 난이도 때문에 Non Enterprise 진영(Google, Yahoo, Amazone)을 중심으로 집중적으로 소개된 것이다. 데이터에 대한 의미 자체가 어떤 비즈니스 요건처럼 Mission Critical한 요건이 아니기 때문에, 서로 데이터를 전송할 수 있는 정도의 상호 이해 수준의 표준만이 필요했지 Enterprise 수준의 표준이 필요하지도 않았고, 벤더들처럼 이를 주도하는 회사도 없었다.

단순히 많이 사용하고 암묵적으로 암암리에 생겨난 표준 비슷한 것이 있을 뿐이다(이런 것을 Defactor 표준이라고 부른다). 

그런데 문제는 정확한 표준이 없다 보니, 개발에 있어 이를 관리하기가 어렵다는 것이다. 표준을 따르면   몇 가지 스펙에 맞춰서  맞춰 개발 프로세스나 패턴을 만들 수 있는데, REST에는 표준이 없으니 REST 기반으로 시스템을 설계하자면 사용할 REST에 대한 자체 표준을 정해야 하고, 어떤 경우에는 REST에 대한 잘못된 이해로 잘못된 REST 아키텍처에 ‘이건 REST다’는 딱지를 붙이기도 한다. 실제로 WEB 2.0의 대표 주자격인 Flickr.com도 REST의 특성을 살리지 못하면서 RPC 스타일로 디자인한 API를 HTTP + XML을 사용했다는 이유로 Hybrid REST라는 이름을 붙여 REST 아키텍쳐아키텍처에 대한 혼란을 초래했다.

근래에 들어서 YAML등과 같이 REST 에 대한 표준을 만들고자 하는 움직임은 있으나, JSON의 자유도를 제약하는 방향이고 Learning curve가 다소 높기 때문에, 그다지 확산이 되지 않고 있다.

이런 비표준에서 오는 관리의 문제점은, 제대로된 REST API 표준 가이드와, API 개발 전후로 API 문서(Spec)을 제대로 만들어서 리뷰하는 프로세스를 갖추는 방법으로 해결하는 방법이 좋다.


기존의 전통적인 RDBMS에 적용 시키기에 쉽지 않다.

예를 들어 리소스를 표현할 때,  리소스는 DB의 하나의 Row가 되는 경우가 많은데, DB의 경우는 Primary Key가 복합 Key 형태로 존재하는 경우가 많다. (여러 개의 컬럼이 묶여서 하나의 PK가 되는 경우) DB에서는 유효한 설계일지 몰라도, HTTP URI는 / 에 따라서 계층 구조를 가지기 때문에, 이에 대한 표현이 매우 부자연 스러워진다.

예를 들어 DB의 PK가 “세대주의 주민번호”+”사는 지역”+”본인 이름일 때” DB에서는 이렇게 표현하는 것이 하나 이상할 것이 없으나, REST에서 이를 userinfo/{세대주 주민번호}/{사는 지역}/{본인 이름} 식으로 표현하게 되면 다소 이상한 의미가 부여될 수 있다.

이외에도 resource에 대한 Unique한 Key를 부여하는 것에 여러가지 애로점이 있는데, 이를 해결하는 대안으로는 Alternative Key (AK)를 사용하는 방법이 있다. 의미를 가지지 않은 Unique Value를 Key로 잡아서 DB Table에 AK라는 필드로 잡아서 사용 하는 방법인데. 이미 Google 의 REST도 이러한 AK를 사용하는 아키텍쳐를 채택하고 있다.

그러나 DB에 AK 필드를 추가하는 것은 전체적인 DB설계에 대한 변경을 의미하고 이는 즉 REST를 위해서 전체 시스템의 아키텍쳐에 변화를 준다는 점에서 REST 사용시 아키텍쳐적인 접근의 필요성을 의미한다. 

그래서 근래에 나온 mongoDB나 CouchDB,Riak등의 Document based NoSQL의 경우 JSON Document를 그대로 넣을 수 있는 구조를 갖추는데,하나의 도큐먼트를 하나의 REST 리소스로 취급하면 되기 때문에 REST의 리소스 구조에 맵핑 하기가 수월하다.



  • REST API 이해와 설계 - #1 개념 잡기 http://bcho.tistory.com/953
  • REST API 이해와 설계 - #2 디자인 가이드  http://bcho.tistory.com/954
  • REST API 이해와 설계 - #3 보안 가이드  http://bcho.tistory.com/955


저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

REST API의 이해와 설계

#1-개념 소개


REST는 웹의 창시자(HTTP) 중의 한 사람인 Roy Fielding의 2000년 논문에 의해서 소개되었다. 현재의 아키텍쳐가 웹의 본래 설계의 우수성을 많이 사용하지 못하고 있다고 판단했기 때문에, 웹의 장점을 최대한 활용할 수 있는 네트워크 기반의 아키텍쳐를 소개했는데 그것이 바로 Representational safe transfer (REST)이다.


REST의 기본

REST는 요소로는 크게 리소스,메서드,메세지 3가지 요소로 구성된다.

예를 들어서 “이름이 Terry인 사용자를 생성한다” 라는 호출이 있을 때

“사용자”는 생성되는 리소스 , “생성한다” 라는 행위는 메서드 그리고 ‘이름이 Terry인 사용자’는 메시지가 된다

이를 REST 형태로 표현해보면

HTTP POST , http://myweb/users/

{  

   "users":{  

      "name":"terry"

   }

}

와 같은 형태로 표현되며, 생성한다의 의미를 갖는 메서드는 HTTP Post 메서드가 되고, 생성하고자 하는 대상이 되는 사용자라는 리소스는 http://myweb/users 라는 형태의 URI로 표현이 되며, 생성하고자 하는 사용자의 디테일한 내용은 JSON 문서를 이용해서 표현된다.


HTTP 메서드

REST에서는 앞에서 잠깐 언급한바와 같이, 행위에 대한 메서드를 HTTP 메서드를 그대로 사용한다.

HTTP 에는 여러가지 메서드가 있지만 REST에서는 CRUD(Create Read Update Delete)에 해당 하는 4가지의 메서드만 사용한다.

메서드

의미

Idempotent

POST

Create

No

GET

Select

Yes

PUT

Update

Yes

DELETE

Delete

Yes


각각 Post,Put,Get,Delete는 각각의 CRUD 메서드에 대응된다. 여기에 idempotent라는 분류를 추가 했는데, Idempotent는 여러 번 수행을 해도 결과가 같은 경우를 의미한다.

예를 들어 a++는 Idempotent 하지 않다고 하지만(호출시마다 값이 증가 되기 때문에), a=4와 같은 명령은 반복적으로 수행해도 Idempotent하다. (값이 같기 때문에) 

POST 연산의 경우에는 리소스를 추가하는 연산이기 때문에, Idempotent하지 않지만 나머지 GET,PUT,DELETE는 반복 수행해도 Idempotent 하다. GET의 경우 게시물의 조회수 카운트를 늘려준다던가 하는 기능을 같이 수행했을 때는 Idempotent 하지 않은 메서드로 정의해야 한다.

Idempotent의 개념에 대해서 왜 설명을 하냐 하면, REST는 각 개별 API를 상태 없이 수행하게 된다. 그래서, 해당 REST API를 다른 API와 함께 호출하다가 실패하였을 경우, 트렌젝션 복구를 위해서 다시 실행해야 하는 경우가 있는데, Idempotent 하지 않은 메서드들의 경우는 기존 상태를 저장했다가 다시 원복해줘야 하는 문제가 있지만, Idempotent 한 메서드의 경우에는 반복적으로 다시 메서드를 수행해주면 된다.

예를 들어 게시물 조회를 하는 API가 있을때, 조회시 마다 조회수를 올리는 연산을 수행한다면 이 메서드는 Idempotent 하다고 볼수 없고, 조회하다가 실패하였을 때는 올라간 조회수를 다시 -1로 빼줘야 한다. 즉 Idempotent 하지 않은 메서드에 대해서는 트렌젝션에 대한 처리가 별다른 주의가 필요하다.


REST의 리소스

REST는 리소스 지향 아키텍쳐 스타일이라는 정의 답게 모든 것을 리소스 즉 명사로 표현을 하며, 각 세부 리소스에는 id를 붙인다.

즉 사용자라는 리소스 타입을 http://myweb//users라고 정의했다면, terry라는 id를 갖는 리소스는 http://myweb/users/terry 라는 형태로 정의한다.

REST의 리소스가 명사의 형태를 띄우다 보니, 명령(Operation)성의 API를 정의하는 것에서 혼돈이 올 수 있다.

예를 들어서 “Push 메서지를 보낸다”는 보통 기존의 RPC(Remote Procedure Call)이나 함수성 접근해서는 /myweb/sendpush 형태로 잘못 정의가 될 수 있지만, 이러한 동사형을 명사형으로 바꿔서 적용해보면 리소스 형태로 표현하기가 조금더 수월해 진다.

“Push 메시지 요청을 생성한다.”라는 형태로 정의를 변경하면, API 포맷은 POST/myweb/push 형태와 같이 명사형으로 정의가 될 수 있다. 물론 모든 형태의 명령이 이런 형태로 정의가 가능한 것은 아니지만, 되도록이면 리소스 기반의 명사 형태로 정의를 하는게 REST형태의 디자인이 된다. 

REST API의 간단한 예제

그러면, 간단한 REST API의 예제를 살펴보도록 하자 간단한 사용자 생성 API를 살펴보면

사용자 생성

다음은 http://myweb/users 라는 리소스를 이름은 terry, 주소는 seoul 이라는 내용(메시지)로 HTTP Post를 이용해서 생성하는 정의이다. 

HTTP Post, http://myweb/users/

{  

   "name":"terry",

   "address":"seoul"

}


조회

다음은 생성된 리소스 중에서 http://myweb/users 라는 사용자 리소스중에, id가 terry 인 사용자 정보를 조회해오는 방식이다. 조회이기 때문에, HTTP Get을 사용한다.

HTTP Get, http://myweb/users/terry

업데이트

다음은 http://myweb/users 라는 사용자 리소스중에, id가 terry 인 사용자 정보에 대해서, 주소를 “suwon”으로 수정하는 방식이다. 수정은 HTTP 메서드 중에 PUT을 사용한다.

HTTP PUT, http://myweb/users/terry

{  

   "name":"terry",

   "address":"suwon"

}


삭제

마지막으로 http://myweb/users 라는 사용자 리소스중에, id가 terry 사용자 정보를 삭제 하는 방법이다.

HTTP DELETE, http://myweb/users/terry


API 정의를 보면 알겠지만 상당히 간단하다. 단순하게 리소스를 URI로 정해준 후에, 거기에 HTTP 메서드를 이용해서 CRUD를 구현하고 메시지를 JSON으로 표현하여 HTTP Body에 실어 보내면 된다. POST URI에 리소스 id가 없다는 것을 빼면 크게 신경쓸 부분이 없다. 


REST의 특성

REST의 기본적인 개념을 이해했으면 조금 더 상세한 REST의 특성에 대해서 알아보도록 하자


유니폼 인터페이스(Uniform Interface)

REST는 HTTP 표준에만 따른 다면, 어떠한 기술이라던지 사용이 가능한 인터페이스 스타일이다. 예를 들어 HTTP + JSON으로 REST API를 정의했다면, 안드로이드 플랫폼이건, iOS 플랫폼이건, 또는 C나 Java/Python이건 특정 언어나 기술에 종속 받지 않고 HTTP와 JSON을 사용할 수 있는 모든 플랫폼에 사용이 가능한 느슨한 결함(Loosely coupling) 형태의 구조이다.

※ 흔히들 근래에 REST를 이야기 하면, HTTP + JSON을 쉽게 떠올리는데, JSON은 하나의 옵션일뿐, 메시지 포맷을 꼭 JSON으로 적용해야할 필요는 없다. 자바스크립트가 유행하기전에만 해도 XML 형태를 많이 사용했으며, 근래에 들어서 사용의 편리성 때문에 JSON을 많이 사용하고 있지만, XML을 사용할 경우, XPath,XSL등 다양한 XML 프레임웍을 사용할 수 있을뿐만 아니라 메시지 구조를 명시적으로 정의할 수 있는 XML Scheme나 DTD등을 사용할 수 있기 때문에, 복잡도는 올라가더라도, 메시지 정의의 명확성을 더할 수 있다. 


무상태성/스테이트리스(Stateless)

REST는 REpresentational State Transfer 의 약어로 Stateless (상태 유지하지 않음)이 특징 중의 하나이다.

상태가 있다 없다는 의미는 사용자나 클라이언트의 컨택스트를 서버쪽에 유지 하지 않는다는 의미로,쉽게 표현하면 HTTP Session과 같은 컨텍스트 저장소에 상태 정보를 저장하지 않는 형태를 의미한다.

상태 정보를 저장하지 않으면 각 API 서버는 들어오는 요청만을 들어오는 메시지로만 처리하면 되며, 세션과 같은 컨텍스트 정보를 신경쓸 필요가 없기 때문에 구현이 단순해진다.


캐슁 가능(Cacheable)

REST의 큰 특징 중의 하나는 HTTP라는 기존의 웹 표준을 그대로 사용하기 때문에, 웹에서 사용하는 기존의 인프라를 그대로 활용이 가능하다. 

HTTP 프로토콜 기반의 로드 밸런서나 SSL은 물론이고, HTTP가 가진 가장 강력한 특징중의 하나인 캐슁 기능을 적용할 수 있다.일반적인 서비스 시스템에서 60%에서 많게는 80%가량의 트렌젝션이 Select와 같은 조회성 트렌젝션인 것을 감안하면, HTTP의 리소스들을 웹캐쉬 서버등에 캐슁하는 것은 용량이나 성능 면에서 많은 장점을 가지고 올 수 있다.구현은 HTTP 프로토콜 표준에서 사용하는 “Last-Modified” 태그나 E-Tag를 이용하면 캐슁을 구현할 수 있다.

아래와 같이 Client가 HTTP GET을 “Last-Modified” 값과 함께 보냈을 때, 컨텐츠가 변화가 없으면 REST 컴포넌트는 “304 Not Modified”를 리턴하면 Client는 자체 캐쉬에 저장된 값을 사용하게 된다.


 

그림  1 Last Modified 필드를 이용한 캐슁 처리 방식

이렇게 캐쉬를 사용하게 되면 네트웍 응답시간 뿐만 아니라, REST 컴포넌트가 위치한 서버에 트렌젝션을 발생시키지 않기 때문에, 전체 응답시간과 성능 그리고 서버의 자원 사용률을 비약적으로 향상 시킬 수 있다.


자체 표현 구조(Self-descriptiveness)

REST의 가장 큰 특징 중의 하나는 REST API 자체가 매우 쉬워서 API 메시지 자체만 보고도 API를 이해할 수 있는 Self-descriptiveness 구조를 갖는 다는 것이다. 리소스와 메서드를 이용해서 어떤 메서드에 무슨 행위를 하는지를 알 수 있으며, 또한 메시지 포맷 역시 JSON을 이용해서 직관적으로 이해가 가능한 구조이다. 

대부분의 REST 기반의 OPEN API들이 API 문서를 별도로 제공하고 있지만, 디자인 사상은 최소한의 문서의 도움만으로도 API 자체를 이해할 수 있어야 한다.


클라이언트 서버 구조 (Client-Server 구조)

근래에 들면서 재 정립되고 있는 특징 중의 하나는 REST가 클라이언트 서버 구조라는 것이다. (당연한 것이겠지만).

REST 서버는 API를 제공하고, 제공된 API를 이용해서 비즈니스 로직 처리 및 저장을 책임진다.

클라이언트의 경우 사용자 인증이나 컨택스트(세션,로그인 정보)등을 직접 관리하고 책임 지는 구조로 역할이 나뉘어 지고 있다.  이렇게 역할이 각각 확실하게 구분되면서, 개발 관점에서 클라이언트와 서버에서 개발해야 할 내용들이 명확하게 되고 서로의 개발에 있어서 의존성이 줄어들게 된다.

계층형 구조 (Layered System)

계층형 아키텍쳐 구조 역시 근래에 들어서 주목받기 시작하는 구조인데, 클라이언트 입장에서는 REST API 서버만 호출한다.

그러나 서버는 다중 계층으로 구성될 수 있다. 순수 비즈니스 로직을 수행하는 API 서버와 그 앞단에 사용자 인증 (Authentication), 암호화 (SSL), 로드밸런싱등을 하는 계층을 추가해서 구조상의 유연성을 둘 수 있는데, 이는 근래에 들어서 앞에서 언급한 마이크로 서비스 아키텍쳐의 api gateway나, 간단한 기능의 경우에는 HA Proxy나 Apache와 같은 Reverse Proxy를 이용해서 구현하는 경우가 많다.


REST 안티 패턴

REST의 개념과 전체적인 특징에 대해서 살펴보았다. 이제는 REST 디자인시 하지 말아야 할 안티 패턴에 대해서 알아보도록 하자.


GET/POST를 이용한 터널링

가장 나쁜 디자인 중 하나가 GET이나 POST를 이용한 터널링이다.

http://myweb/users?method=update&id=terry 이 경우가 전형적인 GET을 이용한 터널링이다. 메서드의 실제 동작은 리소스를 업데이트 하는 내용인데, HTTP PUT을 사용하지 않고, GET에 쿼리 패러미터로 method=update라고 넘겨서, 이 메서드가 수정 메세드임을 명시했다.

대단히 안좋은 디자인인데, HTTP 메서드 사상을 따르지 않았기 때문에, REST라고 부를 수 도 없고, 또한 웹 캐쉬 인프라등도 사용이 불가능하다.

또 많이 사용하는 안좋은예는 POST를 이용한 터널링이다. Insert(Create)성 오퍼러이션이 아닌데도 불구하고, JSON 바디에 오퍼레이션 명을 넘기는 형태인데 예를 들어 특정 사용자 정보를 가지고 오는 API를 아래와 같이 POST를 이용해서 만든 경우이다. 

HTTP POST, http://myweb/users/

{  

   "getuser":{  

      "id":"terry",   

}

}


Self-descriptiveness 속성을 사용하지 않음

앞서 특징에서 설명한 바와 같이 REST의 특성중 하나는 자기 서술성(Self-descriptiveness) 속성으로 REST URI와 메서드 그리고 쉽게 정의된 메시지 포맷에 의해서 쉽게 API를 이해할 수 있는 기능이 되어야 한다. 

특히나 자기 서술성을 깨먹는 가장 대표적인 사례가 앞서 언급한 GET이나 POST를 이용한 터널링을 이용한 구조가 된다.


HTTP Response code를 사용하지 않음

다음으로 많이 하는 실수중의 하나가 Http Response code를 충실하게 따르지 않고, 성공은 200, 실패는 500 과 같이 1~2개의 HTTP response code만 사용하는 경우이다. 심한 경우에는 에러도 HTTP Response code 200으로 정의한후 별도의 에러 메시지를 200 response code와 함께 보내는 경우인데, 이는 REST 디자인 사상에도 어긋남은 물론이고 자기 서술성에도 어긋난다.


오랜만의 블로그 포스팅입니다.

그 동안 예전 REST 관련 글들이 오래되서, 새로운 디자인 가이드와 보안 가이드들을 추가해서 업데이트 하였습니다

많은 공유와 피드백 부탁드립니다.


  • REST API 이해와 설계 - #1 개념 잡기 http://bcho.tistory.com/953
  • REST API 이해와 설계 - #2 디자인 가이드  http://bcho.tistory.com/954
  • REST API 이해와 설계 - #3 보안 가이드  http://bcho.tistory.com/955


저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

마이크로 서비스 아키텍쳐 (MSA의 이해)

조대협(http://bcho.tistory.com)

 

배경


마이크로 서비스 아키텍쳐(이하 MSA)는 근래의 웹기반의 분산 시스템의 디자인에 많이 반영되고 있는 아키텍쳐 스타일로, 특정 사람이 정의한 아키텍쳐가 아니라, 분산 웹 시스템의 구조가 유사한 구조로 설계 되면서, 개념적으로만 존재하던 개념이다.

얼마전 마틴파울러(Martin folwer)가 이에 대한 MSA에 대한 개념을 글로 정리하여, 개념을 정립 시키는데 일조를 하였다.

이 글에서는 대규모 분산 웹시스템의 아키텍쳐 스타일로 주목 받고 있는 MSA에 대한 개념에 대해서 알아보도록 한다.


모노리틱 아키텍쳐(Monolithic Architecture)


마이크로 서비스 아키텍쳐를 이해하려면 먼저 모노리틱 아키텍쳐 스타일에 대해서 이해해야 한다

모노리틱 아키텍쳐 스타일은 기존의 전통적인 웹 시스템 개발 스타일로, 하나의 애플리케이션 내에 모든 로직들이 모두 들어 가 있는 “통짜 구조” 이다.

예를 들어, 온라인 쇼핑몰 애플리케이션이 있을때, 톰캣 서버에서 도는 WAR 파일(웹 애플리케이션 패키징 파일)내에, 사용자 관리,상품,주문 관리 모든 컴포넌트들이 들어 있고 이를 처리하는 UX 로직까지 하나로 포장되서 들어가 있는 구조이다.




각 컴포넌트들은 상호 호출을 함수를 이용한 call-by-reference 구조를 취한다.

전체 애플리케이션을 하나로 처리하기 때문에, 개발툴 등에서 하나의 애플리케이션만 개발하면 되고, 배포 역시 간편하며 테스트도 하나의 애플리케이션만 수행하면 되기 때문에 편리하다.

문제점

그러나 이러한 모노리틱 아키텍쳐 시스템은 대형 시스템 개발시 몇가지 문제점을 갖는다.

모노리틱 구조의 경우 작은 크기의 애플리케이션에서는 용이 하지만, 규모가 큰 애플리케이션에서는 불리한 점이 많다.

크기가 크기 때문에, 빌드 및 배포 시간, 서버의 기동 시간이 오래 걸리며 (서버 기동에만 2시간까지 걸리는 사례도 경험해봤음)

프로젝트를 진행하는 관점에서도, 한두사람의 실수는 전체 시스템의 빌드 실패를 유발하기 때문에, 프로젝트가 커질 수 록, 여러 사람들이 협업 개발하기가 쉽지 않다

또한 시스템 컴포넌트들이 서로 로컬 콜 (call-by-reference)기반으로 타이트하게 연결되어 있기 때문에, 전체 시스템의 구조를 제대로 파악하지 않고 개발을 진행하면, 특정 컴포넌트나 모듈에서의 성능 문제나 장애가 다른 컴포넌트에까지 영향을 주게 되며, 이런 문제를 예방하기 위해서는 개발자가 대략적인 전체 시스템의 구조 등을 이해 해야 하는데, 시스템의 구조가 커질 수 록, 개인이 전체 시스템의 구조와 특성을 이해하는 것은 어려워진다.

특정 컴포넌트를 수정하고자 했을때, 컴포넌트 재 배포시 수정된 컴포넌트만 재 배포 하는 것이 아니라 전체 애플리케이션을 재 컴파일 하여 전체를 다시 통으로 재배포 해야하기 때문에 잦은 배포가 있는 시스템의 경우 불리하며,

컴포넌트 별로, 기능/비기능적 특성에 맞춰서 다른 기술을 도입하고자 할때 유연하지 않다. 예를 들어서, 전체 애플리케이션을 자바로 개발했다고 했을 때, 파일 업로드/다운 로드와 같이 IO 작업이 많은 컴포넌트의 경우 node.js를 사용하는 것이 좋을 수 있으나, 애플리케이션이 자바로 개발되었기 때문에 다른 기술을 집어 넣기가 매우 어렵다.

※ 모노리틱 아키텍쳐가 꼭 나쁘다는 것이 아니다. 규모가 작은 애플리케이션에서는 배포가 용이하고, 규모가 크더라도, call-by-reference call에 의해서 컴포넌트간 호출시 성능에 제약이 덜하며, 운영 관리가 용이하다. 또한 하나의 구조로 되어 있기 때문에, 트렌젝션 관리등이 용이하다는 장점이 있다. 즉 마이크로 서비스 아키텍쳐가 모든 부분에 통용되는 정답은 아니며, 상황과 필요에 따라서 마이크로 서비스 아키텍쳐나 모노리틱 아키텍쳐를 적절하게 선별 선택 또는 변형화 해서 사용할 필요가 있다.


마이크로 서비스 아키텍쳐


마이크로 서비스 아키텍쳐는 대용량 웹서비스가 많아짐에 따라 정의된 아키텍쳐인데, 그 근간은 SOA (Service Oriented Architecture : 서비스 지향 아키텍쳐)에 두고 있다.

SOA는 엔터프라이즈 시스템을 중심으로 고안된 아키텍쳐라면, 마이크로 서비스 아키텍쳐는 SOA 사상에 근간을 두고, 대용량 웹서비스 개발에 맞는 구조로 사상이 경량화 되고, 대규모 개발팀의 조직 구조에 맞도록 변형된 아키텍쳐이다.


아키텍쳐 구조


서비스


마이크로 서비스 아키텍쳐에서는 각 컴포넌트를 서비스라는 개념으로 정의한다. 서비스는 데이타에서 부터 비지니스 로직까지 독립적으로 상호 컴포넌트간의 의존성이 없이 개발된 컴포넌트(이를 버티컬 슬라이싱/Vertical Slicing-수직적 분할이라고 한다.)로 REST API와 같은 표준 인터페이스로 그 기능을 외부로 제공한다.

서비스 경계는 구문 또는 도메인(업무)의 경계를 따른다. 예를 들어 사용자 관리, 상품 관리, 주문 관리와 같은 각 업무 별로 서비스를 나눠서 정의한다. 사용자/상품 관리 처럼 여러개의 업무를 동시에 하나의 서비스로 섞어서 정의하지 않는다.

REST API에서 /users, /products와 같이 주요 URI도 하나의 서비스 정의의 범위로 좋은 예가 된다.  


마이크로 서비스 아키텍쳐의 구조


마이크로 서비스 아키텍쳐의 구조는 다음과 같은 모양을 따른다.

각 컴포넌트는 서비스라는 형태로 구현되고 API를 이용하여 타 서비스와 통신을 한다.



배포 구조관점에서도 각 서비스는 독립된 서버로 타 컴포넌트와의 의존성이 없이 독립적으로 배포 된다.

예를 들어 사용자 관리 서비스는 독립적인 war파일로 개발되어, 독립된 톰캣 인스턴스에 배치된다. 확장을 위해서 서비스가 배치된 톰캣 인스턴스는 횡적으로 스케일 (인스턴스 수를 더함으로써)이 가능하고, 앞단에 로드 밸런서를 배치하여 서비스간의 로드를 분산 시킨다.



가장 큰 특징이, 애플리케이션 로직을 분리해서 여러개의 애플리케이션으로 나눠서 서비스화하고, 각 서비스별로 톰캣을 분산 배치한 것이 핵심이다.


데이타 분리


데이타 저장관점에서는 중앙 집중화된 하나의 통 데이타 베이스를 사용하는 것이 아니라 서비스 별로 별도의 데이타 베이스를 사용한다.       보통 모노리틱 서비스의 경우에는 하나의 통 데이타 베이스 (보통 RDBMS를 사용) 하는 경우가 일반적이지만, 마이크로 서비스 아키텍쳐의 경우, 서비스가 API에서 부터 데이타 베이스까지 분리되는 수직 분할 원칙 (Vertical Slicing)에 따라서 독립된 데이타 베이스를 갖는다.



데이타 베이스의 종류 자체를 다른 데이타 베이스를 사용할 수 도 있지만, 같은 데이타 베이스를 사용하더라도 db를 나누는 방법을 사용한다.

이 경우, 다른 서비스 컴포넌트에 대한 의존성이 없이 서비스를 독립적으로 개발 및 배포/운영할 수 있다는 장점을 가지고 있으나, 다른 컴포넌트의 데이타를 API 통신을 통해서만 가지고 와야 하기 때문에 성능상 문제를 야기할 수 있고, 또한 이 기종 데이타 베이스간의 트렌젝션을 묶을 수 없는 문제점을 가지고 있다. (이러한 데이타 분산에 의한 트렌젝션 문제는 SOA 때부터 있어 왔다.) 데이타 분산으로 인한 트렌젝션 문제는 뒤에서 조금 더 자세하게 설명하도록 한다.


API Gateway


마이크로 서비스 아키텍쳐 설계에 있어서 많이 언급되는 컴포넌트 중의 하나가 api gateway 라는 컴포넌트 이다. api gateway는 마치 프록시 서버 처럼 api들 앞에서 모든 api에 대한 end point를 통합하고, 몇가지 추가적인 기능을 제공하는 미들웨어로, SOA의 ESB (Enterprise Service Bus)의 경량화 버전이다. Apigateway가 마이크로 서비스 아키텍쳐 상에서 수행하는 주요 기능을 살펴보면 다음과 같다.

EndPoint 통합 및 토폴로지 정리

마이크로 서비스 아키텍쳐의 문제점 중의 하나는 각 서비스가 다른 서버에 분리 배포 되기 때문에, API의 End point 즉, 서버의 URL이 각기 다르다는 것이다.

사용자 컴포넌트는 http://user.server.com, 상품 컴포넌트는 http://product.server.com 과 같은 분리된 URL을 사용하는데, 이는 API 사용자 경험 관점에서도 사용하기가 불편하다. 특히 마이크로 서비스 아키텍쳐는 컴포넌트를 되도록이면 업무 단위로 잘게 짜르는 fine grained (작은 덩어리)의 서비스를 지향하기 때문에, 컴포넌트의 URL 수는 더 많이 늘어 날 수 있다.

API를 사용하는 클라이언트에서 서버간의 통신이나, 서버간의 API 통신의 경우 p2p(Point to Point)형태로 토폴로지가 복잡해지고 거미줄 모양의 서비스 컴포넌트간의 호출 구조는 향후 관리의 문제를 일으킬 수 있다. 하나의 end point를 변경하였을때, 제대로 관리가 되지 않을 경우가 있다.




<그림. P2P 형태의 토폴리지>

이러한 토폴로지상의 문제점을 해결하기 위해서 중앙에 서비스 버스와 같은 역할을 하는 채널을 배치 시켜서, 전체 토폴로지를 p2p에서 hub & spoke 방식으로 변환 시켜서, 서비스간 호출을 단순화 시킬 수 있다.




<그림. 버스 기반의 Hub & Spoke 토폴리지>

Orchestration


다른 기능으로는 orchestration 이라는 개념이 있다. 기존 open api의 mash up과 같은 개념으로, 여러개의 서비스를 묶어서 하나의 새로운 서비스를 만드는 개념이다.

예를 들어, 포인트 적립과, 물품 구매라는 서비스가 있을때, 이 두개의 서비스를 묶어서 “물품 구입시 포인트 적립”이라는 새로운 서비스를 만들어 낼 수 있다. 이러한 orchestration 기능은, api gateway를 통해서 구현될 수 있다.

이는 마이크로 서비스 아키텍쳐가 서비스 자체가 fine grained 형태로 잘게 쪼게졌기 때문에 가능한 일인데, 사실 orchestration을 api gateway 계층에서 하는 것은 gateway 입장에서 부담이 되는 일이다. 실제로 과거의 SOA 시절에 많은 ESB(Enterprise Service Bus) 프로젝트가 실패한 원인 중의 하나가 과도한 orchestration 로직을 넣어서 전체적인 성능 문제를 유발한 경우가 많았다. 그래서 orchestration 서비스의 활용은 마이크로 서비스 아키텍쳐에 대한 높은 이해와 api gateway 자체에 대한 높은 수준의 기술적인 이해를 필요로 한다.

실제로 넷플릭스의 경우 마이크로 서비스 아키텍쳐를 사용하면서, 여러개의 서비스들을 gateway 계층을 통해서 orchestration 하는 모델을 사용하고 있다. 


공통 기능 처리 (Cross cutting function handling)


또한 API에 대한 인증 (Authentication)이나, Logging과 같은 공통 기능에 대해서 서비스 컴포넌트 별로 중복 개발해야 하는 비효율성을 유발할 수 있다. api gateway에서 이러한 공통 기능을 처리하기 되면, api 자체는 비지니스 로직에만 집중을 하여 개발에 있어서의 중복등을 방지 할 수 있다.

mediation

이외에도 XML이나 네이티브 메세지 포맷을 json등으로 상호 변환해주는 message transformation 기능이나, 프로토콜을 변환하는 기능, 서비스간의 메세지를 라우팅해주는 기능등 여러가지 고급 mediation 기능을 제공을 하지만, api gateway를 최대한 가볍게 가져간다는 설계 원칙 아래서 가급 적이면 고급적인 mediation 기능을 사용할 때는 높은 수준의 설계와 기술적인 노하우를 동반해야 한다.


※ ESB vs APIgateway

SOA 프로젝트의 실패중의 하나가 ESB로 꼽히는 경우가 많은데, 이는 ESB를 Proxy나 Gateway처럼 가벼운 연산만이 아니라, 여러개의 서비스를 묶는 로직에  무겁게 사용했기 때문이다. (사용하면 안된다는 것이 아니라 잘 사용해야 한다는 것이다.) ESB는 메세지를 내부적으로 XML로 변환하여 처리하는데, XML 처리는 생각하는것 보다 파싱에 대한 오버헤드가 매우 크다.  또한 ESB의 고유적인 버스나 게이트웨이로써의 특성이 아니라 타 시스템을 통합 하기 위한 EAI적인 역할을 ESB를 이용해서 구현함으로써 많은 실패 사례를 만들어 내었다. 그래서 종종 ESB는 Enterprise Service Bus가 아니라 EnterpriSe nightmare Bus로 불리기도 한다. J

이러한 개념적인 문제를 해결하기 위해서 나온 제품군이 apigateway라는 미들웨어 제품군들인데, ESB와 기본적인 특성은 유사하나 기능을 낮추고 EAI의 통합 기능을 제거하고 API 처리에만 집중한 제품군들로, 클라우드상에서 작동하는 PaaS (Platform As A Service)형태의 서비스로는 apigee.com이나 3scale.com 등이 있고, 설치형 제품으로는 상용 제품인 CA社의 Layer7이나 오픈소스인 Apache Service Mix, MuleSoft의 ESB 제품 그리고 WSO2의 API Platform 등이 있다.

Apigateway 부분에 마이크로 서비스 아키텍쳐의 다른 부분 보다 많은 부분을 할애한 이유는, 컴포넌트를 서비스화 하는 부분에 까지는 대부분 큰 문제가 없이 적응을 하지만 apigateway의 도입 부분의 경우, 내부적인 많은 잡음이 날 수 있고, 또한 도입을 했더라도 잘못된 설계나 구현으로 인해서 실패 가능성이 비교적 높은 모듈이기 때문이다. 마이크로 서비스 아키텍쳐의 핵심 컴포넌트이기도 하지만, 도입을 위해서는 팀의 상당 수준의 높은 기술적인 이해와 개발 능력을 필요로 한다.


배포


마이크로 서비스 아키텍쳐의 가장 큰 장점 중의 하나가 유연한 배포 모델이다. 각 서비스가 다른 서비스와 물리적으로 완벽하게 분리되기 때문에 변경이 있는 서비스 부분만 부분 배포가 가능하다 예를 들어서, 사용자 관리 서비스 로직이 변경되었을 때, 모노리틱 아키텍쳐의 경우에는 전체 시스템을 재 배포해야 하지만, 마이크로 서비스 아키텍쳐의 경우에는 변경이 있는 사용자 관리 서비스 부분만 재 배포 하면 되기 때문에, 빠르고 전체 시스템의 영향도를 최소화한 수준에서 배포를 진행할 수 있다.


확장성


서비스 별로 독립된 배포 구조는 확장성에 있어서도 많은 장점을 가지고 오는데, 부하가 많은 특정 서비스에 대해서만 확장이 가능하여 조금 더 유연한 확장 모델을 가질 수 있다. 모노리틱 아키텍쳐의 경우에는 특정 서비스의 부하가 많아서 성능 확장이 필요할때, 전체 서버의 수를 늘리거나 각 서버의 CPU 수를 늘려줘야 하지만, 마이크로 서비스 아키텍쳐의 경우에는 부하를 많이 받는 서비스 컴포넌트 만 확장을 해주면 된다.


Conway’s Law (컨웨이의 법칙)


마이크로 서비스 아키텍쳐의 흥미로운 점중의 하나는 아키텍쳐 스타일의 조직 구조나 팀 운영 방식에 영향을 준다는 것인데, 마이크로 서비스 아키텍쳐는 컨웨이의 법칙에 근간을 두고 있다.

컨웨이의 법칙은 “소프트웨어의 구조는 그 소프트웨어를 만드는 조직의 구조와 일치한다”는 이론이다.

현대의 소프트웨어 개발은 주로 애자일 방법론을 기반으로 하는 경우가 많다. 애자일 팀의 구조는 2 피자팀(한팀의 인원수는 피자 두판을 먹을 수 있는 정도의 인원 수가 적절하다.)의 모델을 많이 따르는데, 한 팀이 7~10명정도로 이루어지고, 이 인원 수가 넘어가면 팀을 분리하는 모델이다.

마이크로 서비스 아키텍쳐는 각 컴포넌트를 팀에 배치해서 책임지고 개발하는 것을 근간으로 하며, 팀간의 의존성을 제거해서 각 팀이 컴포넌트 개발을 독립적으로할 수 있는 구조로 잡혀있다.


마이크로 서비스 아키텍쳐의 문제점


분홍빛 미래 처럼 보이는 마이크로 서비스 아키텍쳐는 아무런 문제가 없는 것일까? 당연히 여러가지 장점을 제공하는 대신에 그만한 단점을 가지고 있다.


성능


모노리틱 아키텍쳐는 하나의 프로세스 내에서 서비스간의 호출을 call-by-reference 모델을 이용한다. 반면 마이크로 서비스 아키텍쳐는 서비스간의 호출을 API 통신을 이용하기 때문에 값을 json이나 xml에서 프로그래밍에서 사용하는 데이타 모델 (java object등)으로 변환하는 marsharing 오버헤드가 발생하고 호출을 위해서 이 메세지들이 네트워크를 통해서 전송되기 때문에 그만한 시간이 더 추가로 소요된다.


메모리


마이크로 서비스 아키텍쳐는 각 서비스를 독립된 서버에 분할 배치하기 때문에, 중복되는 모듈에 대해서 그만큼 메모리 사용량이 늘어난다.

예를 들어 하나의 톰캣 인스턴스에서 사용자 관리와 상품 관리를 배포하여 운용할 경우, 하나의 톰캣을 운영하는데 드는 메모리와, 스프링 프레임웍과 같은 라이브러리를 사용하는데 소요되는 메모리 그리고 각각의 서비스 애플리케이션이 기동하는 메모리가 필요하다.

그러나 마이크로 서비스 아키텍쳐로 서비스를 배포할 경우 사용자 관리 서비스 배포와 상품 관리 서비스 배포를 위한 각각의 별도의 톰캣 인스턴스를 운용해야 하고, 스프링 프레임웍과 같은 공통 라이브러리도 각각 필요하기 때문에, 배포하고자 하는 서비스의 수 만큼 중복된 양의 메모리가 필요하게 된다.

위의 두 문제는 반드시 발생하는 문제점이기는 하나 현대의 인프라 환경에서는 크게 문제는 되지 않는다. (기존에 비해 상대적으로). 현대의 컴퓨팅 파워 자체가 워낙 발달하였고, 네트워크 인프라 역시 기존에 1G등에 비해서 내부 네트워크는 10G를 사용하는 등, 많은 성능상 발전이 있었다. 또한 메모리 역시 비용이 많이 낮춰지고 32bit에서 64bit로 OS들이 바뀌면서, 가용 메모리 용량이 크게 늘어나서 큰 문제는 되지 않는다. 또한 성능상의 문제는 비동기 패턴이나 캐슁등을 이용해서 해결할 수 있는 다른 방안이 많기 때문에 이 자체는 큰 문제가 되지 않는다.

그보다 더 문제점은 아래에서 언급하는 내용들인데,


테스팅이 더 어려움


마이크로 서비스 아키텍쳐의 경우 서비스들이 각각 분리가 되어 있고, 다른 서비스에 대한 종속성을 가지고 있기 때문에, 특정 사용자 시나리오나 기능을 테스트하고자 할 경우 여러 서비스에 걸쳐서 테스트를 진행해야 하기 때문에 테스트 환경 구축이나 문제 발생시 분리된 여러개의 시스템을 동시에 봐야 하기 때문에 테스팅의 복잡도가 올라간다.

운영 관점의 문제


운영 관점에서는 서비스 별로 서로 다른 기술을 사용할 수 있으며, 시스템이 아주 잘게 서비스 단위로 쪼게 지기 때문에 운영을 해야할 대상 시스템의 개수가 늘어나고, 필요한 기술의 수도 늘어나게 된다.


서비스간 트렌젝션 처리


구현상의 가장 어려운 점중의 하나가, 트렌젝션 처리이다. 모노리틱 아키텍쳐에서는 RDBMS를 사용하면서 하나의 애플리케이션 내에서 트렌젝션이 문제가 있으면 쉽게 데이타베이스의 기능을 이용해서 rollback을 할 수 있었다. 여러개의 데이타베이스를 사용하더라도, 분산 트렌젝션을 지원하는 트렌젝션 코디네이터 (JTS – Java Transaction Service)등을 이용해서 쉽게 구현이 가능했는데, API 기반의 여러 서비스를 하나의 트렌젝션으로 묶는 것은 불가능 하다.

쉽게 예를 들어서 설명을 하면, 계좌에서 돈을 빼는 서비스와, 계좌에 돈을 넣는 서비스가 있다고 하자. 이 둘은 API를 expose했을 때, 계좌에서 돈을 뺀 후, 계좌에 돈을 넣기 전에 시스템이 장애가 나면, 뺀 돈은 없어지게 된다. 모노리틱 아키텍쳐를 사용했을 경우에는 이러한 문제를 트렌젝션 레벨에서 롤백으로 쉽게 해결할 수 있지만 API 기반의 마이크로 서비스 아키텍쳐에서는 거의불가능하다.

사실 이 문제는 마이크로 서비스 아키텍쳐 이전에도, 서비스와 API를 기본 컨셉으로 하는 SOA에도 있었던 문제이다.

이러한 문제를 해결하기 위해서 몇가지 방안이 있는데,

그 첫번째 방법으로는 아예 애플리케이션 디자인 단계에서 여러개의 API를 하나의 트렌젝션으로 묶는 분산 트렌젝션 시나리오 자체를 없애는 방안이다. 분산 트렌젝션이 아주 꼭 필요할 경우에는 차라리 모노리틱 아키텍쳐로 접근하는 것이 맞는 방법이다. 앞서도 언급했듯이 마이크로 서비스 아키텍쳐의 경우, 금융이나 제조와 같이 트렌젝션 보장이 중요한 엔터프라이즈 시스템보다는 대규모 처리가 필요한 B2C 형 서비스에 적합하기 때문에, 아키텍쳐 스타일 자체가 트렌젝션을 중요시 하는 시나리오에서는 적절하지 않다.

그럼에도 불구하고, 트렌젝션 처리가 필요할 경우, 트렌젝션 실패시 이를 애플리케이션 적으로 처리해 줘야 하는 데, 이를 보상 트렌젝션(compensation transaction)이라고 한다. 앞의 계좌 이체 시나리오에서 돈을 뺀 후, 다른 계좌에 넣다가 에러가 났을 경우에, 명시적으로, 돈을 원래 계좌로 돌려주는 에러 처리 로직을 구현해야 한다.

마지막 방법으로 복합 서비스 (composite service)라는 것을 만들어서 활용하는 방법이 있는데, 복합 서비스란 트렌젝션을 묶어야 하는 두개의 시스템을 트렌젝션을 지원하는 네이티브 프로토콜을 이용해서 구현한 다음 이를 API로 노출 시키는 방법이다.

두개의 데이타 베이스는 XA(eXtended Architecture)와 같은 분산 트렌젝션 프로토콜을 써서 서비스를 개발하거나 또는 SAP나 Oracle 아답터와 같이 트렌젝션을 지원하는 네이티브 아답터를 사용하는 방법이다. 기존에 SOA에서 많이 했던 접근방법이기는 하나, 복합 서비스를 사용할 경우, 복합서비스가 서로 다른 두개의 서비스에 걸쳐서 tightly coupled하게 존재하기 때문에, 마이크로 서비스 아키텍쳐의 isolation(상호 독립적)인 사상에 위배되고 서비스 변경시에 이 부분을  항상 고려해야 하기 때문에 아키텍쳐상의 유연성이 훼손되기 때문에 꼭 필요하지 않은 경우라면 사용하지 않는 것이 좋다.


거버넌스 모델


거버넌스 (governance)란, 시스템을 개발하는 조직의 구조나 프로세스를 정의한 것으로, 일반적으로 중앙 집중화된 조직에서 표준 프로세스와 가이드를 기반으로 전체 팀을 운용하는 모델을 사용한다. 이를 중앙 집중형 거버넌스 모델 (Centralized governance model) 이라고 하는데, 이 경우 전체 시스템이 동일한 프로세스와 기술을 가지고 개발이 되기 때문에, 유지 보수가 용이하고 팀간의 인원 교체등이 편리하다는 장점을 가지고 있다. 전통적인 개발 모델들은 이러한 중앙 집중현 거버넌스 모델을 사용한다.

그러나 현대의 웹 개발의 경우, 오픈 소스 발달로 선택 가능한 기술들이 많고 각 요구 사항에 따라서 효율성 측면등을 고려할때 각각 최적화된 기술을 사용하는 것이 좋은 경우가 있다.

예를 들어, 전체 표준을 자바+RDBMS로 정했다 하더라도, 파일 업로드 다운로드 관련 컴포넌트는 io 성능과 많은 동시접속자를 처리할 수 있는 node.js가 유리하다던지, 데이타의 포맷은 복잡하지만, 복잡한 쿼리가 없을 경우에는 json document 기반의 mongodb와 같은 NoSQL등이 유리한 사례 등이 된다. 이러한 기술을 도입하기 위해서는 중앙 집중형 거버넌스 모델에서는 모든 개발팀을 교육 시키고, 운영 또한 준비를 해야하기 때문에 기술에 대한 적용 민첩성이 떨어지게 된다.

이러한 문제점을 해결 하는 거버넌스 모델이 분산형 거버넌스 모델 (De-Centralized governance model)인데, 이는 각 팀에 독립적인 프로세스와 기술 선택 권한을 주는 모델로, 각 서비스가 표준 API로 기능을 바깥으로 노출할 뿐 내부적인 구현 기술 구조는 추상화되어 가능한 사상이다

분산형 거버넌스 모델을 수행하려면, 이에 맞는 팀구조가 필요한데, 이 팀 구조는 다음과 같은 몇가지 특징을 가지고 있어야 한다.


Cross functional team


기존의 팀 모델은 역할별로 나뉘어진 모델로 팀을 구분한다. 기획팀,UX팀,개발팀,인프라 운영팀 등 공통적인 특성으로 나누는 것이 기존의 팀 모델이다. 이런 팀 모델은 리소스의 운영에 유연성을 부여한다. 개발 인력이 모자르면 팀 내에서 개발인원을 다른 프로젝트에서 충당하는 등의 리소스 운영이 가능하지만 반대로 팀간의 커뮤니케이션이 팀이라는 경계에 막혀서 원할하지 않고 협의에 걸리는 시간으로 인해서 팀의 운영 속도가 떨어진다.

cross function team 모델은 하나의 팀에 UX, 개발팀,인프라팀등 소프트웨어 시스템을 개발하는데 필요한 모든 역할을 하나의 팀에 구성하고 움직이는 모델로, 각 서비스 개발팀이 cross functional team이 되서 움직인다.



<그림 역할 중심의 개발팀 과 cross functional team에 대한 모델 비교>

이 경우 서비스 기획에서 부터 설계,개발,운영이 가능해지고 다른 팀에 대한 의존성이 없어짐으로써 빠른 서비스 개발이 가능해진다.


You build,You run-Devops


기술에 대한 독립성을 가지려면 구현 뿐만 아니라 운영 또한 직접할 수 있는 능력을 가져야 한다. 그래서 개발과 운영을 하나의 조직에 합쳐 놓는 구조를 Devops라고 한다.

Devops는 Development와 Operation을 합성한 단어로, 개발팀과 운영팀이 다른 팀으로 분리되어 있어서 발생하는 의사 소통의 문제점을 해결하고, 개발이 운영을 고려하고, 운영에서 발생하는 여러 문제점과 고객으로부터의 피드백을 빠르게 수용하여 서비스 개선에 반영하는 개발 모델이다.

이런 모델이 가능해진 이유는 운영팀만의 고유 영역이었던 인프라에 대한 핸들링이 클라우드의 도입으로 인해서 쉬워져서, 애플리케이션 개발자도 웹사이트를 통해서 손쉽게 디스크나 네트워크 설정, 서버 설정등이 가능해졌기 때문이다.

Devops는 대단히 좋은 모델이고 아마존이나 넷플릭스등이 적용하고 있는 모델이기는 하나, 이 역시 대단히 높은 수준의 팀의 성숙도가 필요하다.

개발자가 애플리케이션 개발 뿐만 아니라, 인프라에 대한 설계 및 운영까지 담당해야 하기 때문에 기존의 애플리케이션만 개발하던 입장에서는 대단히 부담이 되는 일이다.

좋은 모델이기는 하지만 충분히 준비가 되지 않은 상태에서 넘어가게 되면은 운영상의 많은 장애를 유발하기 때문에, 팀의 성숙도에 따라서 심각하게 고민해보고 적용을 해보기를 권장한다.


Project vs product


분산형 거버넌스 모델에서 중요한 점 중의 하나는 연속성이다. 거버넌스를 분산 시켜버렸기 때문에 팀별로 다른 형태의 표준과 기술 프로세스를 통해서 개발을 하기 때문에, 새로운 인원이 들어오거나 다른 팀으로 인원이 이동하였을 경우 팀에 맞는 형태의 재 교육이 필요하고 그간의 축적된 노하우가 100% 활용되지 못할 수 가 있기 때문에 가능하면 팀원들은 계속해서 해당 서비스 개발에 집중할 필요가 있다.

이를 위해서는 프로젝트의 컨셉 변화가 필요한데, 일반적으로 프로젝트란 일정 기간에 정해진 요구 사항을 구현하는데 목표가 잡혀 있으며, 프로젝트가 끝나면 인원은 다시 흩어져서 새로운 프로젝트에 투입 되는 형태로 역할 중심의 프로젝트팀 운용 방식에는 적절하다.

그러나 분산형 거버넌스 모델에서는 팀원의 영속성을 보장해줘야 하는데 이를 위해서는 프로젝트가 아니라 프로덕트(즉 상품)형태의 개념으로 개발 모델이 바뀌어야 한다. 팀은 상품에 대한 책임을 지고, 요구사항 정의 발굴에서 부터 개발 그리고 운영까지 책임을 지며, 계속해서 상품을 개선해 나가는 활동을 지속해야 한다. 이를 상품 중심의 개발팀 모델이라고 한다.


Self-organized team


이러한 요건등이 만족 되면, 팀은 독립적으로 서비스 개발을 할 수 있는 형태가 된다. 스스로 기획하고 개발하며 운영을 하며 스스로 서비스를 발전 시키는 하나의 회사와 같은 개념이 되는 것이다.

이렇게 독립적인 수행 능력을 가지고 있는 팀 모델을 self-organized team 모델이라고 한다.


Alignment 


이러한 분산형 거버넌스 모델을 수행하기 전에 반드시 주의해야 할 점이 있는데, alignment 라는 개념이다 alignment는 각 팀간의 커뮤니케이션 방법이나 프로세스등 최소한 표준과 기술적인 수준을 맞추는 과정인데, 쉽게 이야기해서 개발 경험이 전혀 없는 대학을 갓졸업한 사람들로 팀을 만들고, 기존의 팀들은 4~5년차 경력 인원들만으로 팀을 만들어서 전체 팀을 운용하면 어떻게 될까?

마이크로 서비스 아키텍쳐는 각 서비스들이 상호 의존성을 가지고 있기 때문에, 개발경험이 없는 팀이 전체 팀의 개발 속도를 못 따라오고, 또한 품질등에도 심각한 문제가 생긴다. 그래서 어느 일정 수준 이상으로 팀의 능력을 끌어 올려주고, 전체 팀에서 사용하는 최소한의 공통 프로세스등에 대해서는 서로 맞추어 놓을 필요가 있다. 이것이 바로 alignment 의 개념이다.

분산형 거버넌스 모델을 잘못 해석하거나 악용이 되면 팀에게 무조건적인 자치권을 부여하는 것으로 오역되서.. “분산형 거버넌스가 대세랍니다. 우리팀은 우리가 알아서 할테니 신경 끄세요.” 라는 형태의 잘못된 요청으로 전체 팀과 전체 시스템 아키텍쳐를 망쳐 버릴 수 있다.

제대로 된 해석은 “우리는 전체 팀이 나가야 할 방향과 비지니스 밸류에 대해서 이해를 하고 있습니다. 또한 이미 팀간의 커뮤니케이션이나 전체 시스템 구조에 대한 이해를 하고 있습니다. 이를 바탕으로 조금 더 빠른 개발과 효율성을 위한 모든 기능(역할)을 가지고 있는 팀을 운영하고자 합니다.” 가 제대로 된 해석이라고 볼 수 있겠다.


Evolutionary Model (진화형 모델)


지금 까지 간략하게나마 마이크로 서비스 아키텍쳐에 대해서 알아보았다.

마이크로 서비스 아키텍쳐는 서비스의 재사용성, 유연한 아키텍쳐 구조, 대용량 웹 서비스를 지원할 수 있는 구조등으로 많은 장점을 가지고 있지만, 운영하는 팀에 대해서 높은 성숙도를 필요로 한다. 그래서 충분한 능력을 가지지 못한 팀이 마이크로 서비스 아키텍쳐로 시스템을 개발할 경우에는 많은 시행 착오를 겪을 수 있다.

마이크로 서비스 아키텍쳐를 적용할때는 처음 부터 시스템을 마이크로 서비스 아키텍쳐 형태로 설계해서 구현할 수 도 있겠지만, 모노리틱 시스템에서 부터 시작하여, 비지니스 운용시 오는 문제점을 기반으로 점차적으로 마이크로 서비스 아키텍쳐 형태로 진화 시켜 나가는 방안도 좋은 모델이 된다. 비지니스와 고객으로 부터 오는 피드백을 점차적으로 반영 시켜나가면서 동시에 팀의 성숙도를 올려가면서 아키텍쳐 스타일을 변화 시켜가는 모델로, 많은 기업들이 이런 접근 방법을 사용했다. 트위터의 경우에도, 모노리틱 아키텍쳐에서 시작해서 팀의 구조를 점차적으로 변환 시켜 가면서 시스템의 구조 역시 마이크로 서비스 아키텍쳐 형태로 전환을 하였고, 커머스 시장에서 유명한 이베이 같은 경우에도, 그 시대의 기술적 특성을 반영하면서 비지니스의 요구 사항을 적절히 반영 시켜가면서 시스템을 변화 시켜 나가는 진화형 모델로 아키텍쳐를 전환 하였다.


SOA와 비교


마이크로 서비스 아키텍쳐는 종종 SOA와 비교 되며, SOA는 틀리고 마이크로 서비스 아키텍쳐는맞다 흑백 논리 싸움이 벌어지고는 하는데,

SOA와 마이크로 서비스 아키텍쳐는 사실상 다른 개념이 아니라 SOA가 마이크로 서비스 아키텍쳐에 대한 조상 또는 큰 수퍼셋의 개념이다. 흔히 SOA가 잘못되었다고 이야기 하는 이유는 SOA를 아키텍쳐 사상으로 보는 것이 아니라 SOAP 기반의 웹서비스나, Enterprise Service Bus와 같은 특정 제품을 SOA로 인식하기 때문이다. SOA는 말 그대로 설계에 대한 사상이지 특정 기술을 바탕으로 한 구현 아키텍쳐가 아니다.

큰 의미에서 보자면 마이크로 서비스 아키텍쳐의 서비스 역시, SOA에서 정의한 서비스 중에서 fine grained 서비스로 정의되는 하나의 종류이며, api gateway역시 SOA 에서 정의한 ESB의 하나의 구현방식에 불과 하다.

만약에 기회가 된다면 마이크로 서비스 아키텍쳐에 대해 제대로 이해하기 위해서 SOA 를 반드시 공부해보기를 바란다.


결론


마이크로 서비스 아키텍쳐는 대용량 웹시스템에 맞춰 개발된 API 기반의 아키텍쳐 스타일이다. 대규모 웹서비스를 하는 많은 기업들이 이와 유사한 아키텍쳐 설계를 가지고 있지만, 마이크로 서비스 아키텍쳐가 무조건 정답은 아니다. 하나의 설계에 대한 레퍼런스 모델이고, 각 업무나 비지니스에 대한 특성 그리고 팀에 대한 성숙도와 가지고 있는 시간과 돈과 같은 자원에 따라서 적절한 아키텍쳐 스타일이 선택되어야 하며, 또한 아키텍쳐는 처음 부터 완벽한 그림을 그리기 보다는 상황에 맞게 점진적으로 진화 시켜 나가는 모델이 바람직하다.

 특히 근래의 아키텍쳐 모델은 시스템에 대한 설계 사상 뿐만 아니라 개발 조직의 구조나 프로젝트 관리 방법론에 까지 영향을 미치기 때문에 단순히 기술적인 관점에서가 아니라 조금 더 거시적인 관점에서 고려를 해볼 필요가 있다.

 

참고 자료

Ebay 아키텍쳐 : http://www.addsimplicity.com/downloads/eBaySDForum2006-11-29.pdf

Netflix 아키텍쳐 : http://techblog.netflix.com/2013/01/optimizing-netflix-api.html

infoQ Microservice Architecture : http://www.infoq.com/articles/microservices-intro

MicroService 개념 http://microservices.io/patterns/microservices.html

Martin folwer : http://martinfowler.com/articles/microservices.html

Dzone microservice architecture : http://java.dzone.com/articles/microservice-architecture

Thought works의 PPT : http://www.infoq.com/presentations/Micro-Services

node.js로 apigateway 만들기 : 정리 잘되어 있음. http://plainoldobjects.com/presentations/nodejs-the-good-parts-a-skeptics-view/

저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

빠르게 훝어보는 node.js

#15  - Passport를 이용한 OAuth 2.0 API 인증 (Facebook 1/2)

조대협 (http://bcho.tistory.com)



REST 기반의 OPEN API 인증을 고민하다가 보니, 가장 많이 쓰이는게, OAuth 2.0이라서, OAuth 2.0을 보다보니, 도저히 이해가 안되겠다 싶어서, 간단하게 직접 구현해보기로 했다. OAuth 서버를 구현하기전에 먼저 테스트 클라이언트가 필요했기 때문에, node.js + passport 를 이용해서 facebook API를 호출하는 간단한 웹 사이트를 만들어보기로 했다.

Facebook API는 기본적으로 OAuth 2.0을 사용하고, Passport 모듈에서 잘 추상화된 라이브러리를 제공하기 때문에 쉽게 구축할 수 있으리라 생각했다. 실제로 구축해보니, 간단한 예제를 만드는데 약 1시간 30분 정도가 소요되었다.

자아 그러면 이제부터 Passport node.js를 이용해서 간단한 Facebook API를 호출하는 예제를 만들어보도록 하자.

OAuth 2.0

먼저 OAuth 2.0에 대한 개념을 알아보도록 하자. OAuth 2.0, 서비스에 대한 Authentication (인증) Authorization(권한 인가)에 대한 포괄적인 프레임웍이다. 특히 API 인증에 유용하게 사용될 수 있는데, 사용하는 애플리케이션의 타입(,모바일,자바스크립트)에 따라서 다양한 인증 메카니즘을 제공한다. 이를 grant type이라고 하는데, 대표적으로 4가지 형태의 grant type을 제공한다.

Ÿ   Authorization Code for webserver

Ÿ   Implicit for browser-based or mobile apps

Ÿ   Resource owner password credential for logging in with a username and password

Ÿ   Client credential for application access

다른 grant type에 대해서는 차차 소개하기로 하고, 오늘은 authorization code 방식의 grant type에 대해서 알아보도록 하자.

※ 다른 grant type에 대해서는 http://aaronparecki.com/articles/2012/07/29/1/oauth2-simplified#web-server-apps 에 잘 정리가 되어 있다.

지금 구축하려고 하는 것은 facebook API를 내 서비스 웹서버에서 호출하는 시나리오이다.

Facebook API를 호출하기 위해서는 node.js 애플리케이션을 facebook 에 등록해야 한다. 이 등록은 facebook developer portal을 통해서 진행할 수 있는데, 아래 그림과 같이



1.  포탈에 node.js 사이트 관련 정보 (서비스 URL, CallBack URL, 서비스 명 등)을 입력하면,

2.  포탈에서 client_id client_secret을 발급해준다. client_id는 서버에서 발급되는 node.js 만든 서비스에 대한 id 라고 생각하면 되고, client_secret은 그 id에 대한 일종의 인증용 패스워드이다. (절대로 외부에 노출되면 안되는)

3.  이렇게 받은 client_id client_secret node.js 애플리케이션 내에 저장한다.

이렇게 해서 node.js 애플리케이션이 만들어졌으면 호출을 해보자. 호출은 다음과 같은 순서로 이루어 진다. 잠깐 용어를 짚고 넘어가면 OAuth 2.0에서는 Facebook 과 같이 Protected resource (API)를 제공하는 서버를 Resource Server 라고 하고, node.js와 같이 Protected resource (API)에 대한 접근을 요청하는 대상을 Client 라고 정의 한다. Client Resource Server에 접근할때, 사용자의 신분 (id,password)를 기반으로 하여, Resource Server에 접근하는데, 이러한 사용자를 User 라고 정의한다. 그러면 다음과 같은 흐름을 보자.



1.  먼저 사용자는 Web Browser에서, node.js 서버로 서비스를 요청한다.

2.  Node.js 서버는 사용자가 로그인이 되어 있지 않은 상태라면, Facebook log in URL에 대한 rediretion URL을 브라우져에게 보낸다. 이 때, node.js 서버임을 식별하기 위해서 client_id와 몇 가지 추가적인 정보를 보내는데, 그 내용은 다음과 같다.

Ÿ   client_id : 사용자를 통해서 인증을 요청하는 서비스가 앞에서 등록한 node.js 애플리케이션임을 알려준다.

Ÿ   redirect_url : 페이스북 인증서버에서 인증이 끝난후에, 인증 결과를 받을 node.js  애플리케이션으 HTTP URL을 정의한다.

Ÿ   scope : 페이스북에 접근을 요청한 리소스 목록을 정의한다. 예를 들어, 글쓰기, 읽기, 사진 올리기, 연락처 공유등과 같이 리소스에 대한 범주를 정의한다. Oauth 2.0의 특징중의 하나가 단순히 사용자 인증(Authentication)만을 하는 것이 아니라, 사용자가 소유한 리소스중, 접근을 허가 하는 권한 제어(Authorization) 기능을 함께 제공한다는 것이다.

https://oauth2 authorizationserver.com/auth?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos

실제 facebook redirection URL

https://www.facebook.com/login.php?skip_api_login=1&api_key=253044994897796&signed_next=1&next=https://www.facebook.com/v2.0/dialog/oauth?redirect_uri=http://localhost:8080/auth/facebook/callback&scope=read_stream&response_type=code&client_id=253044994897796&ret=login&cancel_uri=http://localhost:8080/auth/facebook/callback?error=access_denied&error_code=200&error_description=Permissions error&error_reason=user_denied#_=_&display=page

3.  Browser Redirect URL을 받아서,페이스북의 인증 서버 URL Redirect를 한다.

4.  페이스북 인증 서버는 log in page를 사용자에게 보낸다.



5.  사용자는 사용자 ID PASSWORD를 입력한다.

인증이 되고 나면, scope에 의해서 리소스에 대한 접근 요청을 허가할 것인지를 물어보는데, 아래는 node.js 애플리케이션이 글쓰기 권한을 요청했을때 사용자에게 node.js 애플리케이션에 글쓰기 권한을 허용할지를 물어보는 화면이다.



6.  페이스북의 인증서버는 ID,PASSWORD를 인증과 권한 획득에 성공하면, 인증과 권한 획득에 성공했다는  Authorization Code와 함께, 다시 2에서 정의된 Service Consumer node.js server callback URL (redirect_url= redirect_uri=http://localhost:8080/auth/facebook/callback ) redirect request를 보낸다.

7.  Browser는 위에서 받은 Authorization code와 함께, 앞에서 받은 node.js 서버의 callback URL redirect를 한다.

http://localhost:8080/auth/facebook/callback?code=AQAKlwhopD1DD5(중략)

8.  Node.js 서버는 이 사용자가 인증 되었음  증명하는 받은 authorization code를 가지고, 페이스북의 authorization server에 문의한다.
POST https://api.oauth2server.com/token
    grant_type=authorization_code&
    code=AUTH_CODE_HERE&
    redirect_uri=REDIRECT_URI&
    client_id=CLIENT_ID&
    client_secret=CLIENT_SECRET

Ÿ   grant_type : grant type은 앞에서 설명한 4개중에서, 현재 사용하는 grant type authorization code 방식이기 때문에 이를 정의한다.

Ÿ   code : 앞에서 받은 authorization code

Ÿ   redirect_uri

Ÿ   client_id : node.js 서버를 인증하기 위한 client id. 앞서 developer portal에서 발급받아서, node.js 서버 애플리케이션안에 넣어놓았다.

Ÿ   client_secret : node.js 서버를 인증하기 위한 client_secret(password). 앞서 developer portal에서 발급받아서, node.js 서버 애플리케이션안에 넣어놓았다.

9.  페이스북의 authorization server는 이 authorization code를 가지고해당 요청이 인증되었음을 확인하고, node.js 서버에게로 API 접근을 허용하는 access_token을 발급한다.

10. Node.js 서버는 이 access_token을 가지고 페이스북 API에 접근을 요청하면, 페이스북 API서버는 access_token을 통해서 API 접근 가능 여부를 판단한 후, 인가된 요청인 경우 API 요청을 수락하여 서비스를 제공한다.

간단하게 authorization code 방식을 이용한 애플리케이션 인증 방식에 대해서 알아보았다. 몇가지 더 짚고 넘어가면, authorization code 방식의 장점은 node.js 서버가 사용자 id,password를 알수 없다. API를 제공하는 서비스 제공자 입장에서, 3’rd party (협력 파트너)에게 자사의 사용자 id,password를 노출 시킬 필요가 없기 때문에, 파트너를 대상으로 안전하게 서비스 사용자의 id,password 없이 사용자 인증을 진행할 수 있다.

아울러 access_token에 대해서 몇가지 짚고 넘어갈것이 있는데, access_token을 취득한 이후에는 별도의 사용자 인증 절차가 필요하지 않다. Node.js 서버에서 access_token만을 기억하고 있으면 해당 사용자에 대한 리소스를 호출하는 API를 이 access_token을 통해서 호출할 수 있다. (즉 호출할때 마다 매번 사용자 인증을 할 필요가 없다는 말이다.)


다음글에서는 실제로 Passport 모듈을 이용하여 Facebook Open API를 호출하는 방법을 설명하도록 하겠다.

저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

REST API 디자인 가이드

아키텍쳐 /WEB 2.0 | 2014.06.12 21:54 | Posted by 조대협

REST API 디자인 가이드

조대협(http://bcho.tistory.com)

REST API 디자인을 보면, REST 사상에 맞춰서 제대로 디자인 (CRUD를 HTTP method에 맞춘)하기도 어렵고, URI Convention등이나 보안, 버전 관리등 고려할 사항이 많다. 이번 글에서는 REST API를 디자인에 대한 가이드를 소개하고자 한다.

동사보다는 명사를 사용하자

URL을 심플하고 직관적으로 만들자

REST API를 URL만 보고도, 직관적으로 이해할 수 있어야 한다
URL을 길게 만드는것 보다, 최대 2 depth 정도로 간단하게 만드는 것이 이해하기 편하다.

/dogs
/dogs/1234

URL에 동사보다는 명사를 사용한다.

REST API는 리소스에 대해서 행동을 정의하는 형태를 사용한다. 예를 들어서

POST /dogs

는 /dogs라는 리소스를 생성하라는 의미로, URL은 HTTP Method에 의해 CRUD (생성,읽기,수정,삭제)의 대상이 되는 개체(명사)라야 한다.
잘못된 예들을 보면

HTTP Post : /getDogs
HTTP Post : /setDogsOwner

위의 예제는 행위를 HTTP Post로 정의하지 않고, get/set 등의 행위를 URL에 붙인 경우인데, 좋지 않은 예 이다. 이보다는

HTTP Get : /dogs
HTTP Post : /dogs/{puppy}/owner/{terry}

를 사용하는 것이 좋다.
일반적으로 권고되는 디자인은 다음과 같다.

리소스POSTGETPUTDELETE
createreadupdatedelete
/dogs새로운 dogs 등록dogs 목록을 리턴Bulk로 여러 dogs 정보를 업데이트모든 dogs 정보를 삭제
/dogs/baduk에러baduk 이라는 이름의 dogs 정보를 리턴baduk이라는 이름의 dogs 정보를 업데이트baduk 이라는 이름의 dogs 정보를 삭제

단수(Singular) 보다는 복수(Plural)형 명상를 사용한다.

되도록이면 추상적인 이름보다 구체적인 이름을 사용하자

리소스간의 관계를 표현하는 방법

Option A.

다른 리소스와의 관계를 표현. 예를 들어 owner가 가지고 있는 개(dogs) 목록

GET /owner/{terry}/dogs

와 같이 /resource명/identifier/other-related-resource 형태로, 해당 리소스에 대한 경로를 /resource명/{그 리소스에 대한 identifier}/{연관되는 다른 리소스 other-related-resource} 형태로 표현한다.

Option B.

https://usergrid.incubator.apache.org/docs/relationships/ 에 보면 다른 형태의 관계 정의 방법에 대해서 나와 있는데, 조금 더 구체적인 API 관계 정의 방법은 다음과 같다.

/resource/identifier/relation/other-related-resource
GET /owner/terry/likes/dogs

이 방식은 리소스간의 관계(relationship)을 URL 내에 정의하는 방법으로,훨씬 더 명시적일 수 있다. (세련되어 보이지는 않지만)
리소스간의 관계가 복잡하지 않은 서비스의 경우에는 Option A를, 리소스간의 관계가 다소 복잡한 경우에는 Option B를 사용하도록 한다.

에러 처리

에러 처리의 기본은 HTTP Response Code를 사용한후, Response body에 error detail을 사용해주는 것이 좋다.

Use HTTP Status Code

HTTP Status Code는 대략 70개의 코드가 있다. 일반적인 개발자들이 모든 코드를 기억할리는 없고, 에러 코드에서는 자주 사용되는 몇개의 코드만 의미에 맞춰서 사용하는 것이 좋다.
Google의 GData의 경우에는 10개, Neflix의 경우에는 9개, Digg의 경우에는 8개를 사용한다.
(※ http://info.apigee.com/Portals/62317/docs/web%20api.pdf)

  • Google GData
    200 201 304 400 401 403 404 409 410 500
  • Netflix
    200 201 304 400 401 403 404 412 500
  • Digg
    200 400 401 403 404 410 500 503

필자의 경우, 아래와 같은 정도의 HTTP Code를 사용하기를 권장한다.

  • 200 성공
  • 400 Bad Request - field validation 실패시
  • 401 Unauthorized - API 인증,인가 실패
  • 404 Not found
  • 500 Internal Server Error - 서버 에러

자세한 HTTP Status Code는 http://en.wikipedia.org/wiki/Http_error_codes 를 참고하기 바란다.

Error Message

HTTP Status Code 이외에, Response body에 detail한 에러 정보를 표현하는 것이 좋은데,
Twillo의 Error Message 형식의 경우

HTTP Status Code : 401
{“status”:”401”,”message”:”Authenticate”,”code”:200003,”more info”:”http://www.twillo.com/docs/errors/20003"}

와 같이 표현하는데, 에러 코드 #와 해당 에러 코드 #에 대한 Error dictionary link를 제공한다.
비단 API 뿐 아니라, 잘 정의된 소프트웨어 제품의 경우에는 별도의 Error # 에 대한 Dictionary 를 제공하는데, Oracle의 WebLogic의 경우에도http://docs.oracle.com/cd/E24329_01/doc.1211/e26117/chapter_bea_messages.htm#sthref7 와 같이 Error #와, 이에 대한 자세한 설명과, 조치 방법등을 설명한다. 이는 개발자나 Trouble Shooting하는 사람에게 많은 정보를 제공해서, 조금 더 디버깅을 손쉽게 한다. (가급적이면 Error Code #를 제공하는 것이 좋다.)

Error Stack

에러메세지에서 Error Stack 정보를 출력하는 것은 대단히 위험한 일이다. 내부적인 코드 구조와 프레임웍 구조를 외부에 노출함으로써, 해커들에게, 해킹을 할 수 있는 정보를 제공하기 때문이다. 일반적인 서비스 구조에서는 아래와 같은 에러 스택정보를 API 에러 메세지에 포함 시키지 않는 것이 바람직 하다.

log4j:ERROR setFile(null,true) call failed.
java.io.FileNotFoundException: stacktrace.log (Permission denied)
at java.io.FileOutputStream.openAppend(Native Method)
at java.io.FileOutputStream.(FileOutputStream.java:177)
at java.io.FileOutputStream.(FileOutputStream.java:102)
at org.apache.log4j.FileAppender.setFile(FileAppender.java:290)
at org.apache.log4j.FileAppender.activateOptions(FileAppender.java:164)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

그렇지만, 내부 개발중이거나 디버깅 시에는 매우 유용한데, API 서비스를 개발시, 서버의 모드를 production과 dev 모드로 분리해서, 옵션에 따라 dev 모드등으로 기동시, REST API의 에러 응답 메세지에 에러 스택 정보를 포함해서 리턴하도록 하면, 디버깅에 매우 유용하게 사용할 수 있다.

버전 관리

API 정의에서 중요한 것중의 하나는 버전 관리이다. 이미 배포된 API 의 경우에는 계속해서 서비스를 제공하면서,새로운 기능이 들어간 새로운 API를 배포할때는 하위 호환성을 보장하면서 서비스를 제공해야 하기 때문에, 같은 API라도 버전에 따라서 다른 기능을 제공하도록 하는 것이 필요하다.
API의 버전을 정의하는 방법에는 여러가지가 있는데,

  • Facebook ?v=2.0
  • salesforce.com /services/data/v20.0/sobjects/Account
    필자의 경우에는

    {servicename}/{version}/{REST URL}
    example) api.server.com/account/v2.0/groups

형태로 정의 하는 것을 권장한다.
이는 서비스의 배포 모델과 관계가 있는데, 자바 애플리케이션의 경우, account.v1.0.war, account.v2.0.war와 같이 다른 war로 각각 배포하여 버전별로 배포 바이너리를 관리할 수 있고, 앞단에 서비스 명을 별도의 URL로 떼어 놓는 것은 향후 서비스가 확장되었을 경우에, account 서비스만 별도의 서버로 분리해서 배포하는 경우를 생각할 수 있다.
외부로 제공되는 URL은 api.server.com/account/v2.0/groups로 하나의 서버를 가르키지만, 내부적으로, HAProxy등의 reverse proxy를 이용해서 이런 URL을 맵핑할 수 있는데, api.server.com/account/v2.0/groups를 내부적으로 account.server.com/v2.0/groups 로 맵핑 하도록 하면, 외부에 노출되는 URL 변경이 없이 향후 확장되었을때 서버를 물리적으로 분리해내기가 편리하다.

페이징 처리와 Partial response

페이징

큰 사이즈의 리스트 형태의 응답을 처리하기 위해서 필요한 것은 페이징 처리와 partial response 처리이다. 리스트 내용이 1000,000개인데, 이를 하나의 HTTP Response로 처리하는 것은 서버 성능, 네트워크 비용도 문제지만 무엇보다 비현실적이다. 그래서, 페이징을 고려하는 것이 중요하다.
페이징을 처리하기 위해서는 여러가지 디자인이 있다.

예를 들어 100번째 레코드부터 125번째 레코드까지 받는 API를 정의하면

  • Facebook API 스타일 : /record?offset=100&limit=25
  • Twitter API 스타일 : /record?page=5&rpp=25 (RPP는 Record per page로 페이지당 레코드수로 RPP=25이면 페이지 5는 100~125 레코드가 된다.)
  • LikedIn API 스타일 : /record?start=50&count=25

apigee의 API가이드를 보면 좀더 직관적이라는 이유로 페이스북 스타일을 권장하고 있다.
record?offset=100&limit=25

Partial Response (Optional)

리소스에 대한 응답 메세지에 대해서 굳이 모든 필드를 포함할 필요가 없는 케이스가 있다. 예를 들어 페이스북 FEED의 경우에는 사용자 ID, 이름, 글 내용, 날짜, 좋아요 카운트, 댓글, 사용자 사진등등 여러가지 정보를 갖는데, API를 요청하는 Client의 용도에 따라 선별적으로 몇가지 필드만이 필요한 경우가 있다. 필드를 제한하는 것은 전체 응답의 양을 줄여서 네트워크 대역폭(특히 모바일에서) 절약할 수 있고, 응답 메세지를 간소화하여 파싱등을 간략화할 수 있다.
그래서 몇몇 잘 디자인된, REST API의 경우 이러한 Partial Response 기능을 제공하는데, 주요 서비스들을 비교해보면 다음과 같다.

  • Linked in : /people:(id,first-name,last-name,industry)
  • Facebook : /terry/friends?fields=id,name
  • Google : ?fields=title,media:group(media:thumnail)
    Linked in 스타일의 경우 가독성은 높지만 :()로 구별하기 때문에, HTTP 프레임웍으로 파싱하기가 어렵다. 전체를 하나의 URL로 인식하고, :( 부분을 별도의 Parameter로 구별하지 않기 때문이다.
    Facebook과 Google은 비슷한 접근 방법을 사용하는데, 특히 Google의 스타일은 더 재미있는데, group(media:thumnail) 와 같이 JSON의 Sub-Object 개념을 지원한다.
    Partial Response는 Google 스타일을 이용하는 것을 권장한다.

검색

검색은 일반적으로 HTTP GET에서 Query String에 검색 조건을 정의하는 경우가 일반적인데, 이 경우 검색조건이 다른 Query String과 섞여 버릴 수 있다. 예를 들어 name=cho이고, region=seoul인 사용자를 검색하는 검색을 Query String만 사용하게 되면 다음과 같이 표현할 수 있다.
/users?name=cho&region=seoul
그런데, 여기에 페이징 처리를 추가하게 되면

/users?name=cho&region=seoul&offset=20&limit=10

페이징 처리에 정의된 offset과 limit가 검색 조건인지 아니면 페이징 조건인지 분간이 안간다. 그래서, 쿼리 조건은 하나의 Query String으로 정의하는 것이 좋은데

/user?q=name%3Dcho,region%3Dseoul&offset=20&limit=10

이런식으로 검색 조건을 URLEncode를 써서 “q=name%3Dcho,region%3D=seoul” 처럼 (실제로는 q= name=cho,region=seoul )표현하고 Deleminator를 , 등을 사용하게 되면 검색 조건은 다른 Query 스트링과 분리된다.
물론 이 검색 조건은 서버에 의해서 토큰 단위로 파싱되어야 하낟.

전역 검색과 리소스 검색

다음으로는 검색의 범위에 대해서 고려할 필요가 있는데, 전역 검색은 전체 리소스에 대한 검색을, 리소스에 대한 검색은 특정 리소스에 대한 검색을 정의한다.
예를 들어 시스템에 user,dogs,cars와 같은 리소스가 정의되어 있을때,id=’terry’인 리소스에 대한 전역 검색은

/search?q=id%3Dterry

와 같은 식으로 정의할 수 있다. /search와 같은 전역 검색 URI를 사용하는 것이다.
반대로 특정 리소스안에서만의 검색은

/users?q=id%3Dterry

와 같이 리소스명에 쿼리 조건을 붙이는 식으로 표현이 가능하다.

HATEOAS (Optional)

HATEOS는 Hypermedia as the engine of application state의 약어로, 디자인의 요지는 하이퍼미디어의 특징을 이용하여 HTTP Response에 다음 Action에 대한 HTTP Link를 함께 리턴하는 것이다.
예를 들어 앞서 설명한 페이징 처리의 경우, 리턴시, 전후페이지에 대한 링크를 제공한다거나

HTTP GET users?offset=10&limit=5
{
[
{‘id’:’user1’,’name’:’terry’}
,{‘id’:’user2’,’name’:’carry’}
]
,’links’ :[
{
‘rel’:’pre_page’,
‘href’:’http://xxx/users?offset=6&limit=5
}
,
{
‘rel’:’next_page’,
‘href’:’http://xxx/users?offset=11&limit=5
}
]
}
와 같이 표현하거나
연관된 리소스에 대한 디테일한 링크를 표시 하는 것등에 이용할 수 있다.
HTTP GET users/terry
{
‘id’:’terry’
‘links’:[{
‘rel’:’friends’,
‘href’:’http://xxx/users/terry/friends
}]
}

HATEOAS를 API에 적용하게 되면, Self-Descriptive 특성이 증대되어 API에 대한 가독성이 증가하는 장점을 가지고 있기는 하지만, 응답 메세지가 다른 리소스 URI에 대한 의존성을 가지기 때문에, 구현이 다소 까다롭다는 단점이 있다.
요즘은 Spring과 같은 프레임웍에서 프레임웍 차원에서 HATEOAS를 지원하고 있으니 참고하기 바란다.http://spring.io/understanding/HATEOAS

단일 API URL

API 서버가 물리적으로 분리된 여러개의 서버에서 동작하고 있을때, user.apiserver.com, car.apiserver.com과 같이 API 서비스마다 URL이 분리되어 있으면 개발자가 사용하기 불편하다. 매번 다른 서버로 연결을 해야하거니와 중간에 방화벽이라도 있으면, 일일이 방화벽을 해제해야 한다.
API 서비스는 물리적으로 서버가 분리되어 있더라도 단일 URL을 사용하는 것이 좋은데, 방법은 HAProxy나 nginx와 같은 reverse proxy를 사용하는 방법이 있다.
HAProxy를 앞에 새우고 api.apiserver.com이라는 단일 URL을 구축한후에
HAProxy 설정에서

api.apiserver.com/user는 user.apiserver.com 로 라우팅하게 하고
api.apiserver.com/car 는 car.apiserver.com으로 라우팅 하도록 구현하면 된다.

보안

API 서비스에 있어서 보안은 매우 중요한 요소이다. API 에 대한 보안은 다음과 같이 보안 대상에 따라서 몇가지로 나눠진다.

인증

인증은, API를 호출하는 클라이언트가 VALID한 사용자인지, 불법적인 사용자인지를 구별하는 방식이다.

HTTP Basic Auth

가장 쉬운 방식으로는 사용자 id,password를 표준 HTTP Basic Auth에 넣어서 전송하는 방식으로 사용자 단위의 인증과 권한 컨트롤이 가능하다는 장점을 가지고 있다. 그러나 이 경우, 매번 사용자 id와 password가 네트워크를 통해서 전송되기 때문에, 해커에 의해서 사용자 id,password가 누출될 수 있다. 사용자 id,password가 누출되면 API 호출 권한뿐만 아니라 웹에 로그인해서 다른 서비스를 사용하는 등 치명적이기 때문에 그리 권장되지 않는 방법이다.
SSL을 사용해서 암호화할 수 는 있겠지만 기본적으로 SSL은 Man in the middle attack (중간에 인증서를 가로체서 SSL 패킷을 열어보는 방법) 에 취약하기 때문에 완벽하다고 볼 수 없다.

Access Token

매번 네트워크를 통해서 사용자 id,password를 보내는 것이 위험하다면, 처음 인증시에만 id,password를 보내고, 인증이 성공하면 서버에서 access_token을 발급하여 API 호출시 access_token으로만 호출하는 방식이다. (OAuth2.0이 유사한 메커니즘을 사용한다.) 이 경우 API를 호출하는 클라이언트가 access_token을 저장하는 메카니즘을 가져야 한다. 또한 access_token이 누출될때를 대비하여, 서버쪽에서 compromised된 (노출된/오염된) token의 경우 revoke(사용금지 처리)를 하고, 클라이언트에게 다시 access_token을 발급하도록 하는 메커니즘과, Expire time을 둬서 주기적으로 token을 교체하도록 하는 방식이 좋다.

API Key

API Key 시나리오는 일반적으로 API 제공자가 API 포탈등을 통해서, 개발자를 인증하고 개발자에게 API Key를 발급한후, 개발자가 API Key를 애플리케이션 코드내에 탑재해서 사용하는 방법을 사용한다. API를 외부 파트너에게 공개하는 경우에 손쉽게 사용할 수 있으며, 꽤 많이 사용되던 방식이다. 단 애플리케이션 (특히 모바일 애플리케이션)이 디컴파일 될 경우 API Key가 누출될 수 있는 위험성을 가지고 있기 때문에, API Key를 잘 관리 하는 것이 중요하다. (난독화를 한다던가)
이 시나리오의 경우 애플리케이션 단위의 인증을 하기 때문에 앞서 설명한 두 방식처럼 사용자 단위의 인증은 불가능 하다.
(사용자 단위의 인증이 필요한 경우 API Key로 애플리케이션을 인증한 후에, 클라이언트 마다 새로운 access_token을 발급하는 방식을 사용할 수 있다. 사실 이게 OAuth 2.0의 client_secret과 access_token 시나리오와 유사하다.)

OAuth 2.0 (Recommended)

OAuth는 근래에 가장 많이 사용되는 API 인가/인증 기술이다. 특징중의 하나는 Authentication(인증)만이 아니라 권한에 대한 통제(Authorization)이 가능하다는 특징을 가지고 있으며, 3 legged 인증을 통해서, 파트너사가 API를 사용할 경우, 인증시에 사용자 ID와 비밀번호를 파트너에게 노출하지 않을 수 있는 장점이 있다. (페이스북 계정을 이용한 웹 애플리케이션들을 보면 가끔, 페이스북 로그인 화면으로 리다이렉트되어 “XX 애플리케이션이 XX에 대한 권한을 요청합니다. 수락하시겠습니까?”와 같은 창이 뜨는 것을 볼 수 있는데, 페이스북 로그인 화면에, 사용자 ID와 비밀 번호를 넣고 페이스북은 인증이 되었다는 정보를 인증을 요청한 웹애플리케이션으로 보내서, 해당 사용자가 인증되었음을 알려준다. 이경우, 웹 애플리케이션은 사용자의 비밀번호를 알 수 없다. )
기본적인 OAuth의 원리는, 사용자 ID/PASSWD로 인증을 한 후에, access_token을 받아서, access_token을 이용해서 추후 커뮤니케이션을 하는 방식이다.

OAuth는 크게 용도에 따라 4가지 타입의 인증 방식을 제공한다.

  • Authorization Code 방식 - 주로 웹 애플리케이션 인증에 유리하며, 위에서 설명한 케이스와 같이 웹을 통해서 Redirect 하는 방식이다.
  • Implicit 방식 - 자바스크립트 기반의 애플리케이션이나 모바일 애플리케이션 처럼 서버 백엔드가 없는 경우 사용한다.
  • Resource Owner password credential 방식 - 인증을 요청하는 클라이언트에서 직접 ID와 PASSWD를 보내는 방식으로, (이 경우 위의 방식들과 다르게 서비스 제공자의 로그인창으로 리다이렉션이 필요 없다.) 클라이언트가 직접 ID,PASSWD를 받기 때문에, 클라이언트에 사용자의 비밀번호가 노출될 수 있어서 서버와 클라이언트를 같은 회사에서 제작한 경우나, 사용자의 정보를 공유해도 되는 1’st party 파트너등과 같은 경우에 사용한다.
  • Client Credential 방식 - 일반적인 애플리케이션 Access에 사용한다.

일반적으로 API를 3’rd party에 제공할 경우에는 Authorization Code 방식을, 자사의 API를 자사나 1’st party 파트너만 사용할 경우에는 Resource Owner password credential 방식이 좋다.

Mutual SSL

가장 강력한 인증 방법으로,클라이언트와 서버가 각자 인증서를 가지고 상호 인증하는 방식이다. 양방향(2-way)SSL 이라고도 한다. 이 경우에는 클라이언트의 인증서(Certificate)를 서버에게 안전하게 전송할 수 있는 메커니즘이 필요하다. 클라이언트가 접속했을때, Certificate를 네트워크를 통해서 전송하고, 서버는 이 인증서가 공인된 인증서인지를 확인하는 방법도 있고, 내지는 서버의 Admin Console등을 통해서 클라이언트가 사용하는 인증서 자체를 업로드 해놓는 방법등 다양한 방법이 있다.
Mutual SSL은 양쪽에 인증서를 사용하기 때문에, Man in the middle attack이 불가능하고, Packet을 snipping해서 보는 것 조차도 불가능 하다. (대신 구현이 다소 까다롭다.)

WhiteList 방식

서버간의 통신에서는 가장 간단하게 할 수 있는 방식이 서버가 API 호출을 허용할 수 있는 IP 목록을 유지하는 방법이다. (WhiteList 방식). 다른 IP에서 들어오는 API 호출의 경우 받지 않는 방법으로, 가장 구현이 간단하다. 방화벽이나 Reverse proxy 설정등으로도 가능하고, 필요하다면, VPN (Virtual Private Network)등을 이용할 수 도 있다.

프로토콜 레벨 암호화

HTTP 통신 프로토콜 자체를 암호화 하는 방식인데, SSL을 이용한 HTTPS가 대표적인 경우이다. API 디자인에서 HTTPS는 반드시 적용하는 것을 권장한다.
HTTPS는 앞에서도 잠깐 언급했듯이 Man in the middle attack에 취약한데 Man in the middle attack의 기본적인 메커니즘은 서버에서 보낸 인증서를 바꿔치기 해서, 클라이언트로 보내는 방식을 이용한다. (http://en.wikipedia.org/wiki/Man-in-the-middle_attack)
가능하면, 인증서 체크 로직을 클라이언트에 두는 것이 좋다. 인증서가 공인된 인증서인지, (또는 그 서버의 인증서가 맞는지를 Issuer등을 통해서 확인할 수 있다. 인증서에 있는 내용들은 기본적으로 중간에 해커가 바꿀 수 다 없다. Signing이 되어있기 때문에, 내용을 바꾸면 Singing된 Signature가 맞지 않는다.) attack

메세지 레벨 암호화

다음으로 JSON과 같은 메세지 자체를 암호화할 수 있는데, 앞서 설명해듯이 SSL을 사용하더라도, 중간에 인증서를 바꿔 치는 등의 행위를 통해서 패킷을 열어볼 경우, 메세지 내용을 노출될 가능성이 있기 때문에 이를 방지 하기 위해서, 중요한 메세지는 암호화하는 것을 권장한다.
이때 전체 메세지를 암호화 하는 것은 비효율적이며 특정 필드의 값만 필요에 따라서 암호화를 하는 것이 좋다.

무결성 관리 (HMAC)

메세지의 무결성 보장이란, 중간에 해커에 의해서 메세지가 변경할 수 없도록 하는 것이다. 기본적인 원리는 메세지에 대한 해쉬값을 계산해서, 보내서, 받는 쪽에서 받은 메세지를 가지고 똑같은 알고리즘으로 해쉬를 생성한후, 보내온 해쉬값과 비교하는 방법을 사용한다. 만약에 메세지가 변조가 되었다면,해쉬값이 일치하지 않기 때문에 메세지 변조 여부를 파악할 수 있다. 자세한 설명과 구현 방법은 http://bcho.tistory.com/807를 참고하기 바란다.

지금까지 간략하게 나마 REST API 설계 방식에 대해서 알아보았다.
자세한 자료들은 아래 참고 자료들은 참고하기 바란다.

참고 : http://info.apigee.com/Portals/62317/docs/web%20api.pdf
참고 : APICract Google groups https://groups.google.com/forum/?fromgroups#!topic/api-craft/
참고 : 마틴파울러의 ‘Glory of REST 향한 단계들’ http://jinson.tistory.com/190


새로운 문서가 업데이트 되었습니다.

REST API 이해와 설계 - #1 개념 잡기 http://bcho.tistory.com/953

REST API 이해와 설계 - #2 디자인 가이드  http://bcho.tistory.com/954

REST API 이해와 설계 - #3 보안 가이드  http://bcho.tistory.com/955



저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

OAuth 2.0 based API 인증 메모

아키텍쳐 /Security & IDM | 2014.06.05 17:02 | Posted by 조대협


* Authroization Code

- Redirect base / 서버 백엔드가 있는 경우 사용 - 파트너사가 API를 사용하는 시나리오에 유리

* Implicit 

- Rediect base / 특히 Java script 처럼 서버 백엔드가 없는 경우 유용. Read Only 등에 사용

* Resource Owner Password Credential 

- Client Id, Secret을 앱에 넣은 후, Client Id/Password로 인증하여, access token을 발급 받는 방식으로, Authorization Server와 Resource Owner가 같은 서비스인 경우 유용함 (자사 API 제공에 유용)

* Client Crendetial


유용한 Link

  • 서버 구현체 : http://oauth.net/2/
  • http://aaronparecki.com/articles/2012/07/29/1/oauth2-simplified
  • node.js 모듈 - https://github.com/jaredhanson/oauth2orize
  • http://vinebrancho.wordpress.com/2014/05/19/oauth2-restapi-server-%EB%AA%A8%EB%B0%94%EC%9D%BC-%EC%95%B1%EC%9D%84-oauth2-0%EC%9C%BC%EB%A1%9C-%EC%A2%80-%EB%8D%94-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C/


저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'아키텍쳐  > Security & IDM' 카테고리의 다른 글

서버와 APNS(애플푸쉬서버)와의 보안 메커니즘  (3) 2014.10.07
OAuth 2.0 노트  (0) 2014.08.11
OAuth 2.0 based API 인증 메모  (0) 2014.06.05
Digital Signing의 개념  (0) 2014.05.21
Java keystore file  (0) 2013.09.27
SSL/TLS 관련 개념 링크  (1) 2013.09.24

대용량 분산 시스템 아키텍쳐 디자인


대용량 분산 시스템에 대한 아키텍쳐 설계에 대한 내용을 공유합니다. 아직 많이 부족합니다. 많은 피드백 부탁드립니다.


1. 아키텍쳐 설계 프로세스


2. 대용량 분산 시스템 아키텍쳐


3. 대용량 분산 시스템 아키텍쳐 디자인 패턴


4. 레퍼런스 아키텍쳐 - SOA 


5. 레퍼런스 아키텍쳐 - REST


저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
angularJS를 이용한 REST Client 예제
서버는 http//URL/msg에서 다음과 같은 리턴값을 보냄
HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
Content-Length: 57

{
"data":{
"name":"Terry",
"city":"Seoul"
}
}


AngularJS 코드

<!--

reference : http://www.jacopretorius.net/2013/04/using-ngresource-with-angularjs.html

reference : https://github.com/wprl/baucis/blob/master/examples/angular-example-resource.html

There are two ways to implement REST client

first one is using $http. It is more flexible but need more coding.

The other one is $resource. It is more efficient. 

-->

<html ng-app='jsonGetApp'>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">

  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular.min.js"></script>

  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular-route.min.js"></script>        

  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular-resource.js"></script>        

<script type="text/javascript">

var jsonGetApp = angular.module('jsonGetApp',['ngResource']);

jsonGetApp.controller("MsgCtrl",["$resource","$scope","$log",function($resource,$scope,$log){

$scope.$log=$log;

$scope.jsonmsg='';

var msgs = $resource('/msg','',{query:{method:'GET',isArray:false}});

$scope.sendJson = function(){

$log.info('sendJson has been called');

var result = msgs.query();

$log.info(result);

$scope.jsonmsg=result;

}

}]

);

</script>

</head>

<body>

<div ng-controller="MsgCtrl">

<textarea rows="5" style="width: 100%" ng-model="jsonmsg" ></textarea>

<button ng-click="sendJson();">Get JSON</button>

{{jsonmsg}}

</div>

</body>

</html>

저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

django 에서 REST API 만들기

프로그래밍/Python | 2014.01.08 00:58 | Posted by 조대협

Dango에서 간단한 REST API 만들기 

조대협



Django에서 REST API를 만들려면 가장 널리 사용되는 프레임웍중 하나가 dango rest_framework이다.

http://django-rest-framework.org/tutorial/quickstart


설치는 다음과 같다.

pip install djangorestframework

pip install markdown       # Markdown support for the browsable API.

pip install django-filter  # Filtering support

 

1. quickstart라는 앱을 생성

 

C:\Users\terry\git\django_restframework_sample\django_restframework_sample>python manage.py startapp quickstart

또는 이클립스 사용시에는 project에서 오른쪽 버튼 누르고 django 메뉴에서 > Custom command (manage.py xxx)에서 startapp 명령어 실행

 



아래와 같이 프로젝트 아래에 앱이 생성된



 

2. DB sync

setting.py에 아래 내용을 추가

DATABASES = {

    'default': {

        'ENGINE': 'django.db.backends.sqlite3',

        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),

        'USER': '',

        'PASSWORD': '',

        'HOST': '',

        'PORT': ''

    }

}

다음 eclipse에서 마찬가지로 오른쪽 버튼을 눌러서, django 메뉴에서 manage.py syncdb 명령어를 수행



이 예제에서는 실제 테이블은 만들지 않고, API 인증을 위해서, django user 테이블만을 생성한다. Syncdb를 수행하면 아래와 같이 첫 user를 만드는데, 이때, 사용자 id,email,password를 입력하도록 한다.



 

3. 다음으로 코딩

1) quickstart/serializer.py 코드를 아래와 같이 작성

from django.contrib.auth.models import User, Group

from rest_framework import serializers

 

 

class UserSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:

        model = User

        fields = ('url', 'username', 'email', 'groups')

 

 

class GroupSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:

        model = Group

        fields = ('url', 'name')

 

2) quickstart/views.py를 다음과 같이 작성

from django.contrib.auth.models import User, Group

from rest_framework import viewsets

from quickstart.serializers import UserSerializer, GroupSerializer

 

 

class UserViewSet(viewsets.ModelViewSet):

    """

    API endpoint that allows users to be viewed or edited.

    """

    queryset = User.objects.all()

    serializer_class = UserSerializer

 

 

class GroupViewSet(viewsets.ModelViewSet):

    """

    API endpoint that allows groups to be viewed or edited.

    """

    queryset = Group.objects.all()

    serializer_class = GroupSerializer

 

위의 테스트 앱은 UserViewSet은 현재 Django에 등록된 사용자 리스트들을 읽어다가 JSON으로 리턴하는 클래스 이고, GroupViewSet은 마찬가지로 django에 등록된 사용자 그룹 리스트를 읽어다가 JSON으로 리턴하는 클래스이다.

 

Rather than write multiple views we're grouping together all the common behavior into classes called ViewSets.

We can easily break these down into individual views if we need to, but using viewsets keeps the view logic nicely organized as well as being very concise.

Notice that our viewset classes here are a little different from those in the frontpage example, as they include queryset andserializer_class attributes, instead of a model attribute.

For trivial cases you can simply set a model attribute on the ViewSet class and the serializer and queryset will be automatically generated for you. Setting the queryset and/or serializer_class attributes gives you more explicit control of the API behaviour, and is the recommended style for most applications.

4. 다음으로 세팅

1) root project(여기서는 django_restframework_sample) urls.py에 다음 코드를 추가함

from django.conf.urls import patterns, url, include

from rest_framework import routers

from quickstart import views

 

router = routers.DefaultRouter()

router.register(r'users', views.UserViewSet)

router.register(r'groups', views.GroupViewSet)

 

# Wire up our API using automatic URL routing.

# Additionally, we include login URLs for the browseable API.

urlpatterns = patterns('',

    url(r'^', include(router.urls)),

    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))

)

일반적인 web application과 다르게 view가 아닌 viewset을 사용하기 때문에, URL rest_framework이 자동으로 설정하도록 세팅하였다.

l  /users à views.UserViewSet API로 맵핑 되고

l  /groups à views.GroupViewSet API로 맵핑 된다.

 

2) Root project setting.py에 다음을 추가

REST_FRAMEWORK = {

    'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),

    'PAGINATE_BY': 10

}

이 부분이 흥미로운 부분인데, JSON 기반의 API 인증(Authentication)  인가(Authorization)을 프레임웍 자체에서 제공한다. 위의 경우에는 Admin 사용자에 대해서만 서비스를 제공하고, HTTP BASIC AUTH 인증을 사용한다.

http://django-rest-framework.org/api-guide/permissions 에 자세한 내용이 나와 았는데,  

  모든 사용자에게 access를 허가하는 Allow Any

  인증된 사용자에게만 access를 허가하는 isAuthenticated

  관리자에게만 인증을 허가하는 isAdminUser

  인증되지 않은 사용자에게는 Read,인증된 사용자에게는 Update를 제공하는 isAuthenticatedOrReadOnly

  기타 DjangoModel (django.contrib.auth에 의해서 관리되는 권한)이나 Object 단위 권한등에 따라서, 조정할 수 있다.

  그리고 TokenHasReadWriteScope의 경우에는 OAuth2Authentication등과 연계하여 Token 기반의 인증이 가능하게 된다.

※ 다양한 인증 메커니즘을 제공하기 때문에, 처음에는 어려울지 몰라도, 이해하고 나면 짜는 것 보다는 훨씬 쉬울듯.

위는 API접근 권한에 대한 것이었고, Authenticationhttp://django-rest-framework.org/api-guide/authentication 에 정의되어 있다. 위에서 사용한 것과 같이 HTTP Basic Auth를 사용할 수 도 있고, Token이나 Http Session 기반의 Authentication 그리고, OAuth 1,2 기반의 인증을 모두 지원한다. (Token 방식의 경우에는 userauthenticated된 다음, Authorization token을 서버에서 발급해준다. Token HTTP를 타고 내려가기 때문에, Token secure하게 전달하기 위해서 HTTPS를 필수로 사용해야 한다.)

 

 

3) 그리고, setting.py INSTALLED_APPS rest_framwwork을 추가하는 것도 있지 말자

INSTALLED_APPS = (

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

    'rest_framework',

)

5. 테스팅

테스트는 Chrome plug-inadvanced rest client를 사용하였다.

여기서 주목할 것은 HTTP Basic Auth 헤더를 어떻게 입력하느냐인데,

Headers 메뉴에서 Form을 선택한후 Add new header에서 Key Authorization을 선택한다.



Authorization이 선택되면 value를 넣는 부분에 아래와 같이 Construct라는 버튼이 나타나는데,



이 버튼을 누르게 되면 아래와 같이 dialog box가 나와서 basic 며소에 대한 log in id password를 넣을 수 있게 된다.



이 과정이 끝나면 Header 부분에 HTTP Basic Auth 부분이 들어가게 되고, 아래와 같은 테스트 결과를 얻게 된다.

 




대부분의 내용은 http://django-rest-framework.org/tutorial/quickstart 를 참고하였음


저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'프로그래밍 > Python' 카테고리의 다른 글

django 에서 REST API 만들기  (1) 2014.01.08
Python 공부 노트 11. Class  (0) 2014.01.03
Python 공부 노트 11. Module install  (0) 2014.01.02
Python에서 Open API 호출하기  (0) 2013.11.22
Django에서 static file (css,img 사용하기)  (0) 2013.11.21
Django Template  (0) 2013.11.21

Python에서 Open API 호출하기

프로그래밍/Python | 2013.11.22 19:09 | Posted by 조대협

Rest API를 호출하기

여러가지 라이브러리 (urllib2, httplib2)등을 체크해봤으나, https 를 가장 쉽게 호출할 수 있고, 사용하기 편한것은 requests라는 것이 가장 편리함 http://www.python-requests.org/en/latest/user/quickstart/#make-a-request 에서 curl 로 다운로드 하고 설치해서 사용



위는 도스창을 이용해서 간단하게 https로 dna.daum.net을 호출한 코드인데, 리턴값이 한글이라서 그런지. cp949 encode 에러가 남. (이건 나중에 수정해야 할거 같고)


기타 참고 자료 (아래)


참고 : API 호출 하기

https://dna.daum.net/tools/python/tutorial


SSL 사용하기

urllib2는 https가 지원되지 않음


1. Python 인스톨본이 SSL을 지원해야 한다.

. 확인하는법

>>> import socket

>>> socket.ssl

<function ssl at 0x4038b0>


2. httplib2 설치

https://code.google.com/p/httplib2/



저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'프로그래밍 > Python' 카테고리의 다른 글

Python 공부 노트 11. Class  (0) 2014.01.03
Python 공부 노트 11. Module install  (0) 2014.01.02
Python에서 Open API 호출하기  (0) 2013.11.22
Django에서 static file (css,img 사용하기)  (0) 2013.11.21
Django Template  (0) 2013.11.21
Django Hello World  (0) 2013.11.21

API 플랫폼에 대한 소개

아키텍쳐 /대용량 아키텍쳐 | 2013.10.30 01:02 | Posted by 조대협

API Platform

조대협


근래에 들어서 모바일 애플리케이션의 발전과 SNS의 발전과 더불어서 Open API 에 대한 관심도가 급격하게 높아졌다. Open API, API를 내부 사용자뿐만 아니라 외부 개발자에게까지 공개해서, 외부 개발자가 API를 이용해서 새로운 서비스를 만들도록 하는 모델이다. 근래에 들어서는 API만 전문적으로 개발 및 서비스를 해서 이를 통해서 수익을 창줄하는 비지니스 모델까지 생겨나고 있다. 이런 배경으로 API 관리에 대한 중요성이 대두되었는데, API에 대한 쉬운 관리, 모니터링 및 유료화 그리고, API에 대한 편리한 사용법, 샘플 코드 및 메뉴얼 제공하는 시나리오가 필요하게 되었는데, 이를 하나의 플랫폼 형태로 묶어 놓은 것을 API 플랫폼이라고 한다.

API 플랫폼은 크게, API에 대한 단일 진입 포인트 역할을 하는 게이트 웨이와, API 문서나 샘플 코드들을 제공하는 API 포탈, 그리고 API 서비스 상황을 모니터링할 수 있는 관리 모니터링 기능으로 분리된다.

제품군으로는 크게 두 가지 종류로 나뉘어 지는데, 예전 SOA(Service Oriented Architecture) ESB (Enterprise Service Bus) 를 게이트웨이로 사용하면서 발전해온 SOA 진영쪽 제품과, 처음부터 API 플랫폼으로 디자인되서 서비스되는 이 양쪽으로 나뉘어 진다. 전자의 경우에는 MuleSoft WSO2 API 플랫폼이 대표적인 예이고, 후자의 경우에는 apigee,3scale,Mashery,Layer 7(CA) 등이 있다. 전자의 경우에는 ESB를 기반으로 하기 때문에, API에 대한 워크 플로우 기능이 매우 강력하다. 워크플로우 기능이란, 메세지를 변경하거나 라우팅 또는 다른 기능을 추가하는 부분이 매우 강력하다.반면 포탈이나 모니터링 기능들은 후자에 비해서 약하다. 후자의 경우에는 워크 플로우 기능은 약한 반면, 유료화 모델이나 다양한 사용자 계층 지원 그리고 무엇보다 포탈이 매우 강력하다.

Apigee Mashery 같은 경우는 설치형이 아니라 클라우드 형으로 서비스를 제공하는데, 개발사나 개발자가 API를 개발하고, API 서비스는 클라우드에 설치된 이 API 플랫폼을 통해서 하면, 이 플랫폼이 중간에 Gateway 역할로써 API에 대한 워크플로우 기능 및 모니터링 기능등을 제공하면서 함께, 포탈등의 부가 기능을 제공한다.

예전에 미국 캘리포니아 실리콘 밸리에 있는 apigee 를 방문할 일이 있었다. 긴 시간은 아니었지만, 서비스와 인력들에 대해서 이야기를 나눌 기회가 있었는데, 재미있는 점은 인력 구성원의 많은 인원들이 기존의 SOA 회사 출신(BEA)으로 이루어져 있다는 것이다. API 서비스의 근간은 SOA를 기반으로 하고 있으며, 기술 역시 SOA ESB와 연관된 기술을 많이 사용함으로 이해할 수 있었다. 회사의 분위기는 여타 실리콘 밸리의 회사와 같이 매우 젊고 활기에 차있었다.

API 플랫폼을 서비스로 제공하는 만큼 운영에 대해서 많이 신경을 쓰고 있었는데, 실제로 플랫폼을 서비스로 제공하면서 얻는 노하우는 대단하리라고 본다.

 

주요 기능



API 인증

먼저 API를 사용하려면, API를 호출하는 클라이언트가 인증이 된 사용자인지 아닌지를 구분할 수 있는 인증 Authentication 기능이 필요하다. 앞에서 REST API 보안에서 설명한것 처럼, API Key를 발급하거나 OAuth 기반의 인증을 제공할 필요가 있다. 근래의 API Platform OAuth 기반 인증을 제공하는 것이 일반적인 흐름이다.

사용자 인증

사용자 인증은 API 인증을 받기 위한 Key OAuth 인증을 받거나, 또는 API 포탈에 접속해서 메뉴얼을 보거나 Admin 으로써, API에 대한 모니터링을 하기 위해서 필요한 시나리오이다. 사용자를 API를 사용하는 일반 사용자, 내부 관리자 또는 일반 사용자/Silver/Gold 등의 레벨로 나눠서 서비스를 하는 등의 다양한 서비스 시나리오에 대한 사용자 관리 기능을 제공한다.

SLA management

SLA Service Level Agreement의 약자로, 서비스에 대해서 약속한 수준의 비기능 요건을 만족해주는 것을 이야기 한다. 쉽게 이야기 해서, API 제공자별로 하루에 5000건의 call을 제공하기로 했으면, 5000건 까지만 제공한다던지, API 사용자별로 하루에 1000건의 call을 제공한다던지의 계약 레벨별로 서비스 수준을 조정해줄 수 있는 기능을 제공한다.

Mediation

Mediation은 들어온 API 호출에 대해서 변형을 가하는 것이다.예를 들어 원래 API가 주문 API였다면, 나중에, 주문 API에 포인트 적립 API를 추가해준다거나 API version에 따라서 라우팅을 해주거나 하는 등의 기능을 한다. 이렇게 원래 호출 내용에 대해서 변경을 가하는 것을 Mediation이라고 한다. 그러면 몇가지 Mediation 패턴에 대해서 소개한다. 이런 Mediation 패턴은 앞서 설명한 Workflow 기능에서 제공되며, 이 챕터의 뒤에서 소개할 SOA 아키텍쳐의 ESB의 부분을 참고하면 좋다.

아래는apigee 플랫폼에서 workflow를 적용하는 UI 화면의 일부이다. 보통 workflow IDE같은 개발환경에서 순서도 같은 흐름에 노드를 추가하는 환경으로 적용한다.



출처 : https://blog.apigee.com/detail/api_platform_update_api_proxy_editor_traffic_composition_reports_updated_policies

 

Routing

들어온 메세지에 대해서 라우팅 작업을 한다. 예를 들어서 결재 시나리오에서 API request 필드에 국가 필드가 나뉘어져 있으면, 국가 필드에 따라서 API를 그 국가의 서버로 라우팅을 하거나, 헤더에 정의된 API버전에 따라서 구 API서버나 새로운 버전의 API서버로 라우팅 하는 시나리오를 예로 들 수 있ㄷ.

Function adding

이 기능은 기존에 있던 API 기능에 새로운 기능을 추가하는 것이다. 예를 들어 기존 구매 프로세스에서 포인트 적립 기능을 추가해주는 것등을 할 수 있다. 기존의 클라이언트나 서버의 구현의 변동없이 새로운 기능을 추가할 수 있다.

Message Transformation

메세지 변경은 들어온 메세지를 다른 포맷으로 변경해주는 것이다. 단순하게는 JSON XML로 변경해준다거나 필드가 틀린것을 맵핑 해준다거나 두 필드를 한필드로 합친다거나 반대로 한필드를 여러 필드로 나눈다거나 하는 등의 기능을 제공 한다.

Monitoring

모니터링은 API 서비스 사업자로써 아주 중요한 위치를 차지하는데, API의 사용량, 어떤 API가 많이 사용되는지, 국가별, APP별 통계량이나 평균 응답 시간등에 대한 리포트는 서비스의 정상적인 상태 체크 뿐만 아니라 향후 API 서비스 개발에 유용하게 사용될 수 있다. 아래는 apigee에서 제공하는 API Traffic 모니터링 화면이다.



출처 : http://apigee.com/docs/enterprise/content/monitor-performance-your-api

 

Monetization

Monetization은 쉽게 이야기 해서 유료화 모델이다. API의 과금 정책 월단위, 사용자 단위, 호출 단말건,호출 건수별 등 다양한 정책을 적용할 수 있다. 이에 더해서 이벤트 가격 적용, 또는 사용자 수나, API 호출 수를 구간 별로 나눠서 금액을 책정하는 등의 다양한 가격 정책을 적용할 수 있는데, 이를 Charging이라고 하며, Charging을 하기 위해서는 누가? 어떤 디바이스가 언제? 어떤 API를 호출했는지 등의 Charging을 하기 위한 기초 로그 데이타를 수집하고, Charging이 끝난후에, 신용카드등을 통해서 Payment를 하게 되면, 서비스 국가에 따라서 세금을 내는 Taxation 그리고 세금을 땐 수익을 다시 API 제공자에게 분배 하는 정산 (Pay out)등의 인프라를 제공한다.

Portal

포탈은 API 사용자가 로그인을 한 후, API에 대한 사용법이나 샘플 또는 테스트 등을 할 수 있는 기능등을 제공한다. API를 만드는 것도 중요하지만 편이성을 제공해서 API사용자가 잘 사용할 수 있데 하는 것이 중요하다.

단순하게 메뉴얼 뿐만 아니라 API사용자 입장에서 얼마나 API를 사용했는지 어떤 API가 많이 호출되었는지등의 자료는 API 사용자 입장에서도 과금에 관련되고 또 향후 서비스 개선을 위한 중요한 자료이기 때문에, API 포탈의 기능은 매우 중요하다.

외부 API 사용자를 위한 기능도 있지만, API를 판매하지 않고, 내부에서 다른 부서에 API를 제공하기 위해서도, 쉽게 API를 사용할 수 있게 함으로써 개발 생산성을 높일 수 있다.

만약에 API 메뉴얼 또는 테스트 사이트를 제공하고 싶은 경우에는 Swagger 라는 오픈소스를 참고해보기를 권장한다. https://github.com/wordnik/swagger-core

Swagger API에 대한 메뉴얼 자동 생성 및 테스트 사이트 생성 기능을 제공한다. 아래는 Swagger에 의해서 생성된 API 메뉴얼 사이트 인데, API에 대한 사용법 및 샘플 데이타를 제공하고, 무엇보다 테스트 호출을 날려볼 수 있는 기능을 제공한다.



출처 : http://swagger.wordnik.com

API Governance

이런 API Platform을 사용함으로써 얻게 되는 부가적인 이득은 API 에 대한 관리이다.

내부적으로 API를 사용하면 가장 크게 문제가 되는 것은 API에 대한 관리인데, 여러 부서가 일을하는 경우, API 의 메세지 포맷이나 API URL Naming convention이 다른 일이 발생한다. 특히 SOA 시절의 WSDL 기반의 WebService를 사용하면 최소한의 표준 준수가 가능했으나 (WebService 자체가 표준이다.) REST/JSON의 경우 WSDL과 같은 메세지 포맷에 대한 표준 자체가 없기 때문에 자유도가 증가하여 표준화가 매우 어렵다.

예를 들어 A부서는 version URI에 넣어서 http://myapi/adduser/version3 와 같은 식으로 서비스하거나, B부서는 version HTTP header에 넣어서 http://myapi/addorder URI로 제공하고http headerx-myapi-version:3 식으로 넣어서 같은 회사인데도 API 메세지 포맷에 대한 통일 성을 읽어 버릴 수 있다.

API플랫폼을 사용하면, 서비스 되는 API는 모두 API 플랫폼을 통해야 함으로 API 플랫폼 관리 조직이 API가 표준에 맞지 않으면 API 플랫폼을 통해서 서비스 하는 것을 거부할 수 있으므로, API 표준 준수를 강요할 수 있는 강제성을 통한 통제성을 확보함으로써 API를 중앙에서 집중 관리할 수 있는 장점이 생긴다.

 

저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

REST API에 대한 보안

아키텍쳐 /대용량 아키텍쳐 | 2013.10.28 00:10 | Posted by 조대협

REST API 보안

조대협

REST API에 대한 보안에 대해서 알아보자. API 에 대한 보안은 인증, 메세지 암호화, 무결성 크게 3가지 관점에서 고민해볼 수 있다.


1)  인증

인증은, REST API를 호출한 사람(클라이언트)가 적절한 사용자 인가를 판단해주는 것이다. 아무나 API를 호출하는 것이 아니라 인증을 받은 사람많이 API를 호출해주게 하는 것인데, 쉽게 생각하면 사용자의 id,passwd로 서비스에 로그인을 하는 개념을 생각할 수 있다.

API Key 방식

API에 대한 인증 방법은 몇가지가 있는데, 그 중에서 가장 기초적인 방법은 API Key를 이용하는 방법이다. API Key, 특정 사용자만 알 수 있는 일종의 문자열이다. 현재 Amazon이 이 방식을 사용하고 있는데 API를 사용하고자 할때, 개발자는 API 제공사의 포탈 페이등에서, API Key를 발급 받고, API를 호출할때, API Key를 메세지 안에 넣어서 호출한다. 서버는 메세지 안에서 API Key를 읽어서, API가 누가 호출한 API인지를 인증하는 흐름이다.

OAuth 방식

가장 기초적이고 구현이 쉬운 방법이지만, 이 방법의 경우에는 표준이 없으며, API 서비스를 제공하는 쪽의 디자인 표준을 따르기 때문에, 근래에는 OAuth라는 형식의 인증 방식을 API 인증에 많이 사용한다. 구글이 이 방식을 사용하는데, OAuth Provider로 부터, 인증을 받으면, OAuth Token이라는 것이 생성된다. API를 호출하는 입장에서는 이 OAuth Token API 호출시 함께 넣어서 호출을 해서, 인증을 받는 방식이다.  OAuth의 경우 API Key 방식보다 조금 더 높은 보안 방식을 제공하고, 표준화가 되어 있기 때문에, API 서비스 제공자 입장에서는 서비스 확산을 위해서 권장되는 방법이다. OAuth의 경우에는 OAuth 인증 표준을 제공하는 인증 서버를 구축해야 하기 때문에, 구현 난이도가 API Key 방식에 비해서 높다.

Bi-diretional Certification

가장 높은 수준의 인증 방식을 제공할 수 있는 개념으로, 보통 HTTPS 통신을 사용할때, 서버에 인증서를 놓고, 단방향으로 SSL을 제공한다. 이 방식은 클라이언트에도 인증서를 놓고, 양방향으로 SSL을 제공하면서, API 호출에 대한 인증을 클라이언트의 인증서를 통해서 하는 방식이다. 가장 구현 방법이 복잡한 방식이기는 하지만, 공인 기관에서 발행된 인증서를 사용한다면 API 를 호출하는 쪽의 신원을 확실하게 할 수 있고, 메세지 까지 암호화 되기 때문에, 가장 높은 수준의 인증을 제공한다. 이런 인증 방식은 일반적인 서비스에서는 사용되지 않으며, 몇몇 높은 인증 수준을 제공하는 서비스나 특정 서버간의 통신을 위해서 사용 하는 것이 좋다.


2)  메세지 암호화

API 내용안에 다른 사람이 봐서는 안될 내용이 있다면 API 메세지를 암호화 해야 한다. 메세지를 암호화 하는 방법은 몇가지 접근 방법이 있는데, 그중에서 가장 손쉬운 방법이 HTTPS를 사용하는 방식이다. 이 방식의 경우에는 서버 설정만으로도 가능하고, 가장 널리 쓰이는 방식이기 때문에 신뢰할만하다.

그런데, HTTPS는 인증서를 이용해서, 초기 통신시에 SSL handshake를 통해서 Secure Connection을 제공하는 방식인데, HTTPS Client Server 사이에서 인증서를 바꿔치면, 중간에 이 인증서를 바꿔친 사람은 메세지를 볼 수 있다. 이를 Man in middle attack이라고 하는데, Server A Client B에게 인증서를 내려줄때, 중간에 Hacker C Server A의 인증서를 자신의 인증서로 바꿔서 Client B에게 전달한다. Client B Hacker C의 인증서로 메세지를 암호화해서 보내면, Hacker C가 중간에 메세지를 자신의 인증서를 이용해서 복호화해서 본후에, Server A가 보낸 원래 인증서로 다시 암호화 해서, 보내면, Server A는 문제가 없다고 판단한다. 실제로 중국 정부의 경우, 중국의 외부에서 들어오는 Traffic을 이러한 방법으로, 인증서를 바꿔쳐서 메세지를 복호화해서 필터링한다고 한다.

이런문제를 해결 하기 위해서는 Server A 에서 발급하는 인증서가 공인된 인증 기관의 인증서임을 확실하게 해주면 된다. 이렇게 인증서를 공인해주는 기관을 CA라고 하는데 Verisign 등이 대표적인 기관이다. 이렇게 공인된 인증서인지는 Client B가 인증서를 받아보고, 해당 인증서가 공인된 기관에서 발급된 인증서인지를 확인하면 된다. (클라이언트 쪽에, 인증서가 공인된 인증서인지를 확인하는 로직을 넣으면 Man in middle attack을 방지할 수 있다.)

데이타 레벨의 암호화

다음으로는 간단하게 암호화가 필요한 특정 필드만 애플리케이션단에서 암호화 해서 보내는 방법이 있다.



http://www.javamex.com/tutorials/cryptography/ciphers.shtml 를 참고하면, 대칭키 기반의 암호화 알고리즘 속도를 비교해놓은 것이 있다. 일반적으로 AES256을 사용하면 빠른 암호화 속도와 높은 보안성을 보장 받을 수 있다. (위, 대칭키 기반의 암호화 알고리즘 속도 비교)


3)  무결성 보장

무결성이란, 서버 입장에서 API 호출을 받았을때, 이 호출이 신뢰할 수 있는 호출인지, 아닌지를 구별하는 방법이다. Hacker가 중간에서 메세지를 가로챈후에, 내용을 변조해서 Server에 보냈을때, 내용이 변조되었는지 여부를 판단하는 방법인데, 일반적으로 HMAC을 이용한 방식이 널리 사용된다. 어떤 원리로 작동하는지 살펴보도록 하자.

먼저 전제는 Rest API를 호출하는 클라이언트와, 서버간에는 대칭키 기반의 암호화 키 “Key”를 가지고 있다고 전제하자. Key는 앞에서 설명한 API Key가 될 수 도 있고, OAuth 토큰이 될 수 도 있고, 또는 사용자가 임의로 정한 키가 될 수 도 있다.



(1) 먼저 클라이언트는 호출하고자 하는 REST API URL을 앞에서 정의한 Key를 이용해서 HMAC 알고리즘을 사용하여 Hash 값을 추출한다.

* 중요 : 여기서는 편의상 URL을 가지고 HMAC 해쉬를 생성하였는데, 전체 메세지에 대한 무결성을 보장하려면 URL이 아니라 메세지 전문 자체에 대해서 Hash를 추출해야 한다.

(2) 그리고 API를 호출할때, 메세지 (또는 URL)에 이 추출한 HMAC을 포함해서 호출한다.

(3) 서버는 호출된 URL을 보고 HMAC을 제외한 나머지 URL을 미리 정의된 Key를 이용해서, HMAC 알고리즘으로 Hash 값을 추출한다.

(4) 서버는 (3)에서 생성된 HMAC값과, API 호출시 같이 넘어온 HMAC 값을 비교해서, 값이 같으면 이 호출을 유효한 호출이라고 판단한다.

만약에 해커가 메세지를 중간에서 가로채서 변조하였을 경우, 서버에서 Hash를 생성하면 변조된 메세지에 대한 Hash가 생성되기 때문에 클라이언트에서 변조전에 보낸 Hash 값과 다르게 된다. 이를 통해서 메세지가 변조되었는지 여부를 판단할 수 있다.

Time stamp를 이용한 replay attack의 방지

그런데, 위의 알고리즘 상의 문제는, 만약에 메세지를 변경하지 않고, Hacker가 동일한 요청을 계속 보낸다면? 메세지를 변조하지 않았기 때문에 서버는 이를 유효한 호출로 인식할 수 있다. 이를 replay attack이라고 하는데 이를 방지하기 위해서는 time stamp를 사용하는 방법이 있다.

HMAC을 생성할때, 메세지를 이용해서만 Hash 값 생성하는 것이 아니라, timestamp를 포함해서 메세지를 생성하는 것이다.

HMAC (Key, (메세지 데이타+timestamp) )

그리고, API를 호출할때, timestamp 값을 같이 실어서 보낸다.

http://service.myapi.com/restapiservice?xxxxx&hmac={hashvalue}&timestamp={호출시간}

이렇게 하면 서버는 이 메세지가 호출된 시간을 알 수 있게 되고, 호출된 시간 +- 10(아니면 개발자가 정한 시간 폭) 만큼의 호출만 정상적인 호출로 인식하고 시간이 지난 호출의 메세지는 비 정상적인 호출로 무시해버리면 된다.

* 참고 : Hacker timestamp URL등을 통해서 볼 수 있다고 하더라도, Key값을 모르기 때문에, timestamp 를 변조할 수 없다. timestamp를 변조할 경우에는 원본 Hash가 원본 timestamp로 생성되었기 때문에, timestamp가 변조된 경우 hash 값이 맞지 않게 된다.

HMAC을 구현하는 해쉬 알고리즘은 MD5,SHA1등이 있는데, 보통 SHA-1 256bit 알고리즘을 널리 사용한다.

HMAC 기반의 REST Hash 구현 방법은

http://www.thebuzzmedia.com/designing-a-secure-rest-api-without-oauth-authentication/

에 설명이 잘나와 있으니 참고하기 바란다.

또한 HMAC 알고리즘 구현은 위키 http://en.wikipedia.org/wiki/HMAC 를 보면 각 프로그래밍 언어별로 예제 링크가 나와 있으므로 참고하기 바란다.

지금까지 간단하게 나마 REST API 보안 방식에 대해서 알아보았다. 보통 REST API를 디자인하는데, 가장 신경 안쓰는 부분 중의 하나가 API 에 대한 보안이다. 대신 한번 뚤리기 시작하면 걷잡을 수 없이 문제를 만드는 부분이 또 보안이다. API 디자인시에는 반드시 보안에 대한 부분을 고려하고, 위의 3가지 관점에 따라서 API 보안을 반영하기 바란다.


글이 예전 내용이라서 새 글이 업데이트 되었씁니다.

REST API 이해와 설계 - #1 개념 잡기 http://bcho.tistory.com/953

REST API 이해와 설계 - #2 디자인 가이드  http://bcho.tistory.com/954

REST API 이해와 설계 - #3 보안 가이드  http://bcho.tistory.com/955



저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License



저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

Open API design

아키텍쳐 /SOA | 2013.07.06 13:14 | Posted by 조대협

OPEN API Design Consideration Point


1. API Type 

Internal APIs

Internal System in same network

User for internal user or authorized user

 

External APIs

for Public Developer

Developer Support is key point

 

for Partner

- Business value is key

 

2. API Design Best Practice

Easy to use

with out documentation, API should be easy enough to use.

Easy to Try and use

For private APIs, it is important to demonstrate real value in running a system or app off of the API

public APIs, one approach is to offer some level of free access. Many developers won’t even consider using your API if there are only paid options. At this point in the customer acquisition cycle, the developer may not know if your API meets their needs or if their app’s business model will support paid access.

 

Diffentiate API

Ÿ   The data is unique, more complete, or more accurate than that of your competitors

Ÿ   You offer better support or an easier signup process

Ÿ   Your API is more robust, reliable, cooler, or faster than alternatives

Ÿ   Your terms are more developer friendly; perhaps you offer more data traffic for free or at better rates

Provide mandatory functionality first time and enhance later

There are other aspects of simplicity that can help make your API easier to adopt. One success factor is to stick to conventions that the developer might already know

Target Specific Developer Segment 

3. Technology Selection

REST API

pragmatic REST API design

Figure 1 Wrong design for shopping cart

 

Figure 2 Good design for shopping cart

Binary protocol

Ÿ   Protocol Buffer

Ÿ   Thrift

 

Versioning

Having mediation layer like ESB

4. Security

Identification

- grant access by using API Key

Authenticate

- Determine who is accessing API.

Ÿ   User name & password

Ÿ   Session based

After log in with user name & password, session token key will be issued. with the session key authentication can be passed

Ÿ   Standard way

SAML

OAuth

 

Authentication vs Identification

Identification based API provides same functionalities and data to client.

Authentication based API provides user dependent data to client

Encryption

use SSL (can be attacked by Man in middle attack)

partial or all message encryption

 

Threat Detection and prevention

 

General recommendation of Security

API Keys for only nonsensitive & read-only data.

Use OAuth 2.0 for more sensitive authentication

use SSL for everthin sensitive data.

Sanitize incoming and outgoing data - prevent javascript attack and SQL attack. (especially in writable API)

 

5. Legal Consideration

 

Consideration point

- What content will be provided

- What right can u or do u want to grant the consumer of API

- Are they have different level of API access?

 

Perspective

Ÿ   Contract & Terms of Use

Ÿ   Privacy Policy

Ÿ   Data retention policy

 

6. API Operation

Monitoring

Throttling (Traffic management)

Depends on contract & consumer, API traffic can be managed. It is important to monetization

Reporting

SLA

Issue management

Internal operation issue tracking

Support

Ÿ   Issue management & ticketing

Ÿ   Documentation

Ÿ   Sand box

 

7. API Measurement

Effectiveness measurement - call per day

Effective measurement - call per day, call per client (by region ,model etc)

Performance measurement - response time

 

 

 참고

http://www.layer7tech.com/

http://www.apigee.com

 

 

저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'아키텍쳐  > SOA' 카테고리의 다른 글

Microservice architecture note  (0) 2014.08.20
Open API design  (0) 2013.07.06
통신 사업자의 SDP의 필수 컴포넌트  (0) 2010.08.03
SDP (Service Delivery Platform)  (1) 2009.09.15
모차세대 시스템의 WAS 아키텍쳐 Blue Print  (8) 2009.07.30
EAI관점에서 본 SOA  (6) 2009.07.29
 

티스토리 툴바