JWT와 HTTP 상태코드들

이병관·2022년 5월 23일
0
post-thumbnail

API랑 친해지기!

API는 응용 프로그램을 좀더 쉽게 사용할 수 있도록,

운영체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있도록 만들어진 인터페이스를 뜻합니다.

주고받는 형식, 방식, 형태, 그리고 통신 방법 등에서 서로 합의를 하여 하나의 API 명세서를 만들고,

이러한 명세서를 바탕으로 API를 호출하여 서로 통신을 합니다.

이러한 API서비스들은 여러가지가 있습니다.


API 서비스들

SOAP

SOAP(Simple Object Access Protocol)은 일반적으로 널리 알려진 HTTP, HTTPS, SMTP등을 이용하여 XML 기반의 메세지를 컴퓨터 네트워크 상에서 교환하는 형태의 프로토콜입니다.

XML의 헤더와 바디를 조합하는 디자인 패턴으로 설계되어 있으며, 헤더는 선택사항이나 반복, 보안 그리고 트랜젝션의 메타데이터를 가지고있고,

바디 부분에는 요청에 대한 중요한 정보를 가지고 있습니다.

기존 원격 통신기술들에 비해 프록시 또는 방화벽에 구애 받지 않고, 플랫폼 독립적인 성격을 가지고 있으며,

프로그래밍 언어에서도 독립적입니다.

허나 XML과 같은 포맷으로 보낸다는건 복잡한 구조로 인해 오버헤드가 발생하며 REST에 비해 상대적을 무겁고 속도도 느릴뿐더러, 캐시를 사용할수 없고

엄격한 통신 규약으로 인해 모든 메세지는 보내기전 미리 메세지를 보내겠다는것을 알려야합니다.

동작 원리

  • 서비스 요청자가 웹 서비스 요청을 SOAP로 인코딩
  • 인코딩한 값을 서비스 제공자에게 전달
  • 서비스 제공자는 전달받은 값을 디코딩 후 적절한 서비스 로직을 수행
  • 나온 결과값을 다시 SOPA로 인코딩하여 서비스 요청자에게 반환

REST API

그리고 시간이 지나 Representational State Transfer의 약자인 REST라는 이름을 가진 소프트웨어 프로그램 아키텍처를 따르는 API가 등장하기 시작합니다.

이는 자원을 이름으로 구분하고, 해당 자원의 상태를 주고받는 모든것을 뜻하는데,

즉 HTTP URI를 통해 자원을 명시하고 HTTP Method를 통해 해당 자원에 대한 CRUD를 어떻게 적용할것인지를 나타냅니다.

HTTP 프로토콜을 따르기 때문에, REST API를 위한 새로운 인프라를 구축할 필요도 없고,

해당 프로토콜을 따르는 모든 플랫폼에서 사용이 가능하며 XML뿐만 아니라 일반 텍스트, HTML, JSON등 다양한 포맷을 허용하고,

SOAP와는 다르게 캐시를 사용할 수 있고 메세지를 보내겠다는 것을 미리 알리지 않아도 됩니다.

그리고 SOAP에 비해 페이로드가 가볍기 때문에 보다 적은 리소스를 필요로 합니다

gRPC

REST는 자원을 표현하는것이 직관적이고 별도의 작업없이도 쉽게 HTTP 프로토콜을 사용하면 쉽게 사용이 가능하지만,

표준형이 아니기 때문에 파라미터와 응답값이 명시적이지 않으며, HTTP 메소드가 제한적이기 때문에 세부적인 기능 구현에 제약이 존재합니다.

그로인해 구글에서는 네트워크로 연결된 서버상의 함수 또는 메소드등을 원격으로 호출하는

RPC(Remote Procedure Call)을 기반으로 만든 gRPC프레임워크를 선보입니다.

gRPC는 클라이언트 애플리케이션에서 마치 자신의 메소드를 호출하는것처럼,

gRPC서버의 메소드를 직접 호출 할수 있습니다.

그 뒤 구조화된 데이터를 직렬화 하기 위해 구글의 ProtoBuf를 사용합니다.

Protocol Buffers - Protobuf
protobuf는 어떤 언어나 플랫폼에서도 통신 프로토콜이나 데이터 저장을 사용할때 구조화된 데이터를 전환하게 해주는 방식입니다.
protobuf는 xml에 비해 간결하고, 덜 모호하며 20~100배 이상 빠르며 더 쉬운 데이터 엑세스 클래스를 제공해줍니다.

.proto파일에 직렬화 하려는 데이터의 구조를 정의합니다.

메세지에는 데이터 타입과 키&값 쌍을 이루는 필드를 하나이상 가지고있습니다.

이렇게 Proto파일로 만들 경우 Json포맷처럼 객체로 파싱하여 사용할 필요가 없어 더욱 빠르게 데이터를 주고 받을수 있습니다.

또한 여러 언어들 역시 지원하기 때문에 Nodejs에도 붙일수 있어, Express 또는 Next에서도 gRPC를 정의할수 있습니다.

이처럼 다양한 형식으로 API 아키텍처는 진화하고 있습니다.

따라서 프로젝트에 맞는 API를 선택하거나 혼용해야 할것입니다.

그리고 특정 API는 특정한 사용자 또는 적절한 인증을 받은 사용자만이 사용할수 있게 해야하는데,

이를 위한 인증은 여러가지가 존재합니다.

해당 인증을 구현하는 여러 방법에 대해 간략히 알아보겠습니다.

인증 Authentication vs 인가 Authorization
인증은 내가 누구인지(Login + password)를 뜻합니다. 로그인과 같은 행위가 인증 행위이며
인가는 내가 무얼할수있는지에 대한 권한(Permission)을 뜻합니다.
예를 들면 A는 데이터를 만들고 삭제할수 있으나
B는 데이터를 읽는것만 가능한것입니다.
누구인지는 상관없이 단지 권한만 체크합니다

