로그인이 신원을 확인해 문을 여는 일이라면, 로그아웃은 그 문을 다시 닫는 일이다. 얼핏 단순해 보이지만, 로그아웃은 인증 방식에 따라 난이도가 크게 갈린다. 서버가 상태를 쥐는 세션 방식에서는 기록을 지우면 그만이지만, 상태를 두지 않는 토큰 방식에서는 이미 발급된 토큰을 어떻게 무효로 만들지가 까다로운 문제가 된다. 로그아웃을 제대로 다루지 못하면, 사용자는 나갔다고 여기지만 실제로는 접근이 살아 있는 위험한 틈이 남는다.


이번 편은 로그아웃이 실제로 무엇을 하는 일인지, 세션 방식과 토큰 방식에서 각각 어떻게 이루어지는지, 무상태 토큰을 무효로 만드는 방법이 무엇인지, 여러 기기의 접근을 한꺼번에 끊는 전체 로그아웃을 어떻게 하는지, 그리고 로그아웃을 다룰 때 흔히 놓치는 지점이 무엇인지를 다룬다. 문을 여는 일만큼이나 닫는 일도 빈틈없이 설계되어야 한다.

로그아웃이 뜻하는 것

로그아웃은 지금까지 유효하던 인증 상태를 끊어, 그 뒤의 요청이 더 이상 그 사용자로 인정받지 못하게 하는 일이다. 사용자가 나가기를 원하는 순간, 그때까지 열려 있던 접근을 닫는 것이다. 이 닫힘이 확실히 이루어져야, 사용자가 나간 뒤에 그 접근이 남아 악용되는 일이 없다.


로그아웃이 온전히 이루어지려면 두 곳에서 상태를 정리해야 한다. 하나는 서버 쪽에서 그 인증을 더 이상 인정하지 않게 하는 것이고, 다른 하나는 클라이언트 쪽에서 인증에 쓰이던 값을 지우는 것이다. 어느 한쪽만 정리하면, 다른 쪽에 남은 흔적이 위험을 남긴다. 두 곳을 모두 정리해야 문이 완전히 닫힌다.


로그아웃의 어려움은 대개 서버 쪽 정리에 있다. 서버가 그 인증을 즉시 무효로 만들 수 있느냐가 방식에 따라 다르기 때문이다. 상태를 쥐고 있으면 즉시 끊을 수 있지만, 상태를 두지 않으면 이미 나간 인증을 되불러 무효로 만들 손잡이가 마땅치 않다. 이 차이가 세션과 토큰의 로그아웃을 가른다.


사용자의 관점에서 로그아웃은 확실해야 한다. 나가기를 눌렀는데 접근이 남아 있다면, 사용자는 안전하다고 믿지만 실제로는 그렇지 않은 위험한 상태에 놓인다. 특히 공용 기기에서 로그아웃은 다음 사람에게 접근이 넘어가는 것을 막는 마지막 방어이므로, 이 순간의 닫힘이 어긋나서는 안 된다.

세션 방식의 로그아웃

세션 방식에서 로그아웃은 서버 저장소의 해당 세션 기록을 지우는 것으로 이루어진다. 기록이 사라지면, 사용자의 기기에 식별자가 남아 있어도 가리키는 기록이 없으므로 더는 통하지 않는다. 상태를 서버가 쥐고 있기에, 그 상태를 지우는 것만으로 인증이 즉시 무효가 된다.


이 즉각적인 무효화가 세션 방식의 큰 장점이다. 로그아웃을 누른 순간 서버가 기록을 지우면, 그 뒤의 어떤 요청도 그 세션으로 인정받지 못한다. 이미 발급된 식별자를 회수하지 못해도, 가리키는 대상이 사라졌으므로 식별자 자체가 무의미해진다. 세션의 로그아웃은 이처럼 깔끔하다.


서버에서 기록을 지운 뒤에는 클라이언트의 쿠키도 함께 정리한다. 식별자를 담은 쿠키를 지우도록 응답을 보내, 기기에 남은 흔적까지 없앤다. 서버 기록만 지우고 쿠키를 두면, 그 쿠키가 새어 나갈 여지가 남고 사용자에게도 로그아웃이 어정쩡하게 보인다. 두 정리를 함께 해야 온전하다.


