8. C# 재접속 토큰 검증 / 세션 탈취 방지
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 재접속 토큰 검증 / 세션 탈취(hijacking) 방지
// ----------------------------------------------------------------------------
// 시나리오:
// - 모바일 게임. 네트워크가 자주 끊겨 "재접속(resume)"이 빈번하다.
// - 최초 로그인 성공 시 서버가 ResumeToken 을 발급해 클라에 준다.
// - 끊긴 세션은 ResumeWindow(예: 60초) 동안 보관되고, 클라가 같은 토큰을
// 들고 재접속하면 기존 세션(인벤토리/위치/매칭 상태)을 그대로 이어받는다.
// - 재접속 요청은 여러 IO 스레드에서 동시에 들어올 수 있다.
//
// 요구사항:
// - 토큰을 모르는 제3자가 남의 세션을 가로채면 안 된다(세션 탈취 방지).
// - 토큰은 ResumeWindow 안에서만, 그리고 한 번의 재접속에만 유효해야 한다.
// - 같은 토큰으로 동시에 두 클라가 들어오는 race 에서도 하나만 성공해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
using System.Net;
public sealed class PlayerSession
{
public long AccountId;
public string ResumeToken; // (A)
public IPAddress ClientIp;
public DateTime DisconnectedAt;
public bool Connected = true;
public void Rebind(System.Net.Sockets.Socket newSocket)
{
// 새 소켓으로 교체하고 세션 상태 이어받기 ...
Connected = true;
}
}
public sealed class ResumeService
{
// 끊김 보관소: token -> 세션
private readonly Dictionary<string, PlayerSession> _suspended =
new Dictionary<string, PlayerSession>();
private readonly object _lock = new object();
private static readonly TimeSpan ResumeWindow = TimeSpan.FromSeconds(60);
// 최초 로그인 성공 시 토큰 발급
public string IssueToken(PlayerSession s)
{
// 짧고 예측하기 쉬운 토큰 (계정 기반) (B)
string token = "R" + s.AccountId.ToString();
s.ResumeToken = token;
return token;
}
// 끊김 시 호출: 보관소에 넣어 둔다
public void Suspend(PlayerSession s)
{
s.Connected = false;
s.DisconnectedAt = DateTime.UtcNow;
lock (_lock)
{
_suspended[s.ResumeToken] = s;
}
}
// 재접속 요청 처리: 클라가 보낸 token 으로 기존 세션을 찾아 이어받는다.
public PlayerSession TryResume(string token, System.Net.Sockets.Socket newSocket,
IPAddress fromIp)
{
PlayerSession s;
lock (_lock)
{
if (!_suspended.TryGetValue(token, out s)) // (C)
return null;
// 만료 검사
if (DateTime.UtcNow - s.DisconnectedAt > ResumeWindow) // (D)
{
_suspended.Remove(token);
return null;
}
}
// 토큰이 맞으면 세션을 넘겨준다. (E)
s.ClientIp = fromIp;
s.Rebind(newSocket);
return s;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 재접속 토큰 검증 / 세션 탈취(hijacking) 방지
// ----------------------------------------------------------------------------
// 시나리오:
// - 모바일 게임. 네트워크가 자주 끊겨 "재접속(resume)"이 빈번하다.
// - 최초 로그인 성공 시 서버가 ResumeToken 을 발급해 클라에 준다.
// - 끊긴 세션은 ResumeWindow(예: 60초) 동안 보관되고, 클라가 같은 토큰을
// 들고 재접속하면 기존 세션(인벤토리/위치/매칭 상태)을 그대로 이어받는다.
// - 재접속 요청은 여러 IO 스레드에서 동시에 들어올 수 있다.
//
// 요구사항:
// - 토큰을 모르는 제3자가 남의 세션을 가로채면 안 된다(세션 탈취 방지).
// - 토큰은 ResumeWindow 안에서만, 그리고 한 번의 재접속에만 유효해야 한다.
// - 같은 토큰으로 동시에 두 클라가 들어오는 race 에서도 하나만 성공해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <unordered_map>
#include <mutex>
#include <string>
#include <chrono>
#include <cstdint>
struct Socket { int fd; };
class PlayerSession {
public:
int64_t accountId;
std::string resumeToken; // (A)
std::string clientIp;
std::chrono::steady_clock::time_point disconnectedAt;
bool connected = true;
void Rebind(Socket* newSocket) {
// 새 소켓으로 교체하고 세션 상태 이어받기 ...
connected = true;
}
};
class ResumeService {
// 끊김 보관소: token -> 세션 (raw 포인터)
std::unordered_map<std::string, PlayerSession*> suspended_;
std::mutex mtx_;
std::chrono::seconds resumeWindow_{60};
public:
// 최초 로그인 성공 시 토큰 발급
std::string IssueToken(PlayerSession* s) {
// 짧고 예측하기 쉬운 토큰 (계정 기반) (B)
std::string token = "R" + std::to_string(s->accountId);
s->resumeToken = token;
return token;
}
// 끊김 시 호출: 보관소에 넣어 둔다
void Suspend(PlayerSession* s) {
s->connected = false;
s->disconnectedAt = std::chrono::steady_clock::now();
std::lock_guard<std::mutex> lk(mtx_);
suspended_[s->resumeToken] = s;
}
// 재접속 요청 처리: 클라가 보낸 token 으로 기존 세션을 찾아 이어받는다.
PlayerSession* TryResume(const std::string& token, Socket* newSocket,
const std::string& fromIp) {
PlayerSession* s = nullptr;
{
std::lock_guard<std::mutex> lk(mtx_);
auto it = suspended_.find(token); // (C)
if (it == suspended_.end()) return nullptr;
s = it->second;
// 만료 검사
auto now = std::chrono::steady_clock::now();
if (now - s->disconnectedAt > resumeWindow_) { // (D)
suspended_.erase(it);
return nullptr;
}
}
// 토큰이 맞으면 세션을 넘겨준다. (E)
s->clientIp = fromIp;
s->Rebind(newSocket);
return s;
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.