[실전 보안 02] 로그인은 나를 어떻게 기억할까

지난 편에서 신생 사이트가 왜 자동 공격의 표적이 되는지, 그리고 방어를 여러 겹으로 쌓아야 한다는 이야기를 했다. 이번 편은 그 방어선의 가장 안쪽, 사용자가 누구인지 확인하는 인증에서 시작한다. 로그인 버튼을 누르고 나면 사이트는 그다음부터 나를 계속 알아본다. 글을 쓸 때도, 댓글을 달 때도, 다시 접속해도 나임을 안다. 대체 사이트는 무엇을 근거로 나를 기억하는 걸까. 이 질문을 파고들면서 나는 인증 설계의 두 갈래 길을 만났고, 우리 커뮤니티가 어느 쪽을 왜 골랐는지 정리하게 됐다.


1. 요청마다 나를 증명해야 하는 이유

웹의 기본 통신 방식은 사실 건망증이 심하다. 브라우저가 서버에 요청을 보내고 응답을 받으면 그 대화는 그걸로 끝이다. 다음 요청이 오면 서버는 이 사람이 방금 전 그 사람인지 전혀 기억하지 못한다. 매 요청이 초면인 셈이다. 이걸 무상태라고 부른다. 이 성질 덕분에 서버는 가볍고 확장하기 쉽지만, 로그인처럼 이전 상태를 이어 가야 하는 기능에는 문제가 된다.


그래서 로그인이 성공하면 서버는 사용자에게 일종의 증표를 하나 쥐여 준다. 이후 사용자는 요청을 보낼 때마다 이 증표를 함께 내민다. 서버는 증표를 확인하고 아, 아까 로그인한 그 사람이구나 하고 알아본다. 놀이공원 입장 팔찌와 비슷하다. 입구에서 표를 검사한 뒤 팔찌를 채워 주면, 안에서는 팔찌만 보여 주면 매번 표를 다시 살 필요가 없다. 이 증표를 어떻게 만들고 어디에 보관하느냐가 인증 설계의 핵심 갈림길이다.


중요한 건 이 증표가 위조되거나 도난당하면 곧바로 계정 탈취로 이어진다는 점이다. 누군가 내 팔찌를 복제하면 그 사람이 나 행세를 할 수 있다. 그래서 증표는 예측할 수 없게 만들어야 하고, 오가는 길에서 가로채이지 않게 암호화된 통신으로만 주고받아야 하며, 훔쳐 가도 오래 쓰지 못하게 수명을 정해 둬야 한다. 인증을 설계한다는 건 결국 이 증표를 얼마나 안전하게 다루느냐의 문제였다.


2. 세션 방식, 서버가 기억하는 쪽

첫 번째 길은 세션 방식이다. 로그인에 성공하면 서버가 자기 저장소에 이 사용자가 로그인했다는 기록을 남기고, 그 기록을 가리키는 짧은 식별자만 사용자에게 준다. 사용자는 요청마다 이 식별자를 내밀고, 서버는 저장소를 뒤져 해당 기록을 찾아 신원을 확인한다. 핵심은 진짜 정보가 서버 안에 있다는 점이다. 사용자가 든 건 사물함 번호표일 뿐이고, 내용물은 서버 사물함에 들어 있다.


이 방식의 가장 큰 장점은 즉시 무효화가 쉽다는 것이다. 어떤 계정이 탈취된 것 같으면 서버 저장소에서 그 기록을 지워 버리면 끝이다. 그 순간부터 해당 번호표는 아무 의미가 없어진다. 그래서 은행이나 의료처럼 로그아웃을 즉시 강제해야 하는 서비스는 세션 방식을 선호한다. 사용자를 서버 마음대로 언제든 끊어 낼 수 있다는 통제력이 강점이다.


단점은 서버가 모든 사용자의 로그인 상태를 계속 저장하고 조회해야 한다는 부담이다. 사용자가 늘어나면 저장소도 커지고, 요청마다 저장소를 뒤지는 비용도 든다. 서버가 여러 대로 나뉘어 있으면 어느 서버에 물어봐도 같은 답이 나오도록 저장소를 공유해야 하는 숙제도 생긴다. 규모가 커질수록 이 상태를 관리하는 일 자체가 무거워진다.


3. 토큰 방식, 증표 안에 정보를 담는 쪽

두 번째 길은 토큰 방식이다. 대표적인 형태가 흔히 제이슨 웹 토큰이라 부르는 것이다. 이 방식은 로그인 성공 시 사용자가 누구인지, 언제까지 유효한지 같은 정보를 증표 자체에 적어 넣고, 서버의 비밀 열쇠로 서명을 붙여 사용자에게 준다. 사용자는 이 증표를 내밀고, 서버는 저장소를 뒤질 필요 없이 서명만 검증하면 곧바로 신원을 확인한다. 정보가 서버가 아니라 증표 안에 들어 있는 셈이다.


여기서 자주 오해하는 부분이 있다. 증표 안의 내용은 암호화된 게 아니라 그냥 인코딩된 것이라 누구나 열어 볼 수 있다. 비밀은 내용을 감추는 게 아니라 서명에 있다. 서버만 아는 비밀 열쇠로 서명했기 때문에, 공격자가 내용을 조금이라도 바꾸면 서명이 깨져 위조가 즉시 들통난다. 그래서 증표 안에는 비밀번호 같은 민감 정보를 절대 넣지 않고, 신원 식별에 필요한 최소한만 담아야 한다.