Authentication

Basic Authentication

HTTP 표준에 정의된 가장 단순한 인증 기법인 Basic Authentication은 다음과 같이 이루어집니다

  • Client는 Server에게 Resource를 요청합니다.
  • Client가 요청한 Resource를 이용하기 위해서는 인증이 필요합니다 따라서 Server는 Client에게 WWW-Authenticate Header를 통해 인증 필요성을 Client에게 전달합니다. Basic 문자열을 통해 Basic 인증 과정이 필요하다는걸 Client에게 알립니다
    • realm은 요청한 Resource의 Protection Space를 나타내는 속성입니다. Resource가 속해 있는 Protection Space마다 Client는 각기 다른 ID, Password를 이용할 수 도 있습니다.
  • 인증 요청을 받은 Client는 ID:Password 문자열을 Base64로 Encoding한 String을 Authorize Header에 추가한뒤 다시 한번 Server에게 Resource를 요청합니다.

[그림 1]에서는 ID, Password가 각각 ssup2라고 가정한다면, ssup2:ssup2 문자열을 Base64의 Encoding 한다면 c3N1cDI6c3N1cDI= 과 같은 문자열로 변환됩니다.

  • Encoding된 ID:Passowrd 문자열을 받은 Server는 자신이 Encoding한 값과 일치하는지 확인한 후 일치하면, 요청한 Resource를 전달합니다.

사용자가 아이디, 패스워드를 알고있고 아주 단순하게 인증이 필요하다면 HTTP Basic을 사용합니다.

다만 Base64는 디코딩이 가능하기 때문에 HTTPS/TLS와 같이 암호화 통신이 아니라면 감청 당할수도 있고, 암호화를 사용 하더라도 되도록 사용을 하지 않기를 권고하고있습니다.

따라서 해당 인증방법은 외부에는 공개하지 않고 내부에서 가볍게 인증이 필요한 부분에 구현하는 편입니다.

Token Authentication

토큰 기반 인증은 쿠키 또는 세션을 이용한 인증보다 보안성이 강하고 효율적입니다.

단편적으로 쿠키만 사용할 경우 민감한 정보들이 쿠키에 담겨 서버로 보내지는데,

이를 탈취당하면 심각한 문제가 발생할 수 있고,

세션의 경우에는 세션ID를 저장할 추가적인 공간이 필요한다는 단점이 존재합니다.

이러한 단점들을 해결하기 위해 토큰 기반의 인증방식이 등장하였습니다.

토큰 기반 인증 시스템은 세션을 사용하는 서버 기반 인증 시스템과 달리

클라이언트가 서버에 접속을 하게 되면 서버에서 해당 클라이언트에게 인증이 되었다는 의미로

'토큰' 을 발급합니다.

해당 토큰은 유일하고, 클라이언트는 서버에 요청을 보낼때 요청 헤더에 해당 토큰을 실어 보냅니다.

그 뒤 서버는 클라이언트로 받은 토큰을 서버에서 제공한 토큰인지에 대한 것을 확인한후(Validation)

해당 요청을 실행할지, 말지를 결정합니다.

장점

  • 헤더와 페이로드를 가지고 서명 필드를 생성하므로 데이터 변조 후 재전송을 막을 수 있습니다.
  • stateless 서버를 만들 수 있습니다.
  • 모바일 어플리케이션에서도 잘 동작합니다.
  • 인증정보를 다른 웹서비스에 전송할 수 있습니다. (OAuth)

단점

  • 여전히 누구나 디코딩이 가능하므로 데이터 유출이 발생할 수 있습니다.
  • 토큰을 탈취당할 경우, 대처하기 어렵습니다. (유효기간을 기다리거나 token refresh를 해야합니다.)
  • JWT의 경우, 토큰의 길이가 길기 때문에 요청이 많아질수록 서버 자원의 낭비가 많아집니다.

Bearer

API에 토큰을 이용하여 인증을 해야할경우 Authorization : Bearer 토큰을 통해 전달해 주게 됩니다.

RFC 6750에 의해 토큰 기반 인증의 표준 내용으로 Bearer라는 타입으로 토큰을 보내주고,

해당 토큰은 크게 JWT 또는 OAuth 토큰입니다.

JWT

JWT(JSON Web Token)는 인증에 필요한 정보들을 암호화 시킨 토큰입니다.

JWT 기반 인증은 이 JWT 토큰을 요청시 HTTP 헤더에 담아 서버가 해당 요청을 한 클라이언트를 식별합니다.

JWT는 .를 구분자로 사용하여 나누어지는 세 가지 문자열의 조합으로 이루어져있습니다.

  • 헤더Header
    • 어떤 알고리즘을 사용하여 암호화를 했는지에 대한 정보와, 어떤 토큰유형인지의 정보가 담겨있습니다.
  • 내용Payload
    • 서버에서 실제로 사용될 정보에 대한 정보가 담겨있습니다
  • 서명Signature
    • 헤더의 인코딩값과 내용의 인코딩값을 합쳐 비밀키를 사용해 해쉬하여 생성됩니다.
    • 토큰의 위변조여부를 확인할때 사용합니다.

OAuth

전통적인 클라이언트-서버 인증 방식에서는

클라이언트가 서버로 인증정보를 전송하면 서버에서는 이를 확인하고 인증합니다.

이러한 방식에서 점차 다양한 서비스들이 출시되게 되고

특정 서비스는 사용자의 정보 뿐만 아니라 사적 정보 까지 많이 포함하고 있는 경우가 있어,

