본문으로 건너뛰기

관리자 동시 접속 보안 강화.

PHP 7 · GODO25
변경 파일 수: 5개
변경 파일 목록
5
변경 파일 코드
추가삭제
Admin/base/duplicate_login.php+84
변경 전duplicate_login.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후duplicate_login.php
<link rel="stylesheet" href="<?= PATH_ADMIN_GD_SHARE ?>css/duplicate-login.css"/>
<form id="frmDuplicateLogin" action="login_ps.php" method="post">
<input type="hidden" name="mode" value="concurrentLoginConfirm"/>
<input type="hidden" name="returnUrl" value=""/>
 
<div class="duplicate-login-backdrop">
<div class="duplicate-login-dialog">
<a href="/base/login_ps.php?mode=logout" class="close duplicate-login-close" aria-label="닫기">&times;</a>
<h2 class="duplicate-login-title">고객님의 계정이 이미 사용중입니다.</h2>
 
<div class="duplicate-login-content">
<p class="duplicate-login-desc">
• 고객님의 계정이 다른 IP에서 로그인되었습니다. 아래 버튼을 선택하여 로그인을 진행해주세요.<br>
• 안전하게 이용하려면 이전 로그인을 종료하고 새로 로그인해주세요.
</p>
 
<table class="duplicate-login-ip-table">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tbody>
<tr>
<th>현재 로그인한 IP</th>
<td><?= gd_htmlspecialchars($currentLoginIp) ?></td>
</tr>
<tr>
<th>이전 로그인한 IP</th>
<td><?= gd_htmlspecialchars($lastLoginIp) ?></td>
</tr>
</tbody>
</table>
 
<div class="duplicate-login-options">
<label class="duplicate-login-option">
<input type="radio" name="keepExisting" value="y" checked />
이전 로그인을 그대로 유지한 상태에서 로그인 (동시 로그인 허용)
</label>
<label class="duplicate-login-option">
<input type="radio" name="keepExisting" value="n" />
이전 로그인은 종료하고, 새로 로그인
</label>
</div>
</div>
 
<div class="duplicate-login-actions">
<a href="/base/login_ps.php?mode=logout" class="btn btn-white">취소</a>
<input type="submit" value="확인" class="btn btn-black"/>
</div>
</div>
</div>
</form>
 
<script>
(() => {
const form = document.getElementById('frmDuplicateLogin');
if (!form) { return; }
 
form.addEventListener('submit', (e) => {
e.preventDefault();
 
const checked = form.querySelector('input[name="keepExisting"]:checked');
const keepExisting = checked?.value ?? 'n';
const standalone = keepExisting !== 'y';
 
fetch('/share/duplicate_login_ps.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
body: JSON.stringify({ standalone: standalone }),
}).then((response) => {
if (!response.ok) {
throw new Error('request failed');
}
location.reload();
}).catch(() => {
alert('요청하신 작업을 완료하지 못했습니다.\n잠시 후 다시 시도해 주세요.');
});
});
})();
</script>
 
Component/Admin/AdminLogDAO.php+6
변경 전AdminLogDAO.php
 
const LOGIN = 'login';
const LOGOUT = 'logout';
 
 
const ALLOWED_MODES = ['authEmail', 'authSms', 'register', 'modify', 'delete', 'setManagePermission', 'getManagerListLayout'];
 
$page = '주문 내역 삭제';
}
 
 
 
 
 
 
$logType = $pageData['type'] ?? AdminLogDAO::LOG_TYPE_P2Y;
// 개인정보접속기록 다운로드로그 5년보관
if ($page == 'adminLogExcel') {
 
변경 후AdminLogDAO.php
 
const LOGIN = 'login';
const LOGOUT = 'logout';
const LOGOUT_SESSION_END = 'logoutSessionEnd';
 
const ALLOWED_MODES = ['authEmail', 'authSms', 'register', 'modify', 'delete', 'setManagePermission', 'getManagerListLayout'];
 
$page = '주문 내역 삭제';
}
 