토큰 방식의 장점은 서버가 아무것도 기억하지 않아도 된다는 것이다. 서명 검증만으로 신원을 확인하니 저장소 조회가 없고, 서버가 여러 대로 흩어져 있어도 같은 열쇠만 있으면 어느 서버든 판단할 수 있다. 확장성이 좋다는 뜻이다. 대신 단점도 명확하다. 한번 발급한 증표는 유효 기간이 끝날 때까지 서버가 강제로 회수하기 어렵다. 이미 나간 증표를 되돌릴 수 없다는 점이 세션과 정반대의 약점이다.


4. 우리 사이트가 토큰을 고른 이유

내가 만든 커뮤니티는 서버리스 환경, 그러니까 요청이 올 때마다 짧게 깨어났다가 사라지는 함수들 위에서 돌아간다. 이런 환경에서는 서버가 상태를 계속 붙들고 있기가 구조적으로 껄끄럽다. 요청마다 별도의 저장소를 조회해 세션을 확인하면 그만큼 응답이 느려지고 비용도 오른다. 무상태로 서명만 검증하면 되는 토큰 방식이 이 환경에는 훨씬 자연스러웠다.


대신 토큰의 약점인 회수 불가 문제를 완화하려고 몇 가지 장치를 뒀다. 우선 증표의 수명을 지나치게 길게 잡지 않았다. 수명이 짧으면 설령 도난당해도 공격자가 쓸 수 있는 시간이 제한된다. 그리고 증표는 자바스크립트가 함부로 읽지 못하는 형태로 브라우저에 보관해, 사이트에 심어진 악성 스크립트가 증표를 훔쳐 가는 경로를 줄였다. 이 부분은 나중에 콘텐츠 보안 편에서 더 자세히 다루겠다.


여기서 가장 중요한 건 서명에 쓰는 비밀 열쇠였다. 이 열쇠가 유출되면 공격자가 마음대로 유효한 증표를 찍어 낼 수 있다. 어떤 계정이든 로그인 없이 흉내 낼 수 있게 되니, 사실상 사이트 전체가 뚫리는 것이다. 그래서 이 열쇠는 절대로 코드 안에 적어 두면 안 되고, 배포 환경의 비밀 저장소에 따로 보관해야 한다. 이 시크릿 관리는 이 시리즈 후반에 독립된 편으로 다룰 만큼 중요한 주제다.


5. 정답은 없고 맥락만 있다

세션이냐 토큰이냐를 두고 어느 쪽이 무조건 옳다고 말하는 글을 종종 본다. 하지만 직접 겪어 보니 그런 절대 정답은 없었다. 즉시 로그아웃을 강제해야 하는 민감한 서비스라면 세션의 통제력이 값지고, 서버가 흩어져 있고 상태를 줄여야 하는 환경이라면 토큰의 무상태성이 값지다. 중요한 건 유행이 아니라 내 서비스의 구조와 위협 상황에 무엇이 맞느냐였다.


이 판단을 위해 나는 몇 가지 질문을 스스로에게 던졌다. 내 서버는 상태를 저장하기 좋은 구조인가 아니면 무상태가 자연스러운가. 로그아웃을 즉시 강제해야 할 만큼 민감한 데이터를 다루는가. 사용자 규모가 저장소 부담을 걱정할 만큼 커질 것인가. 이 질문들에 답을 적어 보니 우리 커뮤니티에 맞는 방향이 저절로 드러났다. 남들이 좋다고 하는 방식을 따라가는 게 아니라, 내 상황을 구체적인 질문으로 쪼개 답하는 과정이 결정의 질을 훨씬 높여 줬다.


실제로는 두 방식을 섞어 쓰는 경우도 많다. 짧은 수명의 토큰으로 평소 요청을 가볍게 처리하되, 세션과 비슷한 별도의 갱신용 증표를 서버가 관리해 필요할 때 강제로 끊는 식이다. 이렇게 하면 무상태의 이점과 즉시 회수의 이점을 어느 정도 함께 취할 수 있다. 방식을 하나로 못 박기보다 상황에 맞게 조합하는 유연함이 실무에서는 더 쓸모 있었다.


한 가지 덧붙이자면, 로그인 상태를 유지하는 편리함과 안전 사이에도 저울질이 필요했다. 증표의 수명을 길게 잡으면 사용자는 매번 다시 로그인하지 않아 편하지만, 그만큼 도난당한 증표의 위험 시간도 길어진다. 반대로 수명을 너무 짧게 잡으면 안전하긴 해도 사용자가 자꾸 로그아웃되는 불편을 겪는다. 나는 평소 요청에 쓰는 증표는 짧게, 그 증표를 조용히 갱신해 주는 별도 장치는 조금 더 길게 두어 두 마리 토끼를 어느 정도 잡으려 했다. 보안 설계는 이렇게 늘 편의와의 줄다리기였다.


어느 방식을 고르든 결국 인증의 안전은 증표 하나에 달려 있다. 그런데 그 증표를 발급하기 위한 대전제가 있다. 로그인 자체가 안전해야 한다는 것이다. 아무리 증표를 잘 관리해도 비밀번호가 허술하게 저장돼 있으면 애초에 로그인 관문부터 뚫린다. 그래서 다음 편에서는 비밀번호를 어떻게 저장해야 하는지, 왜 비밀번호를 그대로 저장하면 절대 안 되는지, 유출에 대비한 해싱이 무엇인지를 실제 사례와 함께 풀어 보겠다.