OAuth는 이를 다른 서비스에서도 활용한다면 어떨까 하는 생각에서 시작합니다.

자사 서비스가 가지고있는 고객정보를 다른 서비스에 활용 할수 있도록 고객의 동의 하에 제공한다면,

제공받는 서비스는 자사의 서비스에 의존적이게 되고, 고객 역시 자사의 서비스에 의존하게 될것입니다.

간단한 예로 여러분 역시 많은 서비스를 구글 로그인을 통해 회원 가입을 하셨을겁니다.

여러 종속성들로 인해 여러분은 좋던 싫던 구글에서 회원 탈퇴를 한다는것 자체를 상상할수 없으실겁니다.

이렇듯 고객➝서비스 사이의 전통적인 구조에서 고객➝정보제공서비스➝서비스 와 같이

새로운 계층을 통해 인증을 하게 된다면 문제가 발생합니다.

  • 고객 A가 정보제공서비스 B를 통해 서비스 C를 가입하려 한다.
  • 서비스C는 고객A의 정보를 저장하고 있어야 정보제공서비스B를 통한 고객A의 정보를 얻을수 있다.
  • 서비스C는 정보제공서비스B와 동등한 권한을 가져야한다
  • 고객A가 서비스C를 더이상 이용하지 않을경우 정보제공서비스B는 고객A인증 정보를 바꿔야 한다.
    • 그래야 더이상 서비스C가 고객A의 정보를 얻는것을 막을수 있다.

따라서 이러한 부가적으로 생긴 문제들을 해결하기 위해

OAuth는 Access Token이라는 핵심이 빠지지 않습니다.

Access Token

Access Token은 임의의 문자열 값입니다.

이 문자열의 값은 토큰을 발급해준 서비스만 알 수 있으며,

서비스는 Access Token을 검증하고, 발급이 정상적으로 된 토큰이라면 해당 고객의 정보를 넘겨주게 됩니다.

간단하게 예를들어 구글을 사용하여 회원가입을 한다고 가정했을때 다음과 같이 이루어집니다

  • 사용자가 서비스에 구글로 회원가입 버튼을 누릅니다
  • 구글창이 뜨고 사용자가 입력한 아이디/비밀정보를 통해 구글의 회원인지 확인합니다
    • 만일 이미 로그인 되어있다면 곧바로 Access Token을 발행합니다
  • 구글은 Access Token을 쿼리스트링에 담아 회원가입을 요청한 서비스로 리디렉트 시켜줍니다
  • 만일 승인된 리디렉션 URI가 아니라면 XSS공격 또는 피싱사이트로 이동과 같은 수상한 행동으로 여기고 리디렉션하지 않습니다.

이렇게 서비스에서 Access Token을 받고, 이제 특정 인증이 필요한 API 들은 해당 Access Token을 같이 넘겨줄것입니다.

즉 OAuth는 크게 나누자면 다음과 같습니다.

  • 보안을 위해 특정한 리디렉션 URI합의
  • Access Token의 발급을 위해 서드파티 페이지로 사용자를 로그인, 및 리디렉트
  • Access Token으로 정보 요청

Refresh Token

허나 조금더 생각해보면, Access Token에는 사용자의 정보가 담겨있습니다.

즉 이를 탈취당해서 복호화를 통해 정보를 취득할 수 있습니다.

따라서 Access Token의 생명주기는 짧은 편인데,

그렇다면 Access Token이 만료될때마다 다시 구글에 로그인하거나 네이버에 로그인해서 Access Token을 재발급 받는것은 매우 번거로울것입니다.

그렇기에 Access Token 만료시 이를 새로 갱신시켜주기 위해 Refresh Token을 사용해 Access Token을 갱신시킵니다.

Refresh Token은 유호한 Access Token을 새로 발급받아 갱신시켜주는 역활을 합니다.

HTTP Status Code

HTTP의 메소드를 통해 통신을 하고, 백엔드에서 해당 작업의 성곰/ 실패 여부를 어떻게 알려줄지에 대한 방법은 다양하지만,

그 중 좀더 명확한 정의를 위해 필요한것이 바로 HTTP 상태코드입니다.

HTTP 상태 코드는 클라이언트가 보낸 HTTP 요청에 대한 서버의 응답 코드로,

해당 코드에 따라 요청의 성공 또는 실패 여부를 판단합니다.

HTTP 상태 코드는 각 상황에 맞는 코드가 표준으로 정해져있으며,

웹에서 동작하는 프로그램의 동작들 또는 프론트엔드와 백엔드의 프레임워크 설계 역시 해당 표준을 기준으로 잡고

설계했기 때문에 되도록 표준을 지키는것이 중요합니다.

100번대

100번대 상태 코드들은 프로토콜을 교체가능하거나, 요청을 계속 보내도 된다는 정보성을 띄고있는 코드입니다.

다만 실제 100번 코드들은 거의 사용되지 않기 때문에 건너 뛰어도 무방합니다

200번대

200번대 상태 코드들은 클라이언트가 요청한 작업을 서버에서 성공적으로 수행되었다는것을 알려주는 코드입니다.

200번대는 콘솔의 네트워크에서 초록색이 나와 시각적으로 성공했음을 볼 수도 있습니다.

200 OK

200은 작업이 성공햇음을 의미합니다. 클라이언트는 서버에 자신이 어떤 작업을 요청했는지 알고 있는 상황이기에

그저 서버에서는 '요청 성공' 이라는 정보만 알려줘도 되서 200 코드 하나로 왠만한 모든 API 응답 성공 상태를 처리합니다. 또한 200응답은 캐쉬 될 수 있습니다

