request->getJSON(true); $id = $data['id'] ?? null; $password = $data['password'] ?? null; $logintype = $data['logintype'] ?? null; if (!$id || !$password) { return $this->fail([ 'errorCode' => 1000, 'message' => '아이디 또는 비밀번호가 누락되었습니다.' ], 400); } $loginModel = new LoginModel(); $builder = $loginModel->getBuilderFor($logintype); // 모델을 통해 빌더를 가져옴 $user = $builder->where('ID', $id)->get()->getRowArray(); if (!$user) { return $this->fail([ 'errorCode' => 1001, 'message' => '존재하지 않는 아이디입니다.' ], 404); } // 비밀번호 검증 if (!password_verify($password, $user['PASSWORD'])) { return $this->fail([ 'errorCode' => 1002, 'message' => '비밀번호가 틀렸습니다.' ], 401); } unset($user['PASSWORD']); // 비밀번호 노출 방지 // JWT 토큰 생성에 필요한 값 로드 $jwtSecret = env('JWT_SECRET'); $kid = env('JWT_KID'); if (empty($jwtSecret) || empty($kid)) { return $this->failServerError('환경변수가 누락되었습니다. 관리자에게 문의하세요.'); } if (!class_exists('\App\Libraries\JwtLib\JWT')) { return $this->failServerError('JWT 라이브러리를 찾을 수 없습니다.'); } $issuedAt = time(); $accessExpire = $issuedAt + 60 * 15; // 15분 //$accessExpire = $issuedAt + 5; // 15분 $refreshExpire = $issuedAt + 60 * 60 * 24 * 14; // 14일 //$refreshExpire = $issuedAt + 5; // 14일 $accessPayload = [ 'iat' => $issuedAt, 'exp' => $accessExpire, 'sub' => $user['ID'], 'name' => $user['NAME'] ?? '', ]; // 리프레시 토큰 existing check $currentRefreshToken = $user['REFRESH_TOKEN'] ?? null; $validRefreshToken = null; // 토큰이 **없거나** (빈 값, null), 존재하지만 만료된 경우 모두 새로 발급 $needIssueRefresh = !$currentRefreshToken; // null 또는 빈 문자열 등 if ($currentRefreshToken) { // 기존 리프레시 토큰 유효성 검사 try { $key = new Key($jwtSecret, 'HS256'); $decoded = JWT::decode($currentRefreshToken, $key); if (isset($decoded->exp) && $decoded->exp > time()) { // 만료되지 않음, 재사용 $validRefreshToken = $currentRefreshToken; $needIssueRefresh = false; } else { // 만료됨 $needIssueRefresh = true; } } catch (\Throwable $e) { // 토큰이 변조됐거나 잘못된 경우에도 새로 발급 $needIssueRefresh = true; } } if ($needIssueRefresh) { // 없거나 만료/무효화 시 새로 발급 및 DB 업데이트 $refreshPayload = [ 'iat' => $issuedAt, 'exp' => $refreshExpire, 'sub' => $user['ID'], ]; try { $validRefreshToken = JWT::encode($refreshPayload, $jwtSecret, 'HS256', $kid); // ADM_LIST에 리프레시 토큰 값 업데이트 (신규 발급 포함) $builder->where('ID', $user['ID'])->update(['REFRESH_TOKEN' => $validRefreshToken]); } catch (\Throwable $e) { return $this->failServerError('JWT 생성 오류: ' . $e->getMessage()); } } try { $accessToken = JWT::encode($accessPayload, $jwtSecret, 'HS256', $kid); } catch (\Throwable $e) { return $this->failServerError('JWT 생성 오류: ' . $e->getMessage()); } return $this->respond([ 'status' => 'active', 'accessToken' => $accessToken, 'refreshToken' => $validRefreshToken, 'user' => $user, ]); } public function refreshToken() { $data = $this->request->getJSON(true); $refreshToken = $data['refreshToken'] ?? null; if (!$refreshToken) { return $this->fail('리프레시 토큰이 필요합니다.', 400); } $jwtSecret = env('JWT_SECRET'); $kid = env('JWT_KID'); try { $key = new Key($jwtSecret, 'HS256'); $headers = null; $decoded = JWT::decode($refreshToken, $key, $headers); if ($decoded->exp < time()) { return $this->fail('리프레시 토큰이 만료되었습니다.', 401); } $userId = $decoded->sub ?? null; if (!$userId) { return $this->fail('유효하지 않은 토큰입니다.', 401); } // ADM_LIST에서 해당 유저와 REFRESH_TOKEN 비교 $db = \Config\Database::connect(); $builder = $db->table('ADM_LIST'); $user = $builder->where('ID', $userId)->get()->getRowArray(); if (!$user) { return $this->fail('사용자를 찾을 수 없습니다.', 404); } unset($user['PASSWORD']); // DB에 저장된 리프레시 토큰과 요청 리프레시 토큰이 일치하지 않으면 거절 if (!isset($user['REFRESH_TOKEN']) || $user['REFRESH_TOKEN'] !== $refreshToken) { return $this->fail('리프레시 토큰 불일치 또는 무효한 요청입니다.', 401); } // 일치하면 액세스 토큰 발급 $issuedAt = time(); $expire = $issuedAt + 60 * 15; // 15분 //$expire = $issuedAt + 5; // 15분 $accessPayload = [ 'iat' => $issuedAt, 'exp' => $expire, 'sub' => $userId, 'name' => $user['NAME'] ?? '', ]; $accessToken = JWT::encode($accessPayload, $jwtSecret, 'HS256', $kid); return $this->respond([ 'accessToken' => $accessToken, 'user' => $user, ]); } catch (\Throwable $e) { return $this->fail('유효하지 않은 리프레시 토큰입니다.', 401); } } }