로그인한 상태는 영원히 지속되어서는 안 된다. 한 번 확인한 신원을 언제까지고 유효하다고 두면, 그 사이에 새어 나간 세션이나 토큰이 무한정 통하게 되고, 공용 기기에 방치된 로그인이 다음 사람에게 넘어간다. 그렇다고 지나치게 자주 로그인을 요구하면 사용자가 지친다. 세션의 만료와 갱신은 이 안전과 편의 사이에서 균형을 잡는 설계다.


이번 편은 세션에 수명을 두는 두 가지 방식이 무엇인지, 활동에 따라 수명을 이어 주는 갱신이 어떻게 작동하는지, 위험이 큰 순간에 어떻게 다시 확인을 요구하는지, 로그인 유지 기능이 무엇을 맞바꾸는지, 그리고 만료를 다룰 때 흔히 놓치는 지점이 무엇인지를 다룬다. 앞서 세션과 토큰의 수명을 여러 번 짚었는데, 이번 편은 그 수명 관리 자체를 정면으로 들여다본다.

수명을 두는 두 방식

세션에 수명을 두는 방식은 크게 두 가지로 나뉜다. 하나는 마지막 활동으로부터 일정 시간이 지나면 만료시키는 유휴 만료이고, 다른 하나는 로그인한 시점으로부터 일정 시간이 지나면 무조건 만료시키는 절대 만료다. 두 방식은 겨냥하는 위험이 서로 달라, 대개 함께 쓰인다.


유휴 만료는 사용자가 활동을 멈춘 세션을 정리한다. 마지막 요청 이후 정해진 시간 동안 아무 요청이 없으면 세션을 끊는 것이다. 이 방식은 사용자가 자리를 비운 세션이 방치되는 위험을 줄인다. 공용 기기에 로그인한 채 자리를 떴을 때, 유휴 만료가 그 세션을 자동으로 닫아 다음 사람이 이어받지 못하게 한다.


절대 만료는 활동이 이어지더라도 세션에 최대 수명의 상한을 둔다. 로그인한 지 정해진 시간이 지나면, 계속 쓰고 있더라도 세션을 끊고 다시 로그인하게 한다. 이 방식은 세션이 무한정 살아 있는 것을 막는다. 유휴 만료만 있으면 계속 활동하는 세션은 영원히 유지될 수 있는데, 절대 만료가 그 상한을 그어 준다.


두 방식을 함께 두면 방치된 세션과 지나치게 오래된 세션이 모두 걸러진다. 유휴 만료가 멈춘 세션을 닫고, 절대 만료가 오래된 세션을 닫는다. 어느 하나만으로는 한쪽 위험만 막으므로, 둘을 겹쳐 두어야 세션의 수명이 온전히 통제된다. 각 만료의 시간은 서비스의 위험에 맞추어 정한다.

활동에 따라 수명을 잇기

유휴 만료를 두면서도 활동하는 사용자를 자주 끊지 않으려면, 활동이 있을 때마다 유휴 만료의 기준 시각을 뒤로 미루는 갱신이 필요하다. 사용자가 요청을 보낼 때마다 마지막 활동 시각을 새로 기록하면, 그 시각으로부터 다시 유휴 시간을 세게 된다. 이렇게 하면 계속 쓰는 사용자는 유휴 만료에 걸리지 않는다.


이 갱신은 활동이 이어지는 한 세션을 살려 두되, 활동이 멈추면 그때부터 유휴 시간을 세어 만료시킨다. 사용자가 쓰고 있는 동안은 끊기지 않고, 손을 뗀 뒤에야 정해진 시간이 지나 닫히는 것이다. 활동에 따라 수명이 미끄러지듯 이어지므로, 편의를 지키면서도 방치된 세션은 정리된다.