다만 앞서 말한것과 같이 다양한 상황의 성공에 따라 해당되는 코드들이 여럿 존재하니,

200으로 모든것을 처리하는것 보다 해당 상황에 맞는 코드를 내려주는것이 더 좋습니다

201 Created

201은 요청이 정상적으로 수행되었고, 해당 요청으로 인해 리소스가 새롭게 생성되었다는 코드입니다.

예를들어 회원가입과 같은 요청은, 해당 요청으로 인해 데이터베이스에 새로운 유저가 생성되었다면

201코드를 내려주어야 합니다.

204 No Content

204는 요청이 정상적으로 수행되었고, 해당 요청으로 인해 관련된 컨텐츠가 더이상 존재하지 않음을 뜻합니다.

예를 들어 어떠한 게시글을 삭제하는 요청이 성공적으로 수행되었다면, 요청에 관련된 게시글이 더이상 존재하지 않음으로

204코드를 내려주는것이 좋습니다.

206 Partial Content

206은 부분 또는 범위 요청이 성공했다는 뜻입니다.

보통 클라이언트에서 시작범위 또는 다운로드 범위를 지정하고 요청을 했을경우,

서버에서는 클라이언트가 요청한 범위에 대한 제공을 성공적으로 수행하였음을 알리기 위해

206 코드를 내려줍니다.

300번대

300번대 코드들은 리디렉션에 관련된 상태들을 의미합니다.

클라이언트에서 요청한 리소스가 옮겨졌거나 혹은 삭제되어서 정상적인 방법으로는

더이상 해당 리소스에 접근이 불가능하고, 다른 URL로 해당 리소스에 접근해야할 경우,

서버에서는 클라이언트에게 '다른 장소에 리소스가 존재'와 같은 정보를 제공할 수 있습니다.

이때 사용되는 상태 코드들이 바로 300번대 입니다.

301 Moved Permanetly

301번의 이명은 Redirect일정도로 리디렉션을 위한 코드중에서 가장 많이 사용됩니다.

만일 브라우저가 요청을 하고, 응답으로 301을 받을 경우, HTTP header에 존재하는

Location 필드를 찾아내고, 해당 필드가 존재할 경우, Location 필드의 URL로 자동 리디렉트 시켜줍니다

또한 구글의 검색엔진 봇과 같은 경우 301 상태 코드를 받을 경우 자동으로 페이지 정보를 갱신하기도 하기에,

SEO의 관점에서도 해당 코드를 잘 사용 해야 합니다

리디렉션설정은 Nginx와 같은 서버엔진의 설정 파일내에서 할 수 있을 뿐만 아니라,

백엔드쪽의 코드에서도 작업할수 있습니다.

일반적으로는 HTTP프로토콜로 접속한 사용자를 HTTPS프로토콜을 사용해야만 접근 가능한 포트로 보내버릴때

많이 사용됩니다.

server {
    listen         80; // 80포트로 접근한 사용자는
    server_name    McCommi.com;
    return         301 https://$host$request_uri;
}

server {
    listen         443 ssl; //HTTPS 프로토콜을 사용해야 접근할수잇는 443으로 리다이렉트
    server_name    McCommi.com;
    ...
}

304 Not Modified

304은 클라이언트가 요청한 리소스가 이전의 요청과 비교하였을때 어떠한 변경점이 존재하지않음,

즉 수정이 되지 않을때 응답하는 코드입니다.

쉽게 설명하자면 클라이언트가 해당 응답을 받을 경우,

클라이언트는 '그렇다면 다시 서버에게 리소스를 달라고 할 필요는 없겠군' 라는 생각을 하게되어

자신이 캐싱해둔 리소스를 사용하게 되고, 불필요한 통신을 줄일수 있습니다.

브라우저 역시 해당 응답을 위해 자체적으로 캐싱 기능을 가지고 있습니다.

그렇기에 304 상태 코드를 응답받을 경우, 캐싱된 리소스가 없다면 빈화면 또는 에러가 나오게 됩니다.

브라우저가 HTTP 응답을 캐싱함으로써 발생할수 있는 문제

살짝 짚고 넘어 가고싶은 부분입니다.

브라우저가 HTTP응답 자체를 캐싱하기 때문에 발생할수 있는 문제가 있는데 바로 CORS문제입니다.

왜냐하면 HTTP응답에는 본문 데이터 뿐만 아니라 헤더도 포함 되어 있기 때문입니다.

Img태그로 이미지를 브라우저가 캐싱할땐, 단순하게 파일만 캐싱하는것이 아니라

헤더까지 함께 캐싱하게 되고, img태그를 사용하여 파일을 다운로드 할 시,

응답 헤더에 Access-Control-Allow-Origin정보가 포함되지 않습니다.

실제로 회사에서 웹 ERP 기능을 추가할때, Img태그를 사용하여 먼저 이미지를 다운 받을 경우

Access-Control-Allow-Origin 헤더가 존재하지 않은채로 이미지가 캐싱되고,

같은 이미지 파일 링크로 요청할시, 브라우저 캐싱 데이터가 존재해 해당 데이터를 사용하는데,

해당 데이터에는 Access-Control-Allow-Origin 헤더가 없기에 브라우저가 CORS에러를 발생시킵니다.

따라서 이 문제는 img 태그로 캐싱된 데이터를 그대로 사용하는것이 문제이기 때문에,

캐싱을 피하는 여러 방법으로 이슈를 해결할 수 있습니다.

가장 간단한 방법은 단순한 수정을 통해 문제를 해결할수 잇습니다.

img태그로 사용하려는 이미지 링크와, 요청에 사용되는 이미지 링크를 다르게 만들어서

캐싱된 이미지를 사용하지 않도록 합니다.