if ($mode === self::LOGOUT_SESSION_END) {
$page = 'adminInfo';
$action = '로그아웃(세션 종료)';
}
 
$logType = $pageData['type'] ?? AdminLogDAO::LOG_TYPE_P2Y;
// 개인정보접속기록 다운로드로그 5년보관
if ($page == 'adminLogExcel') {
 
Component/Member/ManagerCs.php+9
변경 전ManagerCs.php
use Framework\Debug\Exception\DatabaseException;
use Framework\Security\Digester;
use Framework\Security\Otp;
 
use Framework\Utility\ComponentUtils;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\StringUtils;
use Framework\Utility\GodoUtils;
 
 
/**
* Class ManagerCs
* @package Bundle\Component\Member
$request = \App::getInstance('request');
$session = \App::getInstance('session');
$logger = \App::getInstance('logger');
 
 
 
 
 
 
$this->db->query_reset();
$data['lastLoginIp'] = $request->getRemoteAddress();
$data['sessionLimitTime'] = $sessionLimitTime;
$session->del(self::SESSION_TEMP_MANAGER);
$session->set(self::SESSION_MANAGER_LOGIN, $data);
 
}
 
/**
 
변경 후ManagerCs.php
use Framework\Debug\Exception\DatabaseException;
use Framework\Security\Digester;
use Framework\Security\Otp;
use Framework\SimpleCache\SimpleCache;
use Framework\Utility\ComponentUtils;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\StringUtils;
use Framework\Utility\GodoUtils;
use Origin\Service\Storage\ManagerDuplicateLoginService;
 
/**
* Class ManagerCs
* @package Bundle\Component\Member
$request = \App::getInstance('request');
$session = \App::getInstance('session');
$logger = \App::getInstance('logger');
$duplicateLoginService = new ManagerDuplicateLoginService(
\App::getInstance('application'),
\App::getInstance('session'),
SimpleCache::init(SimpleCache::REDIS)
);
 
$this->db->query_reset();
$data['lastLoginIp'] = $request->getRemoteAddress();
$data['sessionLimitTime'] = $sessionLimitTime;
$session->del(self::SESSION_TEMP_MANAGER);
$session->set(self::SESSION_MANAGER_LOGIN, $data);
$duplicateLoginService->addCurrentLoginToList($data['sno']);
}
 
/**
 
Component/Member/Manager.php+74-3
변경 전Manager.php
 
namespace Bundle\Component\Member;
 
 
use Component\Admin\AdminMenu;
use Component\Database\DBTableField;
use Component\Member\Exception\LoginException;
use Framework\Debug\Exception\AlertRedirectException;
use Framework\Security\Digester;
use Framework\Security\Otp;
 
use Framework\Utility\ComponentUtils;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\GodoUtils;
use Framework\Utility\StringUtils;
use Origin\Service\Member\ManagerLogoutService;
 
use Password;
use Request;
use Session;
*/
private $_storage;
 
 
 
 
 
 
 
/**
* 생성자
*/
$this->smsAutoReceiveKind[$newKey] = $val;
}
unset($tmp, $newKey);
 
 
 
 
 
 
}
 