쿠키를 지울 때는 설정할 때와 같은 조건으로 지워야 한다. 범위나 조건이 어긋나면 쿠키가 그대로 남는다. 그래서 로그아웃 시 쿠키를 지우는 처리도 설정만큼 세심하게 다루어, 지웠다고 여겼으나 실제로는 남아 있는 일이 없게 한다. 세션의 로그아웃은 서버 기록 삭제와 정확한 쿠키 정리로 완성된다.

토큰 방식의 어려움

토큰 방식에서 로그아웃은 세션만큼 간단하지 않다. 서버가 토큰을 저장하지 않으므로, 이미 발급되어 클라이언트가 쥐고 있는 토큰을 되불러 무효로 만들 손잡이가 없다. 로그아웃을 눌러도 그 토큰 자체는 여전히 유효하며, 만료 시각이 될 때까지는 검증을 통과한다.


클라이언트 쪽에서 토큰을 지우는 것만으로는 부족하다. 정직한 사용자라면 지운 토큰을 더 쓰지 않겠지만, 그 토큰이 이미 새어 나갔다면 지우는 것과 무관하게 다른 곳에서 쓰일 수 있다. 로그아웃의 목적이 새어 나갔을 수 있는 접근을 끊는 것이라면, 클라이언트에서 지우는 것만으로는 그 목적을 이루지 못한다.


이 어려움은 토큰 방식이 상태를 두지 않는 데서 오는 근본적인 대가다. 무상태의 이점을 누리는 대신, 즉시 무효화라는 손잡이를 잃은 것이다. 그래서 토큰 방식에서 로그아웃을 제대로 다루려면, 무상태의 이점을 얼마간 양보해 최소한의 상태를 두는 절충이 필요하다.


짧은 수명은 이 어려움을 완화하는 첫 번째 장치다. 토큰이 곧 만료되면, 로그아웃 뒤에도 그 토큰이 통하는 시간이 짧아 피해가 제한된다. 실제 접근에 쓰이는 토큰을 짧게 두고, 로그아웃 시 그것을 새로 발급받는 통로인 갱신 수단을 끊으면, 이미 발급된 짧은 토큰이 곧 만료되며 접근이 자연스럽게 닫힌다.

폐기 목록으로 무효화하기

짧은 수명만으로 부족하고 즉시 무효화가 필요하다면, 서버가 무효로 만들 토큰의 목록을 두는 방법이 있다. 각 토큰에 고유한 표를 담아 두고, 무효로 만들 토큰의 표를 폐기 목록에 올린 뒤, 검증할 때 그 목록에 있는지 대조하는 것이다. 목록에 오른 토큰은 만료 전이라도 거부된다.


이 방법은 무상태의 이점을 얼마간 되돌려 놓는다. 모든 토큰을 저장하는 것이 아니라 무효로 만든 것만 목록에 두므로, 저장의 부담이 세션 방식보다 가볍다. 대부분의 토큰은 여전히 저장 없이 검증되고, 무효화가 필요한 소수만 목록으로 관리한다. 순수한 무상태와 완전한 상태 사이의 절충이다.


폐기 목록은 무한정 커지지 않도록 관리해야 한다. 목록에 올린 토큰도 원래의 만료 시각이 지나면 더 대조할 필요가 없으므로, 그 시점에 목록에서 지운다. 만료된 토큰은 어차피 다른 검증에서 걸러지기 때문이다. 이렇게 하면 폐기 목록의 크기가 무효화된 토큰 가운데 아직 만료되지 않은 것들로만 유지된다.


목록을 대조하는 비용도 고려해야 한다. 매 요청마다 폐기 목록을 확인하면, 무상태의 이점 가운데 하나인 저장소 조회 없는 검증을 일부 내주게 된다. 그래서 이 방법은 즉시 무효화의 필요와 그 비용을 저울질해 도입한다. 실제 접근 토큰은 짧게 두어 목록 없이 넘기고, 긴 갱신 수단에만 폐기 목록을 적용하는 절충이 흔히 쓰인다.