<img src = `${imgURL}?t=${timestamp}`/>

400번대

400번대 코드들은 클라이언트가 서버에게 잘못된 요청을 보냈을 경우 사용됩니다.

대부분 프론트엔드의 예외 처리와, 요청에 맞지 않는 값이 들어갔을 경우 일어납니다.

400 Bad Request

Bad Request는 많이 만나보신 응답중 하나일것입니다.

클라이언트가 요청을 잘못했음을 알리는 상태 코드로써, 응답 바디에 어떤부분이 잘못되있는지 설명을 하는 경우도 있으나,

아무런 설명이 없다면 직접 백엔드의 코드와 로그를 확인해야 할수도 있습니다.

401 Unauthorized

401은 인증되지 않은 사용자가 인증이 필요한 리소스를 요청하는 경우, 인증이 필요함을 알리는 응답 코드입니다.

보통 인증된 사용자만 접근할수 있는 API를 비인증된 사용자가 호출할때 사용됩니다.

사용자에게 더 좋은 환경을 제공하기 위해 해당 응답을 받은뒤, 로그인 페이지로 리디렉션 해주기도 합니다.

403 Forbidden

403은 클라이언트가 접근이 금지된 리소스에 접근했음을 알리는 응답코드입니다.

401과는 다른점은, 401은 서버가 클라이언트에게 인증을 먼저 한 뒤 다시 요청을 하라는 메세지라면,

403은 인증의 유무, 사용자가 누구던 해당 리소스는 절대 접근할수 없음을 말합니다.

404 Not Found

404는 말 그대로 요청한 리소스가 존재하지 않음을 뜻합니다.

405 Method Not Allowed

405는 현재 리소스에 맞지 않는 메소드를 사용했음을 뜻합니다.

백엔드에서는 특정 컨트롤러에서 해당 메소드를 사용하는 로직이 존재하지 않다면 자동으로 405를 내려주기도 합니다

408 Request Timeout

클라이언트와 서버의 연결은 성공했으나, 요청이 서버에 도착하지 않는 상황일때 응답합니다.

429 Too Many Requests

429코드는 클라이언트가 서버에 너무 많은 요청을 보낼 경우 발생합니다.

많이 보냈다는건 크게 두가지 상황으로 나눌수 있는데,

말 그대로 너무 짧은 시간안에 많은 요청을 전송해 서버에서 좀 천천히 보내란 의미로 보낸 응답일수도 있고,

유로 API를 사용하는 경우라면 허용된 API 요청 수를 초과했기 때문에 응답한 코드로 사용될 수 있습니다.

서버는 429와 함께 응답 헤더에 Retry-After 라는 필드를 사용하여 해당 시간 이후 재요청하라고 명시할수 있습니다.

500번대

500번대 코드들은 서버에서 무언가 문제가 발생하여 클라이언트에게 돌려주는 응답들입니다.

주로 백엔드의 문제로 인해 발생합니다

500 Internal Server Error

500은 백엔드 어플리케이션 내에 무언가 알수 없는 에러가 발생했을때 응답되는 코드입니다.

에러의 원인은 다양하거나 핸들링 되지 않은 에러이므로 보안상 어떤 에러인지 값을 담지 않고

500코드만 응답하는 경우가 많습니다.

502 Bad GateWay

백엔드의 아키텍처는 크게 프록시 서버가 프론트와 백엔드 사이에 위치하고,

프록시와 백엔드 사이엔 추상적인 통로인 게이트웨이를 통해 연결을 주고받습니다.

만일 백엔드 어플리케이션이 모종의 이유로 죽어버린다면,

프록시 서버는 백엔드 어플리케이션으로 부터 어떠한 응답도 받지 못합니다.

따라서 클라이언트에게 백엔드 어플리케이션이 작동을 하지 않는다는 502 Bad GateWay를 전달합니다.

503 Service Unavailable

503는 서버가 요청을 아직 처리할 준비가 되지 않음을 의미합니다.

즉 서버의 부하나 특정한 요청을 먼저 처리해야 하기때문에 일시적으로 해당 요청을 핸들링할수 없기에

보내는 응답입니다.

429 Too Many Requests처럼 Retry-After 필드처럼 특정시간후 다시 요청을 할수 있도록 클라이언트에게 전달할 수 있습니다.

504 GateWay Timeout

504는 백엔드 아키텍처에서 서버끼리 요청을 주고 받을때,

백엔드 어플리케이션이 일정 시간동안 응답을 하지 않을 경우,

즉 요청에 대한 타임 아웃이 백엔드 아키텍처 내부에서 발생할경우

클라이언트에 504 Gateway Timeout을 응답합니다.

Header

이전에