/**
$this->setManagerOldMenuAccessChange(); // 운영자 접속 권한 - 세부 권한으로 마이그레이션
$this->setManagerFunctionAuthChange(); // 운영자 기능 권한 - 기능 권한으로 마이그레이션
$this->setManagerScmFunctionAuthChange(); // 공급사 기능 권한 - 기능 권한으로 마이그레이션
 
 
// 관리자 로그인 성공 로그
$request->request()->set('adminLoginFl', true);
 
$adminMenuFlagService->updateAdminMenuFlagsInSession();
}
 
 
 
 
 
 
 
 
 
 
 
/**
* setManagerOldMenuAccessChange
* 운영자 세부 접속 권한 마이그레이션
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 관리자 로그아웃
 
*/
public function managerLogout()
{
$cookie = \App::getInstance('cookie');
 
// 관리자 로그아웃 로그 작성
 
$adminLog = \App::load('Component\\Admin\\AdminLogDAO');
$adminLog->setAdminLog($adminLog::LOGOUT);
 
// 세션을 없애기전에 세션 쿠키를 지웁니다.
if ($cookie->has($session->getName())) {
// 관리자 통합회원 로그아웃
/** @var ManagerLogoutService $managerLogoutService */
$managerLogoutService = \App::getInstance(ManagerLogoutService::class);
$managerLogoutService->logout();
 
 
// 관리자만 로그아웃하는 경우 세션 클리어
if (MemberUtil::isLogin()) {
}
 
if ($result > 0 && $type != 'reset') {
Manager::managerLogout();
}
}
 
unset($arrBind);
return gd_htmlspecialchars_stripslashes($data);
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후Manager.php
 
namespace Bundle\Component\Member;
 
use Component\Admin\AdminLogDAO;
use Component\Admin\AdminMenu;
use Component\Database\DBTableField;
use Component\Member\Exception\LoginException;
use Framework\Debug\Exception\AlertRedirectException;
use Framework\Security\Digester;
use Framework\Security\Otp;
use Framework\SimpleCache\SimpleCache;
use Framework\Utility\ComponentUtils;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\GodoUtils;
use Framework\Utility\StringUtils;
use Origin\Service\Member\ManagerLogoutService;
use Origin\Service\Storage\ManagerDuplicateLoginService;
use Password;
use Request;
use Session;
*/
private $_storage;
 
/** @var ManagerDuplicateLoginService */
private $managerLoginStorage;
 
/** @var bool 중복 로그인으로 인한 (강제) 로그아웃 여부 */
private $shouldDuplicateLogout = false;
 
/**
* 생성자
*/
$this->smsAutoReceiveKind[$newKey] = $val;
}
unset($tmp, $newKey);
 
$this->managerLoginStorage = new ManagerDuplicateLoginService(
\App::getInstance('application'),
\App::getInstance('session'),
SimpleCache::init(SimpleCache::REDIS)
);
}
 
/**
$this->setManagerOldMenuAccessChange(); // 운영자 접속 권한 - 세부 권한으로 마이그레이션
$this->setManagerFunctionAuthChange(); // 운영자 기능 권한 - 기능 권한으로 마이그레이션
$this->setManagerScmFunctionAuthChange(); // 공급사 기능 권한 - 기능 권한으로 마이그레이션
$this->setDuplicateLoginList($data['sno']); // 중복 로그인 감지용 로그인 세션 목록에 현재 세션 등록
 
// 관리자 로그인 성공 로그
$request->request()->set('adminLoginFl', true);
 
$adminMenuFlagService->updateAdminMenuFlagsInSession();
}
 
/**
* 동시 로그인 정보를 Redis 에 등록한다.
*
* @param int $sno 관리자 sno
*/
public function setDuplicateLoginList(int $sno)
{
$this->managerLoginStorage->addCurrentLoginToList($sno);
}
 
/**
* setManagerOldMenuAccessChange
* 운영자 세부 접속 권한 마이그레이션
}
}
 
/**
* 중복 로그인으로 인한 (강제) 로그아웃 여부 주입.
* managerLogout() 을 무인자로 유지하기 위해 파라미터 대신 setter 로 받는다.
*
* @param bool $shouldDuplicateLogout true 면 es_adminLog 에 세션 종료 action 으로 기록
* @return void
*/
public function setShouldDuplicateLogout(bool $shouldDuplicateLogout)
{
$this->shouldDuplicateLogout = $shouldDuplicateLogout;
}
 
/**
* 관리자 로그아웃
* 중복 로그인 여부는 setShouldDuplicateLogout() 로 주입한다.
*/
public function managerLogout()
{
$cookie = \App::getInstance('cookie');
 
// 관리자 로그아웃 로그 작성
/** @var AdminLogDAO $adminLog */
$adminLog = \App::load('Component\\Admin\\AdminLogDAO');
$adminLog->setAdminLog($this->shouldDuplicateLogout ? $adminLog::LOGOUT_SESSION_END : $adminLog::LOGOUT);
 
// 세션을 없애기전에 세션 쿠키를 지웁니다.
if ($cookie->has($session->getName())) {
// 관리자 통합회원 로그아웃
/** @var ManagerLogoutService $managerLogoutService */
$managerLogoutService = \App::getInstance(ManagerLogoutService::class);
$managerLogoutService->logout($this->shouldDuplicateLogout);
$this->managerLoginStorage->deleteCurrentLoginFromList();
 
// 관리자만 로그아웃하는 경우 세션 클리어
if (MemberUtil::isLogin()) {
}
 
if ($result > 0 && $type != 'reset') {
$this->managerLogout();
}
}
 
unset($arrBind);
return gd_htmlspecialchars_stripslashes($data);
}
 
/**
* @return bool
*/
public function isCurrentLoginRegistered(): bool
{
return $this->managerLoginStorage->isCurrentLoginRegistered();
}
 
/**
* @return bool
*/
public function isDuplicateLogin(): bool
{
return $this->managerLoginStorage->isDuplicateLatestLogin();
}
 
/**
* @return string|null
*/
public function getLastLoginIp()
{
return $this->managerLoginStorage->getPreviousLoginIp();
}
 
public function allLogoutWithoutMe()
{
$this->managerLoginStorage->deleteLoginAllWithoutMe();
}
}
 
Controller/Admin/Base/IndexController.php+21
변경 전IndexController.php
 
use Component\Member\Manager;
use Core\Base\View\Alert;
 
use Framework\Utility\StringUtils;
use Origin\Service\License\LicenseRenewService;
 
$mainSettingPolicy = \App::load('Component\\Policy\\MainSettingPolicy');
$sessionByManager = $session->get(Manager::SESSION_MANAGER_LOGIN);
 
 
 
// 메인 통계의 권한 체크
$adminMenu = \App::load('Component\\Admin\\AdminMenu');
if ($this->getData('managerPermissionMethodExist') === true && $this->getData('adminMenuPermissionMethodExist') === true) {
 
$this->getView()->setPageName('base/index.php');
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후IndexController.php
 
use Component\Member\Manager;
use Core\Base\View\Alert;
use Framework\Http\Session\SessionManager;
use Framework\Utility\StringUtils;
use Origin\Service\License\LicenseRenewService;
 
$mainSettingPolicy = \App::load('Component\\Policy\\MainSettingPolicy');
$sessionByManager = $session->get(Manager::SESSION_MANAGER_LOGIN);
 
$this->checkDuplicateLogin($session);
 
// 메인 통계의 권한 체크
$adminMenu = \App::load('Component\\Admin\\AdminMenu');
if ($this->getData('managerPermissionMethodExist') === true && $this->getData('adminMenuPermissionMethodExist') === true) {
 
$this->getView()->setPageName('base/index.php');
}
 
public function checkDuplicateLogin(SessionManager $session)
{
$manager = \App::getInstance(\Manager::class);
$request = \App::getInstance('request');
 
if (
$manager->isDuplicateLogin() &&
!$session->get('manager.allowDuplicateLogin')
) {
$this->getView()->setDefine('layout', 'layout_blank_noiframe.php');
$this->getView()->setPageName('base/duplicate_login.php');
$this->getView()->setData('lastLoginIp',$manager->getLastLoginIp());
$this->getView()->setData('currentLoginIp', $request->getRemoteAddress());
echo $this->getView()->render();
exit;
}
}
}