Search
▪️

API Server 만들기

API Server의 개념과 필요성

Server의 경우, Router 중심으로 Coding을 하면 거의 API로 사용을 하게 할 수 있다.
또한 Server의 경우, Main Server와 API Server를 구분해두는 것이 여러모로 좋다. 이렇게 분리해두는 것으로, API Server가 Down되더라도 Main Server에는 지장이 가지 않도록 할 수 있다. (즉, Main Server에 영향이 가지 않기 때문에 API Server는 있어도 그만 없어도 그만인 것이다.) 이런 설계를 Micro Service Architecture라고 한다.
Micro Service Architecture는 장점도 많지만, 단점으로는 Error가 생겼을 시 Error에 대한 Tracking이 어렵다는 점에 있다. (즉, Server를 적당히 쪼개자.)
** 가입이나 게시글을 올리는 것들은 Main Server, Token 인증 및 게시글을 읽어 오는 것들은 API Server 등으로 나눌 수 있다. Main Server와 API Server는 각각 다른 Port에서 구동되어 서로 소통할 수 있다.

NodeBird-API 프로젝트 세팅하기

Sequelize에서 Model을 생성할 때, timestamps, paranoid를 주었던 것과 같이 Option으로 validate를 줄 수 있다. validate Option이 켜져 있다면, 해당 Model을 생성할 때 Attribute들이 조건들을 만족하는지, 올바른 데이터들이 들어왔는지 검증할 수 있다. (데이터의 Type, Length는 기본적으로 Sequelize가 해주지만, 추가적인 검증을 수행할 수 있다.)

clientSecret과 UUID

API Server에서 API를 사용할 수 있도록, 사용자들에게 Key를 나눠줘야 한다. (비공개 API라면 말이다.) 이 때, 사용자들이 가진 Key 값이 중복되지 않기 위해서, Unique한 값을 이용해야 한다. 고유한 Key 값을 사용할 수 있도록 하는 것이 UUID이다. (보통 uuid Package에서 v1 혹은 v4를 보편적으로 사용한다.)
UUID를 Key 값으로 지정했다면, 해당 Key가 Client Secret이 되는 것이다. 이 Secret과 Client의 Domain을 묶어서 저장하면 된다.
Client가 API Server의 API를 사용하기 위해서 Valid한 Secret을 제공하면 API Server는 Valid한 Response를 준다. (Domain 주소는 Client의 Front에서 Request를 보내게 될 때 검증하게 되고, Secret은 Server에 Request가 들어오면 검증을 하게 된다.)

JWT Package

JWT Secret은 JWT Token 발급 및 인증에 사용되기 때문에 잘 보관해야 한다. (노출되면 Secret을 통해서 Token을 생성할 수 있고 Server에서 허가 되지 않은 Token을 이용할 수 있게 된다..)
JWT Token이 필요한 이유는 무엇인가? → UUID로 발급 받았던 Client Secret의 경우 Server to Server에서만 이용 가능하기 때문에 Front(Client) Side에서는 사용할 수 가 없다. 하지만 JWT를 이용하면 Front(Client) Side에서도 사용이 가능하다.
API Server는 Session으로 운영하되, API 이용 관련으로는 JWT로 운영하며 JSON 데이터만 넘겨주는 식으로 이용하도록 한다. (JSON 데이터를 넘겨줘야 하기 때문에, 기존과 같은 Error 처리로 Pug File을 Rendering하는 것이 아니라, Error Code와 Error Message를 보내도록 한다.)
** JWT Token은 Decoding하면 쉽게 볼 수 있다. 민감한 내용들은 저장하지 않도록 한다. 다만 JWT Token의 내용은 공개되지만, 변조는 거의 불가능하다고 보면 된다. (JWT Secret이 있기 때문이다. 변조되면 Secret으로 복호화가 불가능하다.) 따라서 이 변조 불가능한 성질 때문에 유효한 Token을 갖고 있는 것 자체만으로도 사용자에게 Authorization을 주는 것이다.

API Call Server 만들기

API Server (Server로 작동)와 같이 Call Server (Client로 작동)를 두어 API Server를 Test 해볼 수 있다.
이 때, Call Server에서 필요한 Package들은 대체로 dotenv, cookie-parser, express, express-session, pug, morgan과 같이 공통되는 부분들이지만, 이제까지 쓰던 것과는 다른 'axios'라는 Package도 함께 설치를 한다. (axios Package는 다른 Server로 요청을 쉽게 보낼 수 있게 해주는 Package이다.)
Axios Package를 통해서 API Server에 Client Secret을 쉽게 넘길 수 있고, 이에 따른 Token을 쉽게 받아올 수 있는 것이다.
받아온 Token은, Expire되지도 않았는데 매번 받아오는 상황을 만들지 않아야 하므로, Session에 담아둔다.
Token을 담고 처리하는 과정은 Code를 참고하자.
Matching Table로부터 Call에 따른 Response에서 거르고 싶은 Field가 있다면, 인자에 Option을 주어 거를 수 있다. attributes라는 Field의 Option을 이용한다.

API 사용량 제한 구현하기