Private (https://app.clickup.com/3700013/v/dc/3gx9d-8025)

에서 말했듯,

헤더에는 클라이언트와 서버가 요청 또는 응답으로 부가적인 정보를 전송할 수 있도록 해줍니다.

그중 주요 Header에 대해 알아보겠습니다.

공통 헤더

Date

Date: (day-name), (day) (month) (year) (hour):(minute):(econd) GMT

Http 메세지가 만들어진 시간입니다. 자동으로 만들어집니다.

Connection

Connection: keep-alive

connection은 기본적으로 keep-alive를 사용합니다.

사실상 아무런 의미가 없는 헤더이며 http/2 에서는 사라졌습니다

Content-Language

사용자의 언어를 의미합니다.

요청이나 응답이 어떤 언어인지는 관련 없습니다.

Content-Encoding

Content-Encoding: gzip, deflate

컨텐츠가 압축된 방식을 뜻합니다.

응답 컨텐츠를 br, gzip, deflate과 같은 알고리즘으로 압축해서 보내면,

브라우저가 해당 헤더를 통해 어떤 알고리즘인지 판단하여 알아서 압출을 풀어 사용합니다.

컨텐츠의 용량을 줄이기 위해 가능한 한 압축을 사용하여 요청과 응답을 보냅니다.

Cache-Control

해당부분은 중요한 부분이기 때문에 아래에서 설명하겠습니다.

Request Header

Host

Host: www.McCommi.io

포트를 포함한 서버의 도메인 네임이 나타나는 부분입니다.

호스트 헤더에는 반드시 하나의 내용이 존재해야합니다.

Accept Prefix

공통헤더의 Content-Language, Content-Encoding에 대응됩니다.

Accept를 사용하여 원하는 형식을 보내면, 서버에서 해당되는 응답을 Content-를 사용하여 보냅니다.

어떠한 형식으로 보낼지 모르겠다면 브라우저의 기본 설정을 따르거나, 와일드카드를 적습니다.

Authorization

Authorization: <type> <credentials>

Authorization 헤더는 인증시 토큰을 서버로 보낼때 사용하는 헤더입니다.

서버의 사용자임을 증명하는 자격(credentials)를 보내는데,

인증 타입()에 따라 credentials가 변경됩니다.

credentials: 인증타입에 따라 credentials가 변형되는데,

예를들어 Basic이라면 사용자의 아이디와 비밀번호가 콜론으로 합쳐지거나,

Bearer타입이라면 OAuth2.0을 사용하여 얻은 AccessToken 혹은 JWT를 넣습니다

Origin

POST와 같은 요청시 어떤 주소에서 해당 요청이 시작되었는지를 알려줍니다.

이때 만일 요청을 보낸 주소와 받는 주소가 다르다면 CORS 문제가 발생합니다.

Referer

Referer: https://McCommi.com/anotherSite

Referer에는 현재 페이지 이전의 페이지 주소가 담겨있습니다.

즉 해당 헤더에는 어떤 페이지에서 지금 페이지로 들어왔는지 알 수 있기 때문에 분석과 같은 부분에서 많이 쓰입니다.

Response Header

Access-Control-Allow-Origin and Methods

Access-Control-Allow-OriginAccess-Control-Allow-Methods는 CORS를 위한 헤더입니다.

위에서 사용자가 무언가를 요청할시, Request Header에

Origin이라는 헤더에 출처를 담는다 하였습니다.

이후 서버가 해당 요청에 응답을 할때, 응답 헤더의 Access-Control-Allow-Origin 에 해당 리소스에 접근하는것이 허용된 출처를 명시합니다.

브라우저는 해당 응답을 받게되고, Origin과 Access-Control-Allow-Origin 를 비교하여 유효한 응답인지 비교합니다.

Access-Control-Allow-Methods 역시 비슷하게 서버의 리소스에 접근할수 있는 HTTP Method인지 비교를합니다.

Allow

Allow:GET

Allow는  Access-Control-Allow-Methods와 비슷하지만 CORS외에도 사용될 수 있습니다.

서버가 원하지 않는 HTTP Method를 사용자로부터 받았을경우

위에서 설명한 상태코드 405 Method Not Allowed 를 응답하고,

응답 헤더에 Allow:사용가능한 요청 과 같이 응답할 수 있습니다.

Content-Disposition

Content-Disposition: inline
Content-Disposition: attachment; filename='filename.csv'

응답 본문을 브라우저가 어떻게 표시해야 할지 알려주는 헤더입니다.

예를들어 inline인 경우 웹페이지 화면에 표시되고, attachment인 경우 다운로드됩니다.

다운로드 되어야 할 파일에게 filename이라는 옵션을 통해 저장될 파일명을 지정할수 있습니다.

Location

위에서 설명한 상태코드 301 Moved Permanetly와 같이 어떤 페이지로 이동해야하는지를 알려주는 헤더입니다.

Content-Security-Policy

줄여서 CSP라 불리우는 해당 헤더는 웹 브라우저에서 사용하는 컨텐츠 기반의 보안정책입니다.

브라우저는 기본적으로 페이지에서 요청하는 모든 코드들을 다운로드하여 실행하는데,

이는 만일 악성스크립트가 섞일경우 위험한 결과를 초래할수 있습니다.

따라서 CSP를 통해 페이지가 로드하도록 허용되는 리소스를 세분화하여 제어할수 있는

지시문을 제공할수 있습니다.

Ex)

  1. Content-Security-Policy: default-src ‘self’

모든 컨텐츠의 소스를 자신의 도메인에서 가지고 옵니다. 서브도메인은 제외합니다.

  1. Content-Security-Policy: default-src ‘self’ *.example.com

모든 컨텐츠의 소스를 자신의 도메인과 서브도메인에서 가지고 옵니다.

  1. Content-Security-Policy: default-src ‘self’; img-src *; media-src media1.com media2.com; script-src script.example.com

모든 컨텐츠를 자신의 도메인에서 가져오지만 몇가지 특이사항이 있습니다

  • img-src: 이미지 관련된 컨텐츠는 모든 사이트들을 허용합니다.
  • media-src media1.com media2.com: 미디어 관련 컨텐츠는 media1,media2에서만 가져옵니다.
  • script-src script.example.com: 실행 가능한 스크립트 관련 컨텐츠는 script.example.com에서 가져옵니다

Directive Reference

이처럼 여러 지시문을 통해 제어할수 있는데, 그중 몇가지를 소개하자면 다음과 같습니다

  • default-src: 만약 다른 CSP들이 걸려 있지 않다면 Default로 설정되는 CSP.