다만 이 갱신이 절대 만료까지 미루어서는 안 된다. 활동으로 미루어지는 것은 유휴 만료의 기준일 뿐, 로그인 시점부터 세는 절대 만료는 그대로 두어야 한다. 갱신이 절대 만료까지 뒤로 밀면 세션이 영원히 유지될 수 있으므로, 갱신은 유휴 쪽에만 적용하고 절대 상한은 고정해 둔다.


토큰 방식에서는 이 갱신이 짧은 토큰을 새로 발급받는 것으로 이루어진다. 짧은 토큰이 만료되면 갱신 수단으로 새 토큰을 받아 활동을 잇되, 갱신 수단 자체의 수명이 절대 상한의 역할을 한다. 세션이든 토큰이든, 활동으로 이어 주는 부분과 상한으로 끊는 부분을 나누어 두는 구조는 같다.

위험이 큰 순간의 재확인

세션이 유효하다는 것이 모든 동작을 허락한다는 뜻은 아니다. 위험이 큰 동작 앞에서는 세션이 살아 있더라도 신원을 다시 확인하는 것이 안전하다. 로그인 상태를 도용한 세션으로 그런 동작이 함부로 이루어지는 것을 막기 위해서다.


비밀번호나 이메일 주소를 바꾸는 동작, 결제처럼 되돌리기 어려운 동작이 재확인을 요구할 대표적인 순간이다. 이런 동작에서는 세션의 유효만으로 통과시키지 않고, 비밀번호를 다시 입력받거나 두 번째 수단을 다시 확인한다. 세션이 새어 나갔더라도 이 재확인을 통과하지 못하면 위험한 동작이 막힌다.


재확인은 그 순간의 신원을 다시 확인하는 것이므로, 오래전의 로그인만으로는 갈음할 수 없다. 로그인한 지 오래된 세션일수록 재확인의 필요가 커진다. 그래서 재확인이 필요한 동작에서는 최근에 신원을 확인했는지를 따지고, 오래되었으면 그 자리에서 다시 확인을 요구한다.


재확인의 결과는 짧은 시간만 유효하게 두는 것이 좋다. 한 번 재확인했다고 그 뒤로 오래 그 효력을 인정하면, 재확인의 의미가 옅어진다. 그래서 재확인은 방금 이루어졌을 때만 인정하고, 시간이 지나면 다시 요구한다. 위험이 큰 순간마다 그 순간의 신원을 확인하는 것이 재확인의 취지다.

로그인 유지가 맞바꾸는 것

사용자는 흔히 다음에도 로그인 상태로 남기를 원한다. 이 로그인 유지 기능은 편의를 위해 세션의 수명을 길게 두는 것인데, 그만큼 위험도 함께 커진다. 오래 유지되는 세션일수록 그 사이에 새어 나갈 창이 넓어지기 때문이다. 로그인 유지는 편의와 위험을 맞바꾸는 선택이다.


이 맞바꿈을 다루는 한 방법은, 오래 유지되는 부분과 실제 접근에 쓰이는 부분을 나누는 것이다. 긴 수명의 유지 수단을 따로 두고, 실제 접근은 그 수단으로 얻는 짧은 수명의 값으로 하게 한다. 그러면 오래 사는 것은 좀처럼 오가지 않고, 자주 오가는 것은 짧게 산다. 앞서 두 토큰 구조에서 본 분업이 여기서도 쓰인다.


로그인 유지를 켜 두었더라도 위험이 큰 동작 앞에서는 재확인을 요구해야 한다. 오래 유지된 세션은 그만큼 도용의 위험이 크므로, 중요한 동작에서는 그 유지에 기대지 않고 신원을 다시 확인한다. 유지의 편의는 낮은 위험의 동작에만 적용하고, 높은 위험의 동작에는 적용하지 않는 것이다.


유지 수단이 새어 나갔을 때를 대비해, 그것을 즉시 끊을 손잡이도 마련해야 한다. 사용자가 다른 기기의 로그인을 끊거나, 도용이 의심될 때 모든 유지를 해제할 수 있어야 한다. 오래 사는 값일수록 그것을 끊는 수단을 함께 갖추어야, 유지의 편의가 통제를 벗어나지 않는다.

