REST API 보안
- 보안에 관해서는 백번, 천번을 강조해도 과함이 없다.
- 근래의 대부분의 서비스 시스템들은 API를 기반으로 통신한다.
- 앱과 서버 간의 통신 또는 자바 스크립트 웹 클라이언트와 서버 간 대부분의 통신이 이 API들을 이용해서, 이루어지기 때문에 한번 보안이 뚫리면 개인 정보가 탈취되는 것 뿐만 아니라, 더 많은 문제를 일으킬 수 있다.
REST API 보안 관점 및 개요
인증(Authentication)
- 인증은 누가 서비스를 사용하는지를 확인하는 절차이다.
- 쉽게 생각하면 웹 사이트에 사용자 아이디와 비밀번호를 넣어서 사용자를 확인하는 과정이 인증이다.
- API도 마찬가지로 API를 호출하는 대상 (단말이 되었던 다른 서버가 되었던 사용자가 되었건) 확인하는 절차가 필요하다.
- 스프링 시큐리티에서는 인증을
who are you?
라고 표현하고 인가를what are you allow to do?
라는 문장으로 비유하고 있다.
인가(Authorization)
-
인가는 해당 리소스에 대해서 사용자가 그 리소스를 사용할 권한이 있는지 확인하는 권한 체크 과정이다.
-
예를 들어서,
/users
라는 리소스가 있을 때, 일반 사용자 권한으로는 내 사용자 권한만 볼 수 있지만, 관리자 권한으로는 다른 사용자 정보를 볼 수 있는 것과 같은 권한의 차이를 의미한다.
네트워크 레벨 암호화
-
인증과 인가 과정이 끝나서 API를 호출하게 되면, 네트워크를 통해서 데이터가 오가는데, 해커나 누군가 중간에서 이 네트워크 통신을 낚아채 (감청) 데이터를 볼 수 없게 할 필요가 있다.
-
이를 네트워크 프로토콜단에서 처리하는 것을 네트워크 암호화라고 하는데,
HTTP
에서의 네트워크 암호화는 일반적으로HTTPS
기반의 프로토콜을 사용한다.
메시지 무결성 보장
-
메시지 무결성이란, 메시지가 해커와 같은 외부 요인에 의해서 중간에 변조가 되지 않게 방지하는 것을 말한다.
-
무결성을 보장하기 위해서 많이 사용하는 방식은 메시지에 대한 서명(Signature)를 생성해서 메시지와 같이 보내고 검증하는 방식이다.
-
예를 들어 메시지 문자열이 있을 때, 이 문자열에 대한 해시 코드를 생성해서 문자열과 함께 보내고 이 받은 문자열로 생성한 해시 코드를 문자열과 함께 온 해시 코드와 비교하는 방법이다.
-
만약에 문자열이 중간에 변조되었으면 원래 문자열과 함께 전송된 해시 코드와 맞지 않기 때문에 메시지가 중간에 변조되었는지 확인할 수 있다.
-
메시지 무결성의 경우 앞에서 언급한 네트워크 레벨의 암호화를 완벽하게 사용한다면 외부적인 요인(해커)에 의해서 메시지를 해석당할 염려가 없으므로 사용할 필요가 없다.
메시지 본문 암호화
-
네트워크 레벨의 암호화를 사용할 수 없거나, 또는 이를 신뢰할 수 없는 상황에서 추가로 메시지 자체를 암호화하는 방법을 사용한다.
-
이는 애플리케이션 단에서 구현하는데, 전체 메시지를 암호화하는 방법과 특정 필드만 암호화하는 방식 두 가지로 접근할 수 있다.
-
전체 메시지를 암호화할 경우 암호화에 드는 비용이 많은 뿐더러 중간에
API Gateway
를 통해서 메시지를 열어보고 메시지 기반으로 라우팅 변환 작업이 어렵기 때문에 일반적으로 전체 암호화보다는 보안이 필요한 특정 필드만 암호화하는 방식을 사용한다.
인증(Authentication) 방식의 종류
API
에 대한 인증 방식은 여러 가지 방식이 있으며 각 방식에 따라 보안 수준과 구현 난도가 달라서 각 방식의 장단점을 잘 이해하여 서비스 수준에 맞는 적절한 API 인증 방식을 선택하도록 할 필요가 있다.
API 키 방식
- 가장 기초적인 방법은
API
키를 이용하는 방법이다. - API 키(Key)란 특정 사용자만 알 수 있는 일종의 문자열이다.
API
를 호출하고자 할 때, 개발자는API
제공사의 포탈 페이지에서API
키를 발급 받고 API를 호출 할 때 API 키를 메시지 안에 넣어 호출한다.- 서버는 메시지 안에서 API 키를 읽어서 이 API를 누가 호출한 API인지를 인증하는 흐름이다.
- 모든 클라이언트가 같은 API 키를 공유하기 때문에, 한번 API 키가 노출되면 전체 API가 뚫려버리는 문제가 있으므로 높은 보안 인증이 필요할 때에는 권장하지 않는다.
API 토큰 방식
-
API 토큰(
Token
)을 발급하는 방식이 있는데, ID, 비빌번호 등으로 사용자를 인증한 다음에 그 사용자가 API 호출에 사용할 기간이 유효한 API 토큰을 발급해서 API 토큰으로 사용자를 인증하는 방식이다. -
매번 API 호출 시 사용자 ID, 비밀번호를 보내지 않고 API 토큰을 사용하는 이유는 사용자 비밀번호는 주기적으로 바뀔 수 있기 때문이고, 매번 네트워크를 통해서 사용자 ID와 비밀번호를 보내는 것은 보안상 사용자 계정 정보를 탈취당할 가능성이 크기 때문에 API 토큰을 별도로 발급해서 사용하는 것이다.
-
API
토큰을 탈취당하면,API
를 호출할 수는 있지만, 반대로 사용자 ID와 비밀번호는 탈취당하지 않는다. -
사용자 비밀번호를 탈취당하면 일반적으로 사용자들은 다른 서비스에도 같은 비밀번호를 사용하는 경우가 많아서 연쇄적으로 공격을 당할 가능성이 커진다. 따라서 이러한 연쇄적인 피해를 최소화 할 수 있다.
-
위의 그림을 보면 다음과 같은 형태로 인증이 이루어진다.
-
- API 클라이언트가 사용자 ID, 비밀번호를 보내서 API 호출을 위한 API 토큰을 요청한다.
-
- API 인증 서버는 사용자 ID, 비밀번호를 바탕으로 사용자를 인증한다.
-
- 인증된 사용자에 대해서 API 토큰을 발급한다 (유효시간을 가지고 있다.)
-
- API 클라이언트는 이 API 토큰으로 API를 호출한다. API 서버는 API 토큰이 유효한지를 API 토큰 관리 서버에 문의하고 API 토큰이 유효하면 API 호출을 받아들인다.
-
보안 수준에 따른 사용자 인증 방식
HTTP Basic Auth
-
가장 기본적이고 단순한 형태의 인증 방식으로, 사용자 ID와 비밀번호를 가지고, HTTP 헤더에
Base64
인코딩 형태로 넣어서 인증을 요청한다. -
예를 들어서 사용자 ID가 “terry"이고 비밀번호가 “hello world” 일 때 다음과 같이
HTTP
헤더에 “terry: hello world” 라는 문자열을Base64
인코딩을 해서Authorization
라는 이름의 헤더로 서버에 전송하여 인증을 요청한다.
Authorization: Basic ZG9uZ3dvb2s6cGFzc3dvcmQ=
- 다음은 Base64 인코딩을 도와주는 사이트이다. https://www.base64decode.org/
- 중간에 패킷을 가로채서 이 헤더를 Base64로 디코딩하면 사용자 ID와 비밀번호가 그대로 노출되기 때문에, 반드시
HTTPS
프로토콜을 사용해야한다.
Digest Access Authentication
-
HTTP Basic Auth가 Base64 형태로 비밀번호를 실어서 보내는 단점을 보강하여 나온 인증 프로토콜이
Digest Access Authentication
이라는 방법으로, 기본 원리는 클라이언트가 인증을 요청할 때, 클라이언트가 서버로부터nonce
라는 일종의 난수 값을 받고 (클라이언트와 서버가 이 난수 값을 알 고 있다.) 사용자 ID와 비밀번호를 이 난수 값을 이용해서 해시화하여 서버로 전송하는 방식 -
이 경우에는 직접 ID와 비밀번호가 평문 형태로 날아가지 않기 때문에, 해커가 중간에 비밀번호를 탈취할 수 없고, 설령
HASH
알고리즘을 알고 있다고 하더라도 해시된 값에서 반대로 비밀번호를 추출하기가 어려워서Basic Auth
방식보다 향상된 보안을 제공한다.
-
흐름은 다음과 같다.
-
- 클라이언트가 서버에 특정 리소스를 요청한다.
-
- 서버는 해당 세션에 대한
nonce
값을 생성하여 저장하고, 클라이언트에게 반환한다. 이때realm
을 같이 반환하는데,realm
은 인증의 범위로, 예를 들어 웹 서버에 car.war, market.war가 각각http://myweb/car
,http://myweb/market
이라는URL
로 배포되었다고 하면, 이 웹사이트는 각각 애플리케이션car.war
와market.war
에 대해서 서로 다른 인증realm
을 가진다.
- 서버는 해당 세션에 대한
-
- 클라이언트는 앞에서 서버로 부터 받은
realm
과nonce
값으로 해시 값을 생성하는데 다음을 통해서 응답 해시 값을 생성한다.
- 클라이언트는 앞에서 서버로 부터 받은
HA1 = MD5(사용자 이름:realm:비밀번호) HA2 = MD5(HTTP method:HTTP URL) response hash = MD5(HA1:nonce:HA2)
-
- 서버는 3번에서 전달된
nonce
값을 이 세션을 위해서 서버에 저장된nonce
값과 같은지 비교를 한 후, 전달된 사용자 이름인terry
와nonce
값 그리고 서버에 저장된 사용자 비밀번호를 이용해서 같은 3번과 같은 방식으로 해시 값을 계산하여 클라이언트에서 전달된 해시 값과 같은지를 비교한다.
- 서버는 3번에서 전달된
-
클라이언트 인증 추가
- 추가적인 보안 강화를 위해서 사용자 인증 뿐만 아니라, 클라이언트 인증 방식을 추가할 수 있다.
- 페이스북은 API 토큰을 발급 받도록 사용자 ID, 비밀번호 뿐만 아니라,
Client ID
와Client Secret
이라는 것을 같이 입력받도록 하는데,Client ID
는 특정 앱에 대한 등록 ID이고Client Secret
은 특정 앱에 대한 비밀번호로, 앱을 등록하면 앱 별로 발급되는 일종의 비밀번호이다. - API 토큰을 발급 받을 때, Client ID와 Client Secret을 이용하여 클라이언트 앱을 인증하고, 사용자 ID와 비밀번호를 추가적으로 받아서 사용자를 인증하여 API 액세스 토큰을 발급한다.
IP 화이트 리스트를 이용한 터널링
-
만약 API를 호출하는 클라이언트의 API가 일정하다면 사용할 수 있는 손쉬운 방법인데, 서버 간의 통신이나 타사 서버와 자사 서버 간의 통신 같은 경우에 API 서버는 특정 API URL에 대해서 들어오는 IP 주소를 화이트 리스트로 유지하는 방법이 있다.
-
API 서버 앞단에
HAProxy
나Apache
와 같은 웹 서버를 배치하여서 특정 URL로 들어올 수 있는 IP 목록을 제한하거나 아니면 전체API
가 특정 서버와의 통신에만 사용된다면 아예 하드웨어 방화벽 자체에 들어올 수 있는 IP 목록을 제한할 수 있다. -
설정만으로 가능한 방법이기 때문에, 서버간의 통신이 있을 때 적용할 것을 권장한다.
Bi-directional Certification (Mutual SSL)
-
보통은
HTTPS
통신을 할 때, 서버에 공인 인증서를 놓고 단방향으로SSL
을 제공하는데,Bi-directional Certification
(양방향 인증서 방식) 방식은 클라이언트에도 인증서를 놓고 양방향으로SSL
을 제공하면서API
호출에 대한 인증을 클라이언트의 인증서를 이용하는 방식이다. -
구현 방법이 가장 복잡한 방식이기는 하지만, 공인 기관에서 발생된 인증서를 사용한다면
API
를 호출하는 쪽의 신원을 확실하게 할 수 있고 메세지까지 암호화되기 때문에, 가장 높은 수준의 인증을 제공한다. -
이런 인증 방식은 일반 서비스에서는 사용되지 않으며 높은 인증 수준을 제공하는 몇몇 서비스나 특정 서버 통신에 사용하는 것이 좋다.
제 3자 인증 방식 (OAuth 2.0 Authorization grant type)
- 제 3자 인증 방식은 페이스북이나, 구글, 트위터 같은 API 서비스 제공자들이 인증을 대신 해주는 것이다.
Claim 기반의 JWT 방식
Claim 기반 토큰의 개념
-
OAuth
에 의해서 발급되는access_token
은 랜덤 문자열로, 토큰 자체에는 특별한 정보를 가지고 있지 않은 일반적인 스트링 형태이다. -
따라서
access_token
을 통해서 사용자와 연관된 정보를 구별하여 이를 허용해주는 구조인데, 서버 입장에서는 토큰을 가지고 그 토큰과 연관된 정보를 서버 쪽에서 찾아야 한다. -
하지만
JWT
는Claim
기반이라는 방식을 사용하는데,Claim
은 사용자에 대한 프로퍼티나 속성을 말한다. -
토큰 자체가 정보를 가지는 방식인데,
JWT
는 이 클레임을JSON
을 이용해서 정리한다. 아래와 같은 형태로 표현된다.
{
"id": "terry",
"role": "["admin", "user"]",
"company": "pepsi"
}
- 이 토큰을 이용해서 요청을 받는 서버나 서비스 입장에서는 이 서비스를 호출한 사용자에 대한 추가 정보는 이미 토큰 안에 다 들어가 있기 때문에, 다른 곳에서 가져올 필요가 없다는 장점이 있다.
-
결과적으로 토큰을 생성하는 단계에서는 생성된 토큰을 별도로 서버에서 유지할 필요가 없으며, 토큰을 사용하는
API
서버 입장에서는 API 요청을 검증하기 위해서 토큰을 가지고 사용자 정보를 별도로 계정 시스템 등에서 조회할 필요가 없다는 것이다. -
JWT
는 이JSON Claim
을Base64
로 인코딩하여HTTP
헤더에 쉽게 넣을 수 있으며,JSON
기반이기 때문에 파싱과 사용이 쉽다. -
결과적으로
Claim
기반의 토큰은 토큰 자체가 정보를 담음으로써 토큰으로 서비스나API
접근을 제어할 때, 별도의 작업이 서버에서 필요하지 않으며, 토큰 자체를 서버에서 관리할 필요가 없어서 구현이 상대적으로 단순해진다.
JWT의 단점
-
길이 :
Claim
에 넣는 데이터가 많아질 수록JWT
토큰의 길이가 길어진다.API
호출에 사용할 시에 호출마다 헤더에 붙어서 가야하기 때문에 길이가 길다는 것은 그만큼 네트워크 대역폭 낭비가 심하다는 것이다. -
한번 발급된 토큰은 값을 수정하거나 폐기가 불가 :
JWT
는 토큰 내에 모든 정보를 다 가지고 있기 때문에 한 번 발급된 토큰에 대한 변경은 서버에서는 더는 불가능하다. 예를 들어서, 토큰을 잘못 발행해서 삭제하고 싶더라도 서명만 맞으면 맞는 토큰으로 인식하기 때문에 서버에서는 한번 발급된 토큰의 정보를 바꾸는 일이 불가능하다. -
따라서 만약
JWT
를 사용한다면 만료 시간(Expire Time
)을 꼭 명시적으로 두도록 하고Refresh Token
등을 이용하여 중간마다 토큰을 재발행하도록 해야한다. -
암호화 :
JWT
는 기본적으로Claim
에 대한 정보를 암호화하지 않는다, 단순히Base64
로 인코딩만 하기 때문에, 중간에 패킷을 가로채거나 기타 방법으로 토큰을 취득했으면 토큰 내부 정보를 통해서 사용자 정보가 노출될 가능성이 있다. 따라서 이를 보완하는 방법으로 토큰 자체를 암호화하는 방법이 있고,JSON
을 암호화하기 위한 스펙으로는JWE
가 있다.
참고 문헌
>> Home