Javascript, Image, CSS, Fonts, AJAX, 등 다양한 값들을 관리합니다.

  • script-src: Javascript가 명시된 주소에서 왔는지 검사합니다
  • style-src: Stylesheet가 명시된 주소에서 왔는지 검사합니다
  • img-src: 이미지가 명시된 주소에서 왔는지 검사합니다
  • connect-src: XMLHttpRequest, WebSocket 등을 검사합니다
  • font-src: 폰트가 명시된 주소에서 왔는지 검사합니다
  • object-src: <object>, <embed>, <applet> 같은 태그가 허용된 주소에서 왔는지 검사합니다
  • media-src: <audio>, <video>가 허용된 주소에서 왔는지 검사합니다
  • frame-src: 프레임이 허용됐는지 검사합니다
  • sandbox: SOP(Same Origin Policy)를 적용하고 팝업, 플러그인, 스크립트를 방지합니다
  • report-uri: 특정 주소로 browser가 정책실패로 인한 내용을 POST하게 합니다
  • child-src: <frame>, <iframe>등을 검토해 웹 사이트 안에 삽입된 웹을 검토합니다.
  • form-action: <form>의 소스를 검사한다.의 소스를 검사합니다.

Source List Reference

  • * : 모든 도메인들을 다 허용한다.
  • 'none': 어디든 source를 가지고 오지 않습니다
  • 'self': 같은 도메인에서 온 소스를 가져옵니다
  • example.com: example.com에서만 source를 가지고 옵니다.
  • *.example.com: example.com과 example.com의 서브도메인에서만 source를 가지고 옵니다.

이외에도 많은 종류들이 존재하기 때문에,

https://content-security-policy.com/

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

을 참고하시길 바랍니다.

Cookies & Cache Header

사용자에게 좀더 효율적인 경험을 제공하기 위해서는 캐싱은 매우 중요합니다.

사용자가 같은 요청을 보낼때마다 매번 같은 데이터를 내려주기 보단 이미 캐싱되어있는 데이터를 전달해준다면 더욱 빠르게 전달해줄수 있으며 불필요한 전송과정이 사라져 네트워크 자원을 효율적으로 사용 가능합니다.

또한 사용자와 서버간의 데이터를 주고받는 간단한 방식인 쿠키를 헤더를 통해 좀더 세부적인 설정이 가능하기 때문에 해당 헤더들을 아는것 역시 중요합니다.

Cache-Control

Cache-Control은 http/1.1에서 추가된 기능으로써, 여러가지 캐싱에 관한 정책을 제공합니다.

사용자의 요청을 백엔드에 HTTP 통신을 통해 데이터를 제공하는데, 만일 이전에 받은 데이터와 새로 요청한 데이터가 같은 데이터라면 해당 요청은 불필요한 과정일 것입니다.

당연하게 새롭게 보여주어야 할 업데이트된 정보가 존재하지 않기 때문입니다.

이때문에 HTTP에서는 Cache-Control이라는것을 통해, 서버의 부하와 클라이언트의 네트워크 통신비용을 캐시를 사용하여 줄이고, 빠른 응답을 제공할 수 있습니다.

캐시의 유효 시간이 지나기전 동일한 요청이 들어올경우,

브라우저는 서버에 요청을 보내지 않고 디스크 또는 메모리에서 캐시를 읽어와 사용합니다

다음과 같이 유효한 캐시가 메모리에 남아있기 때문에 캐시에서 불러왓다는 from memory cache가

표기됩니다.

max-age=

해당 헤더의 seconds를 지정하게 된다면,

요청한 리소스의 캐시가 유효한 시간은 seconds에 해당되는 초가 됩니다.

max-age=3153600(1년 = 3153600초)로 지정할 경우 1년동안 캐시에 남아있게 됩니다.

Age

Age 헤더는 캐시 응답 때 나타나는데, max-age 시간 내에서 얼마나 흘렀는지 초 단위로 알려줍니다.

max-age= 3153600을 설정한 경우, 1분이 지나면

Age: 60

이 캐시 응답 헤더에 포함됩니다.

Expires

Cache-Control과 별개로 응답에 Expires라는 헤더를 줄 수도 있습니다.

Expires:Fri, 20 May 2022 12:00:00 GMT

응답 컨텐츠가 언제 만료되는지를 나타내며,

Cache-Control의 max-age가 있는 경우 이 헤더는 무시됩니다.

Cache-Control: no-store

Cache-Control: no-store 는 캐싱을 하지 않겠다는 뜻힙니다.

해당 헤더를 브라우저가 응답받을 경우, 응답을 캐싱하지 않습니다.

캐시를 절대로 해서는 안되는 리소스에 사용하는데,

브라우저는 그 어떤 경우에도 캐시저장소에 해당 리소스를 저장하지 않습니다.

즉 라우팅 변경이 일어나거나, 뒤로가기/앞으로 가기 를 실행한다면 다시 무조건 요청하게 됩니다.

Cache-Control: no-cache

Cache-Control: no-cache 는 캐싱을 하지 않겠다는 뜻이 아니라,

캐싱을 하지만, 매번 변경되었는지 확인하겠다는 뜻입니다.

즉 캐시는 저장하지만, 사용하려고 할때마다 서버에 재검증 요청을 보냅니다.

캐시의 유효시간이 지나게되면 캐시는 완전히 사라지는 대신

브라우저에서 서버에 조건부 요청을 통해 캐시가 유호한지 재검증을 수행합니다.

HTTP 조건부 요청

영향을 받는 리소스들을 검사기 값을 이용해 비교함으로써, HTTP는, 성공인 경우라도, 요청의 결과가 변경될 수 있는 조건부 요청의 컨셉을 가지고 있습니다. 그런 요청들은 캐시 컨텐츠와 쓸모없는 컨트롤 회피를 검증하고, 다운로드를 이어서 하거나 서버 상의 문서를 업로드 또는 수정할 때 수정된 내용을 잃지 않도록 할 때처럼, 문서의 무결성을 확증하는데 유용할 수 있습니다.