만료를 사용자에게 알리기

세션이 만료되는 순간의 경험도 설계의 일부다. 사용자가 무언가를 하던 중에 세션이 갑자기 끊기면, 작업이 사라지거나 영문 모를 오류를 마주하게 된다. 그래서 만료가 다가올 때 이를 알리거나, 만료된 뒤에도 작업을 잃지 않게 하는 배려가 필요하다.


만료가 임박했을 때 미리 알려 주면, 사용자는 하던 일을 마무리하거나 세션을 이어 갈 기회를 얻는다. 곧 로그아웃된다는 안내와 함께 계속 있을지를 물으면, 사용자가 활동을 이어 세션을 갱신하거나 안전하게 마무리할 수 있다. 갑작스러운 끊김보다 예고된 만료가 훨씬 매끄럽다.


만료된 세션으로 요청이 오면, 그 상황을 분명한 신호로 알려야 한다. 신원이 확인되지 않았음을 나타내는 응답을 돌려주면, 클라이언트는 이를 받아 로그인 화면으로 유도하거나 다시 로그인하는 흐름으로 넘어간다. 만료를 애매하게 처리하면 사용자가 무엇이 잘못되었는지 알 수 없으므로, 만료는 명확한 신호로 드러내야 한다.


다시 로그인한 뒤에는 사용자가 하던 자리로 돌아갈 수 있게 하는 것이 좋다. 만료 때문에 처음 화면으로 밀려나면, 하던 일을 다시 찾아가야 하는 수고가 든다. 만료 시점의 위치를 기억해 두었다가 다시 로그인한 뒤 그 자리로 돌려보내면, 만료의 불편이 크게 줄어든다.

여러 기기의 세션을 함께 관리하기

한 사용자가 여러 기기에서 로그인하면 여러 세션이 동시에 살아 있게 된다. 이 여러 세션의 만료와 갱신을 함께 관리하는 것도 이 편의 주제다. 각 세션은 저마다 활동에 따라 갱신되고 각자의 상한으로 만료되지만, 사용자 단위로 이들을 함께 다루어야 할 때가 있다.


비밀번호를 바꾸거나 도용이 의심되는 순간에는, 그 사용자의 모든 세션을 한꺼번에 만료시켜야 한다. 한 세션만 끊고 나머지를 두면, 도용된 다른 세션이 그대로 살아남는다. 그래서 이런 순간에는 사용자 단위로 모든 세션을 끊어, 어느 기기에 남은 세션도 더는 통하지 않게 한다.


사용자에게 지금 살아 있는 세션의 목록을 보여 주는 것도 유용하다. 어느 기기에서 언제 로그인했는지를 보여 주면, 사용자가 낯선 세션을 알아채고 끊을 수 있다. 이 목록과 개별 종료 기능은 사용자가 자기 계정의 접근을 스스로 관리하게 돕는 방어 수단이 된다.


여러 세션을 관리하려면 서버가 각 세션의 상태를 쥐고 있어야 한다. 어느 세션이 살아 있고 언제 활동했는지를 서버가 기록해 두어야, 목록을 보여 주고 개별로 끊는 일이 가능해진다. 상태를 서버가 쥐는 세션 방식이 이런 관리에 유리하며, 토큰 방식에서는 갱신 수단에 상태를 두어 비슷한 관리를 이룬다.

만료를 다룰 때의 함정

가장 흔한 함정은 만료를 검사하지 않는 것이다. 세션이나 토큰에 만료 시각을 두고도 요청을 처리할 때 그 값을 확인하지 않으면, 만료된 것이 계속 통한다. 수명을 정한 의미가 사라지므로, 매 요청의 검증에 만료 확인을 반드시 넣어 기한이 지난 것은 걸러 내야 한다.