Express.js에서는 API의 사용량을 제한하는 Third Party Package가 있다. express-rete-limit이라는 Package를 이용하면 된다.
해당 Package는 사용량에 대한 제한을 주기 때문에 Middleware의 호출을 제한하는 것과 동일하다. 즉, Middleware를 이용하는 곳에서 (Middleware의 Entry Point에서) Import하여 사용하면 된다. 조금 더 자세히 언급하자면, Token을 받는 것을 제하고 Token을 이용하는 API 요청의 Initial Middleware는 Verify Middleware이다. 이 Verify Middleware를 이용하는 곳에서 Rate Limit을 걸어준다.
자세한 것은 Code를 참고하자. (express-rate-limit의 Object인 RateLimiter를 Option을 주어 Instantiate하고, 해당 Instance를 사용히여 Middleware를 만든다.)
** API들을 Version 별로 나누어 Router를 운영하는 것도 Limiter를 이용할 수 있다. 새로운 Version이 배포되었다면, 해다 Version을 이용할 수 있도록 Limiter로 제한을 걸어버리는 것이다.
** Object인 RateLimit의 Option
windoMs: 주어진 시간 동안의
max: 최대 횟수
delayMs: 요청간 간격 시간
handler(req, res): Emit Message While Handling Function
statusCode: 기본 값 429
** 모든 Router에서 공통적으로 이용한다면, 일일이 Middleware를 이용하지 않고 use() Method를 이용하여 모든 Middleware들이 use() Method를 먼저 거치게 한다.

CORS 해결하기

CORS (Cross Origin Resource Sharing)이라 함은 Frontend에서 사용하는 주소와 Backend에서 사용하는 주소가 서로 다르면서 생길 수 있는 문제이다. 정확히 얘기하면, CORS Error는 자원을 사용하는 주체의 주소와 자원을 제공하는 주체의 주소가 다른 경우 발생하는 Error인데, Frontend는 Backend의 자원을 사용하기 때문에 Frontend와 Backend의 주소가 서로 다르면 CORS Error가 발생할 수 있는 것이다.
Header에 Option을 주어 CORS Error를 해결하는 경우, Manual하게 값을 Header를 설정하여 해결할 수도 있지만 cors라는 Package를 통해서 쉽게 해결할 수도 있다. (Code를 참고하자.) 해당 Package 이용 시, cors() Method가 Request의 Header에 Access-Control-Allow-Origin을 설정해주기 때문에 CORS Error가 해결되는 것이다.
cors() Method에서 아무런 인자도 주지 않으면 모든 주소에 대해서 CORS Error를 만들지 않겠다는 것이고, 특정 주소를 주면 해당 주소만 CORS Error를 만들지 않겠다는 것이다.
하지만 위와 같이 수동으로 Domain 허가를 하는 것은 좋지 않으므로, 요청이 들어온 Domain이 데이터베이스에 추가되어 있는 Domain인지 확인 후 cors()에 인자로 주는 것이 좋다. http 요청이 들어오면, req.get('origin')을 통해서 URL을 확인할 수 있다.
** 주소는 Port Number를 포함한다.
** Server to Server에서는 CORS Error가 발생하지 않는다. 따라서 서로 다른 주소를 사용하는 Front와 Backend 사이에 Backend를 두어 Server to Server로 작동하게 하는 Proxy Request를 사용하여 CORS Error를 해결하는 방법도 있고, Header에 특정 Option을 주어 CORS Error를 해결할 수도 있다.
** CORS에 대해서 처리해준 경우, Request를 보내게 되면 OPTIONS Method로 Request가 하나 더 보내진다. OPTIONS Method로 보내진 Request는 Access-Control-Allow-Origin을 검사하게 된다.

스스로 해보기2 (무료 / 유료에 따라 사용량 차등 제한)

401 // Invalid Token
410 // Deprecated
419 // Expired Token
429 // Too Many Requests

스스로 해보기3 (Client / Server 비밀 키 구분하기)

이제까지 Chapter를 진행하면서 Server Side에서 이용하던 Client Secret은 Browser에서 노출되어도 되는 JavaScript Key로 이용하여 CORS Error 문제가 없다면 해당 Key로 Server에 요청을 보낼 수 있도록 한다. 반대로 Proxy Request를 보낼 수 있게 주소가 같은 Server를 활용하게 된다면, 해당 Server에서 이용하는 Server Key를 새로 생성하여 REST API Key로 활용한다. (CORS Error를 해결하여 Client에서 Server로 Direct로 요청을 날리는 경우, Client Secret이 Browser에서 노출 되어 위험할 수 있기 때문이다. )
기존에 Server Side에서 사용하던 Client Secret은 Server Secret이 되고 여전히 UUID를 사용한다. 새로 정의하는 Client Side에서 사용하는 Client Secret도 UUID를 사용하게 된다. (즉, Client Secret, Server Secret 모두 UUID를 사용한다.)
** Kakao 같이 큰 회사의 경우 JavaScript Key 뿐만 아니라 REST API Key 역시 노출되어도 대응을 할 수 있다. 그래도 조심하자.
** Client Secret이 노출되어도 되는 이유는 Client Secret에 대한 검증은 Domain을 통해서 검증을 하기 때문에 갖고 가더라도 사용할 수가 없는 것이다. (Client Secret으로 API를 이용하는 경우 origin Field를 통해서 Domain을 얻어내고, 해당 Domain을 cors() Method에 등록하여 허용하기 때문이다. 따라서 다른 Domain에서는 Client Key를 갖더라도 이용할 수 없는 것이다.)
** Frontend에서 Request를 직접 날리는 경우에는 Request에 origin Field가 담겨서 가지만, Backend에서 Request를 직접 날리는 경우에는 Request에 origin Field가 생성되지 않는다. 따라서 별도로 origin Field를 생성하여 Request를 API Server로 던져주든가, API Server에서 origin Field 검증을 Frontend에서 직접 받는 경우에만 사용을 하든가 한다. (전자가 더 쉽다. 하지만 굳이 Backend에 Middleware 더 넣어줘야 하고, Backend로 Request를 날리는 경우에는 Domain에 대해서 cors() Method에 추가할 필요가 없는데도 추가를 하게 되므로 후자로 처리하는 것이 조금 더 효율적일 것으로 생각된다.)
** Client Secret, Server Secret을 나누어 사용하는 경우 API Server에서 처리를 잘 해줘야 한다.