여러 기기를 한꺼번에 끊기

로그아웃에는 지금 이 기기에서만 나가는 것과, 모든 기기에서 한꺼번에 나가는 것이 있다. 도용이 의심되거나 비밀번호를 바꾸는 순간에는, 지금 기기뿐 아니라 그 사용자의 모든 접근을 한꺼번에 끊어야 한다. 한 기기만 끊고 나머지를 두면, 도용된 다른 접근이 그대로 살아남기 때문이다.


세션 방식에서 전체 로그아웃은 그 사용자에게 딸린 모든 세션 기록을 지우는 것으로 이루어진다. 사용자 단위로 세션을 조회할 수 있으므로, 그 사용자의 세션을 모두 찾아 지우면 어느 기기에 남은 식별자도 통하지 않게 된다. 상태를 서버가 쥐는 덕에 이 일괄 종료가 깔끔하게 이루어진다.


토큰 방식에서는 전체 로그아웃이 조금 더 복잡하다. 각 토큰을 폐기 목록에 올리는 방식으로는 발급된 토큰을 일일이 찾아야 하므로, 대신 그 사용자의 토큰을 한꺼번에 무효로 만드는 기준을 두는 방법이 쓰인다. 사용자마다 기준 시각을 두고, 그 시각 이전에 발급된 토큰을 모두 거부하는 것이다. 전체 로그아웃 시 이 기준 시각을 지금으로 올리면, 이전에 발급된 토큰이 일제히 무효가 된다.


이 기준 시각 방식은 개별 폐기보다 다루기 쉽다. 발급된 토큰을 하나하나 찾을 필요 없이, 기준 시각 하나만 올리면 그 사용자의 모든 옛 토큰이 함께 무효가 되기 때문이다. 검증할 때 토큰의 발급 시각이 기준 시각보다 이전이면 거부하면 된다. 전체 로그아웃과 비밀번호 변경 같은 순간에 이 방식이 특히 잘 맞는다.

강제 무효화가 필요한 순간

사용자가 스스로 로그아웃하지 않아도, 서버가 나서서 인증을 끊어야 하는 순간이 있다. 비밀번호가 바뀌었을 때, 도용이 의심될 때, 권한이 크게 바뀌었을 때가 그렇다. 이런 순간에는 기존의 인증을 그대로 두는 것이 위험하므로, 서버가 능동적으로 무효화한다.


비밀번호를 바꾸는 순간이 대표적이다. 비밀번호를 바꾸는 이유 가운데 하나가 도용의 우려인데, 바꾼 뒤에도 이전에 열린 접근이 살아 있으면 바꾼 의미가 옅어진다. 그래서 비밀번호를 바꿀 때는 그 사용자의 기존 인증을 모두 무효로 만들어, 새 비밀번호로 다시 로그인하게 하는 것이 안전하다. 다만 지금 비밀번호를 바꾼 그 세션은 남겨 두어 사용자가 곧바로 밀려나지 않게 배려한다.


도용이 의심되는 신호가 감지될 때도 강제 무효화가 필요하다. 낯선 곳에서의 접근이나 이상한 활동이 포착되면, 그 사용자의 인증을 끊고 다시 신원을 확인받게 한다. 의심스러운 순간에는 접근을 유지하기보다 끊고 다시 확인하는 편이 안전하다. 이때 사용자에게 알림을 보내 무슨 일이 있었는지 알리는 것이 좋다.


권한이 줄어드는 변경에서도 무효화를 고려해야 한다. 특히 토큰에 권한이 담겨 있다면, 권한이 줄었는데도 옛 토큰이 옛 권한을 물고 다닐 수 있다. 그래서 권한이 줄어드는 순간에는 관련된 인증을 무효로 만들어, 새 권한이 반영된 인증을 다시 받게 한다. 강제 무효화는 이처럼 상태의 변화가 인증에 즉시 반영되게 하는 손잡이다.

클라이언트 쪽 정리

서버에서 인증을 무효로 만들었다면, 클라이언트에 남은 인증 값도 지워야 한다. 서버가 거부하더라도 클라이언트에 값이 남아 있으면, 그 값이 새어 나갈 여지가 남고 사용자에게도 로그아웃이 어정쩡하게 보인다. 그래서 로그아웃 처리는 서버의 무효화와 클라이언트의 정리를 함께 수행한다.


쿠키에 담긴 인증 값은 지우도록 응답을 보내 정리하고, 다른 저장 공간에 둔 값은 그 공간에서 지운다. 인증에 쓰이던 값을 남김없이 지워야, 그 값을 통한 접근의 흔적이 기기에 남지 않는다. 특히 공용 기기에서는 이 정리가 다음 사람에게 접근이 넘어가는 것을 막는 마지막 방어가 된다.


클라이언트 정리는 서버 정리를 대신할 수 없다는 점을 분명히 해야 한다. 클라이언트에서 값을 지우는 것은 정직한 사용자의 편의와 위생을 위한 것일 뿐, 이미 새어 나간 값을 무력화하지는 못한다. 새어 나갔을 수 있는 접근을 실제로 끊는 것은 서버의 무효화이므로, 두 정리는 각자의 역할이 있고 어느 하나가 다른 하나를 대신하지 않는다.


로그아웃 뒤에 화면을 어디로 보낼지도 정리의 일부다. 로그아웃한 사용자를 로그인 상태에서만 볼 수 있는 화면에 그대로 두면 혼란이 생기므로, 공개된 화면이나 로그인 화면으로 보낸다. 로그아웃이 상태의 정리뿐 아니라 사용자 경험의 매듭까지 포함한다는 점을 놓치지 말아야 한다.

로그아웃을 다룰 때의 함정

가장 흔한 함정은 클라이언트에서 값을 지우는 것으로 로그아웃을 끝냈다고 여기는 것이다. 특히 토큰 방식에서 토큰을 지우기만 하면, 이미 새어 나간 토큰은 만료 전까지 그대로 통한다. 즉시 무효화가 필요하다면 짧은 수명이나 폐기 목록, 기준 시각 같은 서버 쪽 장치를 함께 두어야 한다.


쿠키를 설정과 다른 조건으로 지우려다 실패하는 것도 흔한 함정이다. 지우는 요청의 범위나 조건이 설정 때와 어긋나면 쿠키가 그대로 남는다. 지웠다고 여겼으나 남아 있는 이 상태는 특히 위험하므로, 쿠키를 지울 때는 설정 때의 조건을 그대로 맞추어야 한다.


전체 로그아웃을 두지 않아 도용된 다른 세션을 끊지 못하는 경우도 경계해야 한다. 지금 기기의 로그아웃만 있고 모든 기기를 끊는 수단이 없으면, 도용이 의심될 때 사용자가 할 수 있는 일이 없다. 그래서 비밀번호 변경이나 도용 대응과 맞물린 전체 로그아웃을 함께 마련해 두어야 한다.


로그아웃 자체가 남용되지 않게 하는 것도 잊지 말아야 한다. 다른 사용자의 로그아웃을 함부로 일으킬 수 있으면, 그것이 방해의 수단이 된다. 그래서 로그아웃 요청도 그것을 요청한 사용자가 자신의 것을 끊는지 확인해, 남의 인증을 임의로 끊지 못하게 해야 한다. 문을 닫는 일에도 그 문의 주인만 닫을 수 있게 하는 확인이 필요하다.


정리하면 로그아웃은 서버에서 인증을 무효로 만들고 클라이언트에서 인증 값을 지우는 두 정리로 완성되며, 세션 방식은 기록을 지워 즉시 끊지만 토큰 방식은 짧은 수명과 폐기 목록, 기준 시각 같은 장치로 무효화를 이룬다. 여러 기기를 한꺼번에 끊는 전체 로그아웃과 서버가 나서는 강제 무효화를 함께 갖추고, 클라이언트 정리가 서버 무효화를 대신하지 못함을 잊지 말아야 한다. 다음 편에서는 인증을 통과한 사용자가 무엇을 할 수 있는지를 정하는 권한과 역할의 설계를 들여다본다.