만료된 세션의 기록을 정리하지 않는 것도 흔한 문제다. 만료 표시만 하고 실제로 지우지 않으면 저장소에 죽은 기록이 쌓인다. 그래서 만료된 기록을 주기적으로 정리하는 절차를 함께 두어, 저장소가 무한정 커지는 것을 막아야 한다. 만료는 표시로 끝나는 것이 아니라 정리까지 이어져야 한다.


시각을 다루는 데서 오는 실수도 조심해야 한다. 서버와 클라이언트의 시각이 어긋나거나, 시간대를 잘못 다루면 만료 판정이 엉뚱해진다. 그래서 만료의 기준이 되는 시각은 서버가 일관된 기준으로 다루고, 클라이언트가 보낸 시각을 그대로 믿어 만료를 판단하지 않아야 한다. 만료의 판단은 언제나 서버가 쥐고 있어야 한다.


갱신을 너무 헐겁게 두어 사실상 만료가 없어지는 경우도 경계해야 한다. 활동이 있을 때마다 절대 상한까지 미루거나, 아주 작은 활동에도 수명을 크게 늘리면, 세션이 좀처럼 만료되지 않는다. 갱신은 유휴 쪽에만 적용하고 절대 상한은 고정하며, 갱신이 방어를 무력화하지 않도록 그 폭을 신중히 정해야 한다.


만료 시간을 정하는 기준

유휴 만료와 절대 만료의 시간을 얼마로 둘지는 서비스의 성격에 따라 달라진다. 정답이 하나로 정해져 있지 않으므로, 그 서비스가 다루는 것의 민감함과 사용자의 이용 방식을 함께 고려해 정한다. 위험이 클수록 시간을 짧게, 이용이 잦고 위험이 낮을수록 시간을 여유 있게 두는 것이 대체적인 방향이다.


다루는 정보가 민감할수록 만료를 짧게 잡는다. 금전이나 중요한 개인 정보를 다루는 서비스는 세션이 새어 나갔을 때의 피해가 크므로, 유휴 만료를 짧게 두어 방치된 세션이 오래 살아 있지 않게 한다. 반면 가벼운 열람 위주의 서비스는 만료를 길게 두어도 피해가 작으므로, 편의를 위해 여유를 둘 수 있다.


사용자가 어떤 환경에서 쓰는지도 기준이 된다. 공용 기기에서 자주 쓰이는 서비스라면 방치된 세션이 다음 사람에게 넘어갈 위험이 크므로 유휴 만료를 짧게 두고, 개인 기기에서 주로 쓰인다면 조금 여유를 둘 수 있다. 이용 환경의 위험을 가늠해 시간을 맞추면, 같은 서비스라도 상황에 맞는 균형을 잡는다.


한 번 정한 시간을 고정해 두기보다 운영하며 재검토하는 것이 좋다. 사용자가 지나치게 자주 끊긴다고 느끼면 이용이 불편해지고, 지나치게 오래 유지되면 위험이 커진다. 실제 이용의 양상과 사고의 징후를 보며 시간을 조정하되, 편의를 위해 만료를 늘릴 때는 그만큼 다른 방어를 두껍게 하는 균형을 함께 고려해야 한다.

정리하면 세션의 수명은 활동이 멈추면 끊는 유휴 만료와 오래되면 끊는 절대 만료를 함께 두어 통제하며, 활동에 따라 유휴 쪽만 이어 주되 절대 상한은 고정한다. 위험이 큰 순간에는 세션이 유효하더라도 재확인을 요구하고, 로그인 유지의 편의는 그 위험을 따로 통제한다. 만료는 사용자에게 명확히 알리고, 여러 세션은 함께 관리하며, 검증과 정리와 시각을 빠짐없이 다루어야 한다. 다음 편에서는 이 세션을 능동적으로 끝내는 로그아웃과 토큰 무효화를 들여다본다.