여기서 must-reavalidate라는 헤더 역시 캐시가 만료된다면 최초 조회시 본 서버에 검증을 요청하는데,

주의해야 할 점은 no-cache와 must-revalidate의 차이가 존재한다는것입니다.

• no-cache
no-cache의 경우 원서버에 검증해야 하지만 만약 원서버에 접근을 할 수 없을 시(에러 등등의 이유로)
프록시 캐시에서 오래된 데이터라도 받아와 클라이언트에게 응답합니다. (200 OK 응답)

• must-revalidate
must-revalidate도 원서버에 검증을 해야 하는데 만약 원서버에 접근할 수 없을 시
프록시 캐시와 상관없이 클라이언트에게 꼭 에러를 응답해야 합니다.(504 Gateway Timeout)

다시 본론으로 돌아와서,

재검증 결과 브라우저가 가지고 있는 캐시가 유효하다면,

서버는 [304 Not Modified] 요청을 내려줍니다.

이를 위한 재검증 요청 헤더들은 다음과 같습니다

If-None-Match

캐시된 리소스의 ETag 값과 현재 서버 리소스의 ETag 값이 같은지 확인합니다.

같을경우 304 Not Modified를 응답하여 캐시를 사용합니다

ETag
HTTP 컨텐츠가 바뀌었는지를 검사할 수 있는 태그입니다.
같은 주소의 자원이더라도 컨텐츠가 달라졌다면 ETag가 다릅니다

GET www.redprinting.com의 응답 본문이 Hello Red!이고 ETag 헤더 값이 12345라면,
만약 서버 컨텐츠(응답 본문)가 동일할때,
GET www.redprinting.com을 할 때마다 ETag는 12345로 올것입니다.
그런데 Hello Red!에서 GoodBye Red로 컨텐츠가 바뀐다면
ETag 헤더 값도 54321과 같이 다르게 바뀝니다.
그러면 서버가 클라이언트의 응답 내용이 달라졌기 때문에, 캐시된 데이터를 지우고 새로 컨텐츠를 받습니다.

If-Modified-Since

캐시된 리소스의 Last-Modified 값 이후에 서버 리소스가 수정되었는지 확인합니다.

재검증 결과 캐시가 유효하지 않으면,

서버는 [200 OK] 또는 적합한 상태 코드를 본문과 함께 내려줍니다. 

또한 CDN과 같은 중간 서버 또는 프록시를 사용할때는 캐시가 여러곳에 생길수 있습니다.

서버의 응답을 CDN이 캐싱하고, 해당 캐싱된 데이터를 브라우저가 다시 가져가기 때문에,

캐시를 잘 다루지 못한다면 원하는 답을 못줄수도, 이전의 데이터만 보여줄수 있기 때문입니다.

중간서버나 CDN이 여러개 잇을경우 캐시가 여러곳에 존재하기 때문에

각각의 모든 서버에 대해 캐시를 삭제해야합니다.

따라서 위와같은 상황에선 캐시를 지우기 힘들기 때문에,

Cache-Control의 Max-age와 같이 생명주기와 관련된 설정들은 주의해야합니다.

public은 모든 사람과 중간 서버, 또는 프록시가 캐시를 저장할 수 있음을 나타내고, 

private은 가장 끝의 사용자 브라우저만 캐시를 저장할 수 있음을 나타냅니다.

예를들어 max-age 값과 같이 다른 값과 조합하려면 

Cache-Control: public, max-age=86400 과 같이 콤마로 연결할 수 있습니다.

덧붙이자면 모든 Cache-Control의 정책은 기본적으로 private입니다.

서버에서 클라이언트(브라우저)한테 쿠키를 저장하라고 명령하는 응답 헤더입니다.

Set-Cookie: 키=값; 옵션들

Set-Cookie: Red=Hello

Red라는 키에 Hello라는 값을 담아 보낼수 있으며 몇가지 옵션 역시 설정할 수 있습니다

  • Expires: 쿠키 만료 날짜를 알려줄 수 있습니다.
  • Max-Age: 쿠키 수명을 알려줄 수 있습니다. Expires는 무시됩니다.
  • Secure: https에서만 쿠키가 전송됩니다.
  • HttpOnly: 자바스크립트에서 쿠키에 접근할 수 없습니다. XSS 요청을 막으려면 활성화해두는 것이 좋습니다.
  • Domain: 도메인을 적어주면 도메인이 일치하는 요청에서만 쿠키가 전송됩니다.
  • Path: 패스를 적어주면 이 패스와 일치하는 요청에서만 쿠키가 전송됩니다.

Set-Cookie: Red=Hello; Expires=Fri, 20 May 2022 12:00:00 GMT; Secure; HttpOnly

쿠키의 생명주기는 두가지로 정의할수있는데,

세션쿠키는 현재 세션이 끝날때 삭제되고,

지속적인 쿠키는 expires 속성에 명시된 날짜에 삭제되거나, Max-age 속성에 명시된 기간 이후에 삭제됩니다.

반대로 클라이언트가 서버한테 쿠키를 보내줄 때는 이 요청 헤더에 담아 보냅니다.

Cookie: 키=값; 키=값;

서버는 이 쿠키 헤더를 파싱해서 사용하게 됩니다.

CSRF 공격같은 것을 막기 위해서 반드시 서버는 쿠키가 제대로 된 상황에서 온 것인지 확인하는 로직을 갖춰야 합니다.

profile
뜨겁고 매콤하고 화끈하게

0개의 댓글