From 76260572b176de4dee39bfb37ebabce1f9ae104b Mon Sep 17 00:00:00 2001 From: taekyoungc Date: Wed, 24 Jun 2026 18:40:20 +0900 Subject: [PATCH] =?UTF-8?q?feat(security):=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=95=8C=EB=A6=BC=20=E2=80=94=20=EC=A7=81=EC=A0=84=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=EA=B3=BC=20IP=20=EB=B9=84=EA=B5=90,?= =?UTF-8?q?=20=EC=83=88=20=ED=99=98=EA=B2=BD=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=8B=9C=20=EA=B2=BD=EA=B3=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit completeLogin에서 직전 성공 로그인 IP와 비교해 다르면 로그인 메시지에 경고 표시(이전 로그인 일시·IP 안내). 기존 member_log 활용, 외부 인프라 불필요. Co-Authored-By: Claude Opus 4.8 --- app/Controllers/Auth.php | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/app/Controllers/Auth.php b/app/Controllers/Auth.php index d34aa3e..ff94fb9 100644 --- a/app/Controllers/Auth.php +++ b/app/Controllers/Auth.php @@ -565,16 +565,39 @@ class Auth extends BaseController 'mb_locked_until' => null, ]); + // 로그인 알림 — 직전 성공 로그인과 IP 비교(새 IP면 경고). 현재 로그인 기록 INSERT 이전에 조회. + $loginNotice = ''; + try { + $curIp = (string) $this->request->getIPAddress(); + $prev = model(MemberLogModel::class) + ->where('mb_idx', (int) $member->mb_idx) + ->where('mll_success', 1) + ->orderBy('mll_idx', 'DESC') + ->first(); + if ($prev) { + $prevIp = (string) ($prev->mll_ip ?? ''); + $prevAt = (string) ($prev->mll_regdate ?? ''); + if ($prevIp !== '' && $prevIp !== $curIp) { + $loginNotice = ' ⚠ 새로운 환경(IP ' . $curIp . ')에서 로그인되었습니다. 본인이 아니면 비밀번호를 변경하세요. (이전 로그인: ' . $prevAt . ' · ' . $prevIp . ')'; + } elseif ($prevAt !== '') { + $loginNotice = ' (이전 로그인: ' . $prevAt . ')'; + } + } + } catch (\Throwable $e) { + // 알림 계산 실패는 로그인에 영향 주지 않음 + } + $this->insertMemberLog($logData, true, '로그인 성공', (int) $member->mb_idx); + $successMsg = '로그인되었습니다.' . $loginNotice; if ((int) $member->mb_level === \Config\Roles::LEVEL_LOCAL_ADMIN) { - return redirect()->to(site_url('admin'))->with('success', '로그인되었습니다.'); + return redirect()->to(site_url('admin'))->with('success', $successMsg); } if (\Config\Roles::isSuperAdminEquivalent((int) $member->mb_level)) { - return redirect()->to(site_url('admin/select-local-government'))->with('success', '로그인되었습니다.'); + return redirect()->to(site_url('admin/select-local-government'))->with('success', $successMsg); } - return redirect()->to(site_url('/'))->with('success', '로그인되었습니다.'); + return redirect()->to(site_url('/'))->with('success', $successMsg); } private function buildLogData(string $mbId, ?int $mbIdx): array