가장 기초적인 API 보안의 형태는 HTTP Basic Authentication(HTTP 기본 인증)
이다. 이 방식은 매우 간결하게 작동하기 때문에 API 서버를 만드는 사람이나 API를 활용할 개발자 모두에게 편리함을 제공해준다.
- 개발자에게 API키(보통 ID와 PW)가 주어진다.
- 개발자는 발급받은 API키를 자신의 서버 내 안전한 곳에 보관해야 할 책임이 있다.
- 개발자는 API키를 HTTP 인증 헤더에 담아 API 서버에 request를 날린다.
- API 서버에서는 HTTP 헤더를 찾아내면, base64 디코딩을 하여 API키로부터 ID와 PW를 뽑아내고 검증을 한 뒤에 응답을 계속 진행할 것인지 결정한다.
HTTP 기본 인증 방식은 간결하면서 훌륭한 인증방식이다. 개발자는 API키를 발급받는 것만으로 API 서비스를 간편하게 사용할 수 있다.
하지만 HTTP 기본 인증 방식이 모바일 앱에 적합하지 않은 이유는 API키를 모바일 기기 내에 안전하게 저장하는 이슈 때문이다. 또한 HTTP 기본 인증 방식은 Raw한 API키를 매 요청때마다 사용해야 하므로, 시간이 갈수록 취약점 공격에 노출될 가능성이 높아진다. 또한 API키가 내장된 채로 배포된 앱을 리버스 엔지니어링을 하게 되면 API키를 알아내는 것이 가능해지기 때문에 API 서비스가 악용될 소지가 있다. 대부분의 경우 다수에게 배포되는 모바일 앱 내에 안전하게 API 키를 보관하는 방법은 없다고 볼 수 있다. 때문에 HTTP 기본 인증 방식은 웹 브라우저나 모바일 앱과 같은 신뢰하기 힘든 환경에 적합한 인증 방식은 아니다.
OAuth2는 신뢰하기 어려운 기기(모바일)에서 사용되는 API 서비스의 보안을 위한 훌륭한 프로토콜이다. 또한 모바일 사용자를 인증할 수 있는 수단으로 토큰 인증방식을 제공한다. 사용자의 관점에서 OAuth2 토큰 인증은 다음과 같이 진행된다. (OAuth2는 이 프로세스를 Password Grant Flow
라고 부른다.)
- 사용자가 모바일 앱을 실행하고, ID 혹은 메일 주소와 비밀번호를 입력하는 창이 뜬다.
- 모바일 앱에서 입력 받은 사용자의 정보를 POST 방식으로 API 서버로 보낸다.(SSL)
- 유저 인증값을 검증하고 일정 시간이 지나면 만료될 액세스 토큰을 생성한다.
- 발급받은 액세스 토큰을 모바일 기기에 저장한다. API 토큰과 마찬가지로 보안이 적용되는 안전한 장소에 보관해야 하며, API 서비스에 접근할 때 이 토큰을 사용한다.
- 액세스 토큰이 만료되면 더이상 작동하지 않으며, ID 혹은 메일 주소와 비밀번호를 입력하는 창을 다시 띄운다.
OAuth2가 API 보안에 좋은 이유는 API키 자체를 안전하지 않은 환경에 저장할 필요가 없기 때문이다. 대신에 액세스 토큰을 생성하여 안전하지 않은 환경에 임시로 저장해놓기만 하면 된다. 공격자가 액세스 토큰에 접근할 수 있게 되더라도 일정 시간이 지나면 만료되기 때문에 잠재적인 피해를 줄일 수 있다.
OAuth2에서 API에 접근할 때 필요한 액세스 토큰을 발급 받았다면, 모바일 기기 어딘가에 저장을 해야 한다. 토큰 저장소는 개발을 진행하는 플랫폼에 따라 정해진다. 예를 들어 안드로이드 앱
을 개발하고 있다면 모든 액세스 토큰을 SharedPreference
에 저장할 것이다. iOS
의 경우 액세스 토큰을 Keychain
에 저장하게 된다.
액세스 토큰은 JWT(Json Web Token)를 주로 사용하는데, JWT는 다음과 같은 특징을 갖는다.
- 클라이언트로 발행될 수 있다.
- 당신이 생성했다는 사실을 증명할 수 있다.(서명)
- 일정 시간 뒤에 자동으로 만료된다.
- JSON 타입으로 변수 정보를 저장할 수 있다.
- 서버에서 쿼리하지 않아도 사용자를 로컬에서 검증할 수 있기 때문에 API 호출의 횟수를 줄일 수 있다.
JWT는 다음과 같은 방식으로 항상 암호화 서명을 사용한다.(Cryptographically signed).
- 보안을 위해 램덤 문자열을 생성하고, API 서버에 저장한다.
- 새 JWT를 만들 때 이 문자열을 JWT 라이브러리로 전달하여 저장할 JSON데이터(ID, 권한 등)과 함께 토큰에 서명한다.
- 토큰이 생성되면 다음과 같이 나타난다.
header
.payload
.signature
- 헤더와 페이로드(claims) 그리고 서명은 base64로 인코딩 된 긴 문자열 형태로 나타난다.
모바일 클라이언트에서도 JWT에 저장된 내용을 볼 수 있다. JWT 라이브러리가 있으면 JWT 내부의 JSON 데이터를 쉽게 확인할 수 있기 때문이다. JWT는 토큰 만료 기능을 기본적으로 지원하기 때문에 토큰만을 가지고 토큰의 유효성을 확인할 수 있다. 따라서 JWT 라이브러리를 사용한다면 API 호출 없이도 로컬에서 JWT의 유효성을 검사할 수 있다. JWT인증 시스템은 서버단에서도 훌륭한 작업을 수행한다. 만약 모바일 멀웨어나 해커가 토큰을 조작했다면 서버에서는 JWT를 수신하고 검증할 때 다음과 같은 작업을 수행한다.
- 서버만 알고 있는 비밀 문자열을 사용해 토큰이 훼손되지 않았는지 체크한다.(앞서 보았던 암호서명(signature)을 이용)
- JWT의 만료 시간을 체크하여 아직 유효한 JWT인지 검증한다.
JWT의 이러한 기능은 인증 / 만료 / 보안을 매우 간결하게 만들어준다. JWT를 이용해 서비스를 개발할 때 명심해야 할 점은 오직 하나이다.
유출되어도 상관없는 정보만 저장한다.
1. 유저가 앱을 실행
2. 앱이 인증 정보를 요구
OAuth2의 암호 부여 타입 스키마를 사용해 사용자 인증을 하기 때문에 ID와 PW가 필요하다.
3. 사용자가 인증 정보를 입력
4. 앱이 API 서버로 POST request를 전송
OAuth2 프로세스가 시작되는 부분
단순히 API 서버로 HTTP Post 요청만 전송
5. API 서버에서 유저 인증
사용자의 계정과 PW가 일치하는지 확인
6. API 서버에서 모바일 앱에 저장할 JWT를 생성
1. OAuth2 인증을 통과했기 때문에 액세스 토큰을 발급해 모바일 앱으로 전달
2. ID와 사용자 권한 그리고 앱에서 바로 접근할 필요가 있는 어떤 정보든 JSON 타입으로 담는다.
3. JWT를 생성한다.
7. 앱에서 서버로 인증된 요청을 보낸다.
저장된 JWT를 사용해 HTTP 인증 헤더를 만들고 API 요청을 보내 사용자를 인증할 수 있도록 한다.
8. 마지막으로 서버단에서는 다음과 같은 활동을 수행한다.
- HTTP 인증 헤더 값을 검사하여 Bearer가 들어있음을 확인한다.
- 뒤에 따라오는 문자열 값을 가져와 액세스 토큰으로 인식한다.
- JWT 라이브러리를 사용해 액세스 토큰이 유효하며, 서명되어있고, 만료되지 않았는지 검사하고 검증한다.
- 사용자의 ID와 권한을 확인한다.
- 사용자의 계정을 데이터베이스로부터 가져온다.
- 모든 것이 통과된다면 API 요청에 맞는 응답을 한다.