Laravel Authentication Session Life Time 종료시 강제로 로그아웃 로그 쌓기

Laravel Auth Session을 통한 로그인,로그아웃 구현

라라벨에서는 사용자가 애플리케이션으로 인증하고 "로그인"할 수있는 Authentication(인증) 방법을 제공하기 때문에 손 쉽게 로그인 로그아웃 기능을 구현할 수 있다.  세션 스토리지와 쿠키를 사용하여 상태를 유지하는 session 가드와, 각 요청-request와 함께 전달되는 "API 토큰"을 사용하여 사용자를 인증하는 token 가드를 제공하는데, 최근 진행했던 프로젝트의 경우 다수의 사용자가 아닌 특정 사용자들이 이용하는 Contents Management System(이하 CMS)를 만드느라 session을 이용했다.

Session은 Redis에 저장하였고 session life time은 180분으로 설정하여 사용자가 180분동안 액션이 없을 경우 자동으로 세션이 끊겨 로그아웃이 진행되도록 만들었다.

로그아웃 로그 추가 요청

그런데 이게 왠말인가, 세션이 자동으로 끊길때를 로그로 남겨달라는 요청사항이 들어왔다. session 특성상 기간이 만료되면 session id가 사라져서 특정 사용자를 찾을 수 없었고, 특히 특정 사용자의 session이 만료될 때의 이벤트를 감지할만한 리스너 혹은 옵저버따위는 없다.

처음 생각한 방법은 커스텀 미들웨어를 만들어서 Auth::check()를 통과하지 못할경우 세션이 끊겼다는걸 감지하여 해당 시간을 기점으로 로그아웃 시간을 쌓으려 했었는데 그럴 경우 이미 session이 끊긴 상태라 유저를 특정지을 수 없었다.

두 번째로 생각한 방법은 브라우저 스크립트에서 30분마다 서버로 세션이 살아있나 감지하는 api를 보내고, 해당 api 에서 return해주는 값을 토대로 로그를 쌓으려고 하였지만, 이 방법에는 두가지 문제점이 존재하였다.

  1. api를 호출하는 것 자체가 session의 연장이 되기 때문에 절대로 로그아웃이 되지 않는다는 문제
  2. 호출 시간을 아슬아슬하게 session life time과 조절하여 1번의 문제를 해결하더라도, 특정 상황(와이파이가 끊기는 등 네트워크 문제)이 발생했을 경우 의도치 않는 오류 발생

세 번째로 생각한 방법은 Command 명령어와 Schedual을 사용하여 특정 시간마다 Redis에서 살아있는 로그인 관련 session id들을 다 가져와서 DB와 조회하여 세션이 종료된 모든 사용자들의 로그아웃 시간을 자동 update 해주기.

로그아웃 로그를 쌓기 위해 분단위로 (혹은 시간단위로) job을 실행해야 하는가에 대한 의구점이 들었지만, 요청사항이 워낙 확고했던 터라 세 번째 방법으로 진행하기로 했다.

구현

log_manager_login 이라는 테이블을 생성하고 LogManagerLogin Model과 LogManagerLoginRepository를 생성하였다. 사용자가 login 이라는 행위를 할 때마다 session_id 컬럼에 sessionId를 insert해주었고, 아래 코드를 통해서 전체 log중에 logout_at 컬럼이 null인 데이터만 가져와서 session_id 데이터와 Redis의 key()를 조회하여 해당 값이 없다면 세션이 종료된 걸로 간주하고 해당 데이터의 logout_at 값을 update해주었다.

class CheckStatusIsLoginIfNotChangLogout extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'check:status-login';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = '1분 단위로 로그인 세션을 체크하여 풀렸을 경우 로그아웃 시간을 업데이트 해준다';

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        $logManagerLoginRepository = new LogManagerLoginRepository();
        $items = $logManagerLoginRepository->findByDataLogoutIsNull();
        $redis = Redis::connection();
        $nowTime = date('Y-m-d H:i:s');

        foreach ($items as $item) {
            if ($item->session_id) {
                $key = "cms_cache_:".$item->session_id;
                $isLoginSession = $redis->get($key);

                if (empty($isLoginSession)) {
                    $todoLogoutItem = $logManagerLoginRepository->firstBySessionId($item->session_id);
                    $todoLogoutItem->state = 'logout';
                    $todoLogoutItem->logout_at = $nowTime;
                    $todoLogoutItem->save();
                }

            }
        }

        return Command::SUCCESS;
    }
}