- 목적과 사용자의 요구사항
- 일관된 인터페이스를 통해 사용자가 예측 가능하게 동작하는 API
- RESTful 또는 GraphQL 선택
- 인증 및 보안
- 에러 처리
- 성능 최적화 / 가용성과 확장성 / 유지 보수
📍 인증 (Authentication)
🧐 Basic Authentication
- 클라이언트가 서버에 요청을 보냅니다.
- 서버는 요청 헤더에 "Authorization" 필드를 확인합니다.
- "Authorization" 필드에는 "Basic"이라는 인증 방법과 사용자 이름과 비밀번호의 Base64로 인코딩된 조합이 포함됩니다.
- 서버는 Base64 디코딩을 통해 사용자 이름과 비밀번호를 추출합니다.
- 서버는 추출된 사용자 이름과 비밀번호를 사용하여 사용자를 인증합니다.
- BA는 웹 서버와 클라이언트 간의 인증을 위한 간단한 인증 방법 중 하나입니다.
- Basic Auth를 사용하지 말고 표준 인증방식을 사용하세요. (ex. JWT, OAuth 등)
- HTTP 요청 Header에 사용자 정보(ID와 PW)를 포함시켜 서버에 자격 증명을 제공합니다.
- 보안 수준이 낮아서 암호화되지 않은 텍스트로 인증 정보를 전송하기 때문에 보안에 취학합니다.
- 패킷을 중간에 가로채서 인증정보 알아내면 바로 치고 들어와 버리는 방식이어서 되도록이면 지양
- 대안으로 HTTPS를 쓰는 것은? 서버에 SSL 인증서를 저장 및 관리해야 하는 추가적인 운영 부담이 있고, 결정적으로 SSL을 통해 전송된 트래픽은 캐시되지 않습니다. (이 부분은 잘 생각하셔야 합니다.)
- 또 어떤 분들은 X.509 인증서를 각 클라이언트에 설치하는 TLS 기능을 이용하시기도 하는데요. 해당 인증서가 만료되었을때 수많은 클라이언트에 깔린 인증서를 어떻게 처리하고 재발행할지에 대한 고민이 필요합니다.
- 클라이언트에서 인증서를 관리하는 방식이 서버에서 인증서를 관리하는 것보다 훨씬 힘들고 부담스럽습니다. (유념하세요.)
- 결론은 비보안 컨텐츠들은 HTTP를 통해 트래픽을 받아내고, 인증된 안전한 페이지의 보안은 HTTPS를 혼합해서 사용하세요.
- SSL이든 TLS든 사용 목적을 분명히 한 후 부분적으로 집중화해서 적용하세요. 범용으로 적용하면 성능 손해가 발생합니다.
표준 인증방식
인증, 토큰 생성, 패스워드 저장은 직접 개발하지 말고 표준을 사용하세요.
- 참 중요한 포인트입니다. 적극 동감하는 부분입니다.
- 보안 관련된 체계는 시장에서 널리 인증받고 있는 JWT, OAuth등과 같은 솔루션, 기술을 사용하시기 바랍니다.
- 다시 말해, 직접 보안 레이어를 구현하시기를 되도록이면 피하시기 바랍니다. 잘못하면 Hell gate를 여는 꼴이 됩니다.
- 보안 관련 기술은 차용하고 핵심 서비스 구현에 해당 노력과 시간을 투자하시는게 현명한 결정이라 믿고 있습니다.
🧐 JWT (JSON Web Token)
- 무작위 대입 공격을 어렵게 하기 위해 랜덤하고 복잡한 키값 (JWT Secret)을 사용하세요.
- 요청 페이로드에서 알고리즘을 가져오지 마세요. 알고리즘은 백엔드에서 강제로 적용하세요. (HS256 혹은 RS256)
- 토큰 만료기간 (TTL, RTTL)은 되도록 짧게 설정하세요.
- JWT 페이로드는 디코딩이 쉽기 때문에 민감한 데이터는 저장하지 마세요.
- Amazon Cognito API를 통해 JWT 방식인 Temporary Credentials(토큰 3개)
- 요즘 인증 트랜드가 User base 인증 보다는 Role base 인증 방식이어서 JWT 임시 토큰을 발급하여 관리하는게 안전하다 생각합니다.
- 물론 authorization HTTP Header에 해당 토큰값이 물려서 들어가거나 Session에 물려서 노출될 위험은 있지만, 뒷단에서 Role과 매핑 처리하면 인증과 별도로 권한체크가 수행되어 비인가된 요청은 deny 처리되는 효과도 볼 수 있습니다.
🧐 OAuth
- 허용된 URL만 받기 위해서는 서버단에서 redirect_uri가 유효한지 항상 검증하세요.
- 토큰 대신 항상 코드를 주고 받으세요. (respons_type=token을 허용하지 마세요)
- OAuth 인증 프로세스에서 CSRF를 방지하기 위해 랜덤 해쉬값을 가진 state 파라미터를 사용하세요.
- 디폴트 스코프를 정의하고 각 애플리케이션마다 스코프 파라미터의 유효성을 검증하세요.
- 가장 유명한 인증 방식입니다.
- 적용하기 위한 레퍼런스도 많고, 자료도 풍부한 편입니다.
- 하지만 적용하려면 기본 Auth Flow와 주변 짝투리 지식들을 파악하실 필요는 있습니다.
📍 접근 (Access)
- DDoS나 무작위 대입 공격을 피하려면 요청수를 제한하세요. (Throttling)
- MITM (중간자 공격)을 피하려면 서버단에서 HTTPS를 사용하세요.
- SSL Strip 공격을 피하려면 HSTS 헤더를 SSL과 함께 사용하세요.
- 제 경우는 중간에 API Gateway Layer를 하나 두고, 모든 요청 HTTPS Header에 API Key라는 헤더를 추가했습니다.
- 넘어온 API Key를 통해 Throttling과 모니터링, 통계등 각종 지표 데이터도 수집하는 용도로 사용하고 있습니다.
- 또한 API Key는 CorelationID와 조합해서 사용하면 로깅과 트레이싱에 유용하여 디버깅에 효과적입니다.
- 각 요청 연산에 맞는 적절한 HTTP 메서드를 사용하세요. GET (읽기), POST (생성), PUT (대체/갱신), DELETE (삭제)
- REST 방식으로 API를 구현하면 직관적이고, 좋아 보이긴 합니다.
- But, 구성원간에 합의가 반드시 필요하구요. 해당 기술을 리드하시는 분의 역량과 추진력도 절대적입니다.
- 그리고 REST라는 개념이 정확한 가이드라인이 없어서 당사자의 편한 입장에서 해석하고 적용하려는 경향도 없지 않아 있습니다.
- 실례로 PUT Method는 보안 관련 이슈가 있으니 사용하지 말고 POST로 대체하라는 가이드도 있습니다.
- 여러분이 지원하는 포맷 (예를 들어 application/json이나 application/xml 등)만을 허용하기 위해서는 요청의 Accept 헤더의 content-type을 검증하여 매칭되는게 없을 경우엔 406 Not Acceptable로 응답하세요.
- 클라이언트와 서버간의 capability 교환을 위해 가능한 HTTP OPTIONS Method를 사용하고 있습니다.
- 서버가 지원 가능한 포맷과, 각종 커스텀 헤더들을 OPTIONS Method의 Response로 반환하여 선을 딱 그어 버리는 거죠.
- 구현할 땐 추가적으로 구현해야 하는 수고도 있지만, Front End 개발시 자주 접하는 CORS 이슈도 덩달아 해결할 수 있습니다.
-
요청 받은 POST 데이터의 content-type을 검증하세요. (예를 들어 application/x-www-form-urlencoded나 multipart/form-data 또는 application/json 등)
-
일반적인 취약점들을 피하기 위해선 사용자 입력의 유효성을 검증하세요. (예를 들어 XSS, SQL-Injection 또는 Remove Code Execution 등)
-
URL에는 그 어떤 민감한 데이터 (자격 인증 (crendentials), 패스워드, 보안 토큰 또는 API 키)도 포함하고 있어서는 안되며 이러한 것들은 표준 인증 방식의 헤더를 사용하세요.
📍 서버 처리
- 잘못된 인증을 피하기 위해 모든 엔드포인트가 인증 프로세스 뒤에서 보호되고 있는지 확인하세요.
- 사용자의 리소스 식별자를 사용하는건 지양하세요. /user/654321/orders 대신 /me/orders를 사용하세요.
- 자동 증가 (auto-increment) 식별자 대신 UUID를 사용하세요.
- XML 파일을 파싱하고 있다면, XXE (XML 외부 엔티티 공격, XML external entity attack)를 피하기 위해 엔티티 파싱을 비활성화 하세요.
- XML 파일을 파싱하고 있다면, 지수적 엔티티 확장 공격을 통한 빌리언 러프/XML 폭탄을 피하기 위해 엔티티 확장을 비활성화 하세요.
- 파일 업로드에는 CDN을 사용하세요.
- 거대한 양의 데이터를 다루고 있다면, HTTP 블로킹을 피하고 응답을 빠르게 반환하기 위해 워커나 큐를 사용하세요.
- 디버그 모드를 꺼놓는일은 절대 잊지 마세요.
- 상기의 적혀 있는 사항들 이외에 아래의 사항들도 참고하세요.
- 일단 기본적으로 HA 구조는 가져가셔야 합니다. 한쪽 서버가 죽으면 전체 시스템 사용이 불가하게 되는 이런 구조는 절대 피하세요.
- 서버 앞단에 Load Balancer를 추가하여 패킷을 분산 시키세요.
- 클러스터링 개념도 적용하시고, Auto-Scaling 처리시 Service Discovery를 통해 서비스/서버 자원 관리를 효율적으로 구성하세요.
- 그리고 서버로의 진입 포인트, 즉 인터페이싱은 여기 저기 문어발식으로 구성하지 마시고, 단일 포인트, 한놈이 전담하게 하세요.
- 정적 컨텐츠는 서버 트래픽을 줄이기 위해 CDN 서비스를 적용해 보세요. latency 측면이나 비용 측면에서 도움을 받을 수 있습니다.
- 만약 서버를 클라우드 상에서 운영하고 계신다면, 여건이 허락하는 한도내에서 Managed 서비스를 받아 보세요.(로직에만 집중)
- 가장 안정되고, 신뢰할 만한 솔루션을 적용하시되, 항상 최신 버전을 유지하도록 하세요. (관리/모니터링을 주기적으로 하란 이야기)
📍 반환 및 응답
- X-Content-Type-Options: nosniff 헤더를 반환하세요.
- X-Frame-Options: deny 헤더를 반환하세요.
- Content-Security-Policy: default-src 'none' 헤더를 반환하세요.
- X-Powered-By, Server, X-AspNet-Version 등의 디지털 지문 (fingerprinting) 성격의 헤더는 제거하세요.
- 응답에 content-type을 강제하세요. 만약 application/json 데이터를 반환하고 있다면 응답의 content-type은 application/json입니다.
- 자격 인증 (crendentials), 패스워드, 보안 토큰과 같은 민감한 데이터는 반환하지 마세요.
- 각 연산에 맞는 적절한 상태 코드를 반환하세요. (예를 들어 200 OK, 400 Bad Request, 401 Unauthorized, 405 Method Not Allowed 등)
- 그때 그때 필요한 헤더들을 적절히 구성하시고, 보안 관련된 사항은 두루뭉실하게 제약을 가하기 보다는 명확하게 찝어서 제약 하세요.
- HTTP 헤더의 상태코드와 더불어 에러시, 내부 에러코드를 표준화해서 클라이언트와 서버간의 명확히 인터페이싱하세요.
[reference]
API 설계시 고려사항 / 체크리스트|작성자 몽키몽키