오늘날 사용되는 웹 사이트는 Http 프로토콜을 통해 통신이 이루어집니다. Http 프로토콜은 `Stateless` 하다는 특징을 지니는데, 이는 Http 프로토콜을 통한 각각의 요청이 상태를 지니지 않아 이전 요청에 대한 정보를 알 수 없습니다. 하지만 회원을 다루는 사이트의 경우 각각의 요청이 어떤 사용자가 보낸 것인지 알아야 할 필요가 있습니다. 따라서 다양한 인증 방식을 도입하여 사용하는데 이에 대해 알아보도록 하겠습니다.
🟦인증 방식
🟩쿠키
쿠키는 웹 브라우저에서 로컬에 작은 텍스트 데이터를 저장하는 영역이라고 할 수 있습니다. 따라서 별도로 서버에 쿠키 정보를 저장하지 않고 사용할 수 있습니다. 쿠키는 클라이언트와 서버 간의 상태를 유지하고 추적하기 위해 사용을 하는데, 보통 `Name`과 `Value`로 이루어져 있고, 추가적인 정보를 설정할 수도 있습니다.
보통 쿠키가 언제까지 유효한지 설정되어 있지 않으면, 브라우저가 닫힐 때 쿠키도 함께 사라집니다. 이런 쿠키를 `세션 쿠키`라고 부릅니다.
response.addHeader("Set-Cookie", "ID=" + userId);
서버단에서는 위와 같이 Header에 `Set-Cookie` 값을 넣어주어 쿠키 값을 지정할 수 있습니다.
응답으로는 다음과 같이 Response Header 영역에 담겨오게 됩니다.
쿠키의 단점
쿠키에는 다음과 같은 단점이 있습니다.
- JS로 쿠키에 접근이 가능하다 -> JS 코드로 `document.cookie`를 입력하면 현재 쿠키 값을 확인할 수 있습니다.
- 이는 XSS (Cross Site Scripting) 공격에 취약합니다. 대표적으로는 게시판 제목 또는 이미 src에 해커사이트 주소로 연결되게 작성하여 쿠키를 탈취합니다.
- 하나의 브라우저에 저장되기 때문에 다른 브라우저로 요청시 접근이 불가능합니다.
- 헤더에 담기는 내용이기 때문에 조작이 가능합니다.
- 쿠키의 용량은 4KB로 제한되어 있어 많은 정보를 담을 수 없습니다.
HTTP ONLY
Cookie cookie = new Cookie(key, value);
cookie.httpOnly(true);
사실 JS로 쿠키에 접근이 가능하다는 점은 위와 같이 `HttpOnly` 설정을 통해 접근을 차단하여 XSS 공격을 막을 수 있습니다. 해당 설정을 적용하면 브라우저에서 해당 쿠키로 접근할 수 없게 되지만, 쿠키에 포함된 정보의 대부분이 브라우저에서 접근할 필요가 없기 때문에 Http Only Cookie는 기본적으로 적용하는 것이 좋습니다.
SAME SITE
ResponseCookie cookie = ResponseCookie.from(key, value)
.sameSite("None")
.build();
SameSite 속성은 쿠키가 같은 도메인에서만 전송되도록 제한하는 옵션입니다. SameSite 옵션은 CSRF(Cross Site Request Forgery) 공격으로부터 쿠키를 보호하고, 사이트 간 요청 위조를 방지하는 데 도움을 줍니다. 스프링 프로젝트에서 해당 옵션을 지정하기 위해서는 일반적인 Cookie 클래스로는 사용할 수 없고 ResponseCookie 클래스를 통해 위와 같이 사용할 수 있습니다.
SameSite 속성을 `Strict`로 지정하면 외부 사이트에서의 요청에서 쿠키가 전송되지 않으며, `Lax`로 지정하면 외부 사이트에서 GET 메서드로 이루어지는 요청에서만 쿠키가 전송됩니다. `None`으로 지정하면 다른 도메인에서 지정한 쿠키도 전송됩니다.
보통 백엔드 서버에서 Http Only Cookie를 발행하여 프론트 서버로 넘겨주는 식이기 때문에 SameSite 속성을 `None`으로 지정하여 사용하는 것 같습니다. `None`으로 지정하기 때문에 CSRF 방지를 하지 못하게 되는데, 이를 보완하기 위해 CSRF 토큰을 사용한다던가 Referer를 검증하는 등의 방법이 존재합니다.
Secure
암호화된 HTTPS 연결에서만 전송되도록 지정하는 옵션입니다.
🟩세션
세션은 쿠키와 다르게 서버에서 세션 정보를 저장하여 관리합니다. Spring Boot를 통해 만든 프로젝트라면 보통 내장 톰캣의 메모리에 세션 정보가 저장되게 됩니다. 서버에서 직접 관리하기 때문에 요청이 많아도 네트워크에 걸리는 부하가 적다는 장점이 있습니다.
클라이언트가 서버에 로그인하면 서버는 해당 사용자에 대한 세션을 생성하고 세션 저장소에 저장한 뒤 Response Header에 담아 반환합니다. 이후 로그인 유지를 위해서 사용자는 요청에 세션을 담아 서버로 보내며, 서버는 사용자에게 받은 세션값과 세션 저장소와 비교하여 일치하는 경우 로그인을 유지하게 합니다.
세션의 단점
- 서버 측에서 상태를 유지하게 되기 때문에 많은 수의 세션 데이터가 저장되는 경우 서버에 부하가 증가합니다.
- 이를 해소하기 위해 `Sticky Session`, `Session Clustering` 과 같은 방안이 존재하지만, 이를 사용하기 위한 추가적인 비용이 발생합니다.
- Sticky Session의 경우 처음 지정받은 서버만 사용할 수 있기 때문에 해당 서버가 터지거나 과부하가 걸리면 대응할 수 없다고 합니다.
- Session Clustering은 모든 서버마다 세션을 복사해줘야 하기 때문에 상당한 메모리를 요구한다고 합니다.
- 이를 해소하기 위해 `Sticky Session`, `Session Clustering` 과 같은 방안이 존재하지만, 이를 사용하기 위한 추가적인 비용이 발생합니다.
- 서버 환경이 다중 서버로 구성되어있다면 로그인 유지가 되지 않을 수 있습니다.
2번의 경우는 세션 정보가 없는 서버에 요청을 보내게 되는 경우 발생하는 문제인데 별도의 세션 저장소를 두고 다수의 서버가 세션 저장소를 바라보게 한다면 해결할 수 있겠습니다.
🟩JWT (Json Web Token)
JWT는 세션이나 일반적인 토큰과는 다르게 자체적으로 값이 암호화 되어있으며, JWT를 서버에서 복호화 후 인증하기 때문에 기본적으로 DB를 사용하지 않으면서 보다 안전하게 사용할 수 있다는 장점이 있습니다. 쿠키와 세션에도 유효기간이 존재하지만, 토큰의 경우 그 유효기간이 상대적으로 훨씬 짧기 때문에 탈취되더라도 보안 공백이 그리 길지 않습니다. (리프레시 토큰의 경우는 그렇지 못한데 이는 다음에 설명하도록 하겠습니다)
response.setHeader("Authorization", "Bearer " + token);
일반적으로 위와 같이 Header의 name 값으로 `Authorization` 을 전달해주며 `인증타입 token`의 형태를 value로 전달해줍니다. Oauth와 JWT를 사용하는 경우 일반적으로 인증타입을 Bearer로 지정합니다.
JWT 구조
JWT는 `Hader, Payload, Signature`으로 나눌 수 있으며 각각 `.` 을 통하여 분류합니다.
- Header : 토큰의 암호화 종류(alg)와 타입(typ)이 정의된 Json을 Base64로 인코딩 합니다
// 인코딩 전
{
"alg" : "HS256",
"typ" : "JWT"
}
// Base64 인코딩 후
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- Paylaod : 해당 토큰이 포함할 몇 가지 필드 (Key : Value)가 담긴 Json을 Base64로 인코딩 합니다.
// 인코딩 전
{
"iat" : 1554076800000,
"exp" : 1554681600000,
"uuid" : "09226aac-5f8c-11e9-8647-d663bd873d93"
}
// Base64 인코딩 후
// eyJpYXQiOjE1NTQwNzY4MDAwMDAsImV4cCI6MTU1NDY4MTYwMDAwMCwidXVpZCI6IjA5MjI2YWFjLTVmOGMtMTFlOS04NjQ3LWQ2NjNiZDg3M2Q5MyJ9
일반적으로 JWT에서 payload에 담는 key-value 데이터를 `claim`이라고 합니다. payload에 어떤 데이터를 포함할 것인지는 개발자와 개발 환경에 따라 다르지만, JWT 문서 대부분에서 `발급시간(iat)`과 `만료시간(exp)`은 포함하기를 권장하고 있습니다.
- Signature : 위변조를 검증하기 위해 사용하며, 검증은 발급자쪽에서만 가능합니다. Header와 Payload의 encode 값 + 서버에 별도로 저장되어 있는 Secret Key를 기반으로 암호화됩니다. `Header와 Payload의 encode 값을 포함하여 암호화되기 때문에 Header 혹은 Payload의 위변조를 파악할 수 있습니다.`
var encodedHeader = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
var encodedPayload = "eyJpYXQiOjE1NTQwNzY4MDAwMDAsImV4cCI6MTU1NDY4MTYwMDAwMCwidXVpZCI6IjA5MjI2YWFjLTVmOGMtMTFlOS04NjQ3LWQ2NjNiZDg3M2Q5MyJ9";
var secret = "서버에서 지정하는 비밀키";
var signature = HS256.encode(encodedHeader + "." + encodedPayload, secret).toBase64String();
// signature
// 0mgNdgQZNw_C2QfloguymnakqIpDizuvo0uFRuxDWQo
JWT의 단점
- 쿠키와 세션 방식에 비해 Header에 담아줘야 하는 정보의 양이 많아 네트워크에 부하가 생길 수 있습니다.
- 별도의 DB를 사용하지 않고 서버 메모리 상에서 검증을 통해 인증이 이루어지기 때문에 사실상 탈취에 대해서는 무력하다고 볼 수 있습니다.
네트워크에 걸리는 부하의 경우 소프트웨어, 하드웨어를 막론하고 계속해서 기술들이 발전해 나가고 있기 때문에 그렇게까지 큰 장애물이 되지는 않을 거라는 생각이 들지만, 탈취의 경우에는 상당히 심각한 이슈이기 때문에 고민해야할 내용이 상당히 많을 것 같습니다. 따라서 추후에 해당 내용에 대해서 다뤄보도록 하겠습니다.
Ref
https://theheydaze.tistory.com/550
01. 시큐리티 - HTTP Only 와 Secure Cookie
개념 브라우저(크롬, 사파리)에서 request(요청) GET 또는 POST 하게 되는 경우 모든 쿠키들이 서버에 넘어가 사용자를 체크합니다 오래전 부터 쿠키를 사용하고 있어 많은 사이트 대부분이 쿠키를
theheydaze.tistory.com
[프로그래밍 이론] JWT (Json Web Token)
▶ 주제 : JWT (Json Web Token). 서버가 클라이언트의 권한 및 인증정보가 표현된 JSON을 암호화 후 Token으로 발급하여, 클라이언트가 서버에 통신할 때 Session 없이 상태를 표현할 수 있도록 해주는 인
spiralmoon.tistory.com