Normalize admin routes and docker config
This commit is contained in:
@@ -1,12 +1,17 @@
|
||||
<?php
|
||||
// /kngil/auth/oidc-callback.php
|
||||
session_start();
|
||||
ini_set('log_errors', '1');
|
||||
ini_set('error_log', '/proc/self/fd/2');
|
||||
require_once dirname(__DIR__) . '/vendor/autoload.php';
|
||||
require_once dirname(__DIR__) . '/bbs/db_conn.php';
|
||||
$config = require_once dirname(__DIR__) . '/bbs/oidc_config.php';
|
||||
|
||||
use Jumbojett\OpenIDConnectClient;
|
||||
|
||||
$usersTable = 'kngil.users';
|
||||
$membersTable = 'kngil.members';
|
||||
|
||||
$oidc = new OpenIDConnectClient(
|
||||
$config['issuer'],
|
||||
$config['client_id'],
|
||||
@@ -16,16 +21,124 @@ $oidc = new OpenIDConnectClient(
|
||||
$oidc->setRedirectURL($config['redirect_url']);
|
||||
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT to_regclass('kngil.users') AS reg");
|
||||
$reg = $stmt ? $stmt->fetchColumn() : null;
|
||||
if (!$reg) {
|
||||
$stmt = $pdo->query("SELECT to_regclass('public.users') AS reg");
|
||||
$reg = $stmt ? $stmt->fetchColumn() : null;
|
||||
if ($reg) {
|
||||
$usersTable = 'public.users';
|
||||
$membersTable = 'public.members';
|
||||
} else {
|
||||
throw new Exception(
|
||||
"사용자 테이블을 찾을 수 없습니다. DB 초기화가 필요합니다. "
|
||||
. "docker compose down -v 후 다시 실행하거나, "
|
||||
. "DB_NAME/DB_USER/DB_PASS 설정을 확인하세요."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$memberReg = $pdo->query("SELECT to_regclass('{$membersTable}') AS reg");
|
||||
$memberReg = $memberReg ? $memberReg->fetchColumn() : null;
|
||||
if (!$memberReg) {
|
||||
$altMembersTable = $membersTable === 'kngil.members' ? 'public.members' : 'kngil.members';
|
||||
$memberReg = $pdo->query("SELECT to_regclass('{$altMembersTable}') AS reg");
|
||||
$memberReg = $memberReg ? $memberReg->fetchColumn() : null;
|
||||
if ($memberReg) {
|
||||
$membersTable = $altMembersTable;
|
||||
} else {
|
||||
throw new Exception("회원 테이블을 찾을 수 없습니다. DB 초기화가 필요합니다.");
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->exec("ALTER TABLE {$usersTable} ADD COLUMN IF NOT EXISTS oidc_sub VARCHAR(255) UNIQUE");
|
||||
|
||||
if (!$oidc->authenticate()) {
|
||||
throw new Exception("Authentication failed");
|
||||
}
|
||||
|
||||
$userInfo = $oidc->requestUserInfo();
|
||||
$idToken = $oidc->getIdToken();
|
||||
$accessToken = $oidc->getAccessToken();
|
||||
$jwtClaims = [];
|
||||
if (!empty($idToken)) {
|
||||
$parts = explode('.', $idToken);
|
||||
if (count($parts) >= 2) {
|
||||
$payload = strtr($parts[1], '-_', '+/');
|
||||
$padding = 4 - (strlen($payload) % 4);
|
||||
if ($padding < 4) {
|
||||
$payload .= str_repeat('=', $padding);
|
||||
}
|
||||
$decoded = json_decode(base64_decode($payload), true);
|
||||
if (is_array($decoded)) {
|
||||
$jwtClaims = $decoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 디버그용: ID 토큰 확보 여부 로그 출력 (파일)
|
||||
$logDir = dirname(__DIR__) . '/log';
|
||||
if (!is_dir($logDir)) {
|
||||
@mkdir($logDir, 0775, true);
|
||||
}
|
||||
$logPath = $logDir . '/oidc_debug.log';
|
||||
if (!is_writable($logDir)) {
|
||||
$logPath = '/tmp/oidc_debug.log';
|
||||
error_log('[OIDC_DEBUG] log_dir_not_writable, fallback=/tmp/oidc_debug.log');
|
||||
}
|
||||
$tokenInfo = empty($idToken) ? 'MISSING' : ('PRESENT len=' . strlen($idToken));
|
||||
$claimKeys = empty($jwtClaims) ? 'none' : implode(',', array_keys($jwtClaims));
|
||||
$logLine = sprintf(
|
||||
"[%s] host=%s uri=%s sid=%s id_token=%s claims=%s\n",
|
||||
date('c'),
|
||||
$_SERVER['HTTP_HOST'] ?? '-',
|
||||
$_SERVER['REQUEST_URI'] ?? '-',
|
||||
session_id(),
|
||||
$tokenInfo,
|
||||
$claimKeys
|
||||
);
|
||||
$writeOk = @file_put_contents($logPath, $logLine, FILE_APPEND);
|
||||
if ($writeOk === false) {
|
||||
error_log('[OIDC_DEBUG] log_write_failed path=' . $logPath);
|
||||
}
|
||||
// 디버그용: userInfo/claims 전체 덤프 (토큰 제외)
|
||||
$dump = [
|
||||
'userInfo' => $userInfo,
|
||||
'jwtClaims' => $jwtClaims
|
||||
];
|
||||
$dumpLine = sprintf(
|
||||
"[%s] oidc_dump=%s\n",
|
||||
date('c'),
|
||||
json_encode($dump, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
||||
);
|
||||
$dumpOk = @file_put_contents($logPath, $dumpLine, FILE_APPEND);
|
||||
if ($dumpOk === false) {
|
||||
error_log('[OIDC_DEBUG] dump_write_failed path=' . $logPath);
|
||||
}
|
||||
// 도커 로그로도 출력
|
||||
error_log('[OIDC_DEBUG] ' . $dumpLine);
|
||||
// $userInfo 에 포함된 데이터 예시: sub, email, name, preferred_username 등
|
||||
|
||||
$email = $userInfo->email ?? null;
|
||||
$sub = $userInfo->sub ?? null; // IDP 고유 식별자
|
||||
$name = $userInfo->name ?? ($userInfo->preferred_username ?? 'Unknown');
|
||||
$preferred = $userInfo->preferred_username ?? null;
|
||||
$name = $userInfo->name ?? null;
|
||||
if (!$email && isset($jwtClaims['email'])) {
|
||||
$email = $jwtClaims['email'];
|
||||
}
|
||||
if (!$name && isset($jwtClaims['name'])) {
|
||||
$name = $jwtClaims['name'];
|
||||
}
|
||||
if (!$name && $preferred) {
|
||||
$name = $preferred;
|
||||
}
|
||||
if (!$name && $email) {
|
||||
$name = $email;
|
||||
}
|
||||
if (!$name && $sub) {
|
||||
$seed = strtolower(preg_replace('/[^a-z0-9]/', '', (string)$sub));
|
||||
$name = 'oidc_' . substr($seed, 0, 10);
|
||||
}
|
||||
|
||||
if (!$email && !$sub) {
|
||||
throw new Exception("IDP provided insufficient user information.");
|
||||
@@ -33,7 +146,7 @@ try {
|
||||
|
||||
// 1. 사용자 매핑 (sub 또는 email 기준)
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT * FROM kngil.users
|
||||
SELECT * FROM {$usersTable}
|
||||
WHERE (oidc_sub = :sub OR LOWER(email) = LOWER(:email))
|
||||
AND use_yn = 'Y'
|
||||
LIMIT 1
|
||||
@@ -42,14 +155,99 @@ try {
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$user) {
|
||||
// [정책 선택] 새 사용자 자동 생성 또는 로그인 거부
|
||||
// 여기서는 예시로 로그인 거부 처리
|
||||
throw new Exception("등록되지 않은 사용자입니다. 관리자에게 문의하세요. (IDP: $email)");
|
||||
$defaultMemberId = getenv('OIDC_DEFAULT_MEMBER_ID') ?: '';
|
||||
if ($defaultMemberId !== '') {
|
||||
$checkMember = $pdo->prepare("SELECT 1 FROM {$membersTable} WHERE member_id = :member_id LIMIT 1");
|
||||
$checkMember->execute([':member_id' => $defaultMemberId]);
|
||||
if (!$checkMember->fetchColumn()) {
|
||||
throw new Exception("OIDC_DEFAULT_MEMBER_ID가 members에 존재하지 않습니다: {$defaultMemberId}");
|
||||
}
|
||||
} else {
|
||||
$memberStmt = $pdo->query("SELECT member_id FROM {$membersTable} ORDER BY member_id ASC LIMIT 1");
|
||||
$defaultMemberId = $memberStmt ? $memberStmt->fetchColumn() : '';
|
||||
if (!$defaultMemberId) {
|
||||
throw new Exception("기본 member_id를 찾을 수 없습니다. OIDC_DEFAULT_MEMBER_ID를 설정하세요.");
|
||||
}
|
||||
}
|
||||
|
||||
$defaultAuth = getenv('OIDC_DEFAULT_AUTH_BC') ?: 'BS100500';
|
||||
|
||||
$baseId = $userInfo->preferred_username ?? ($email ? explode('@', $email)[0] : '');
|
||||
$baseId = strtolower(preg_replace('/[^a-z0-9]/', '', $baseId));
|
||||
if ($baseId === '') {
|
||||
$seed = strtolower(preg_replace('/[^a-z0-9]/', '', (string)($sub ?? 'oidc')));
|
||||
$baseId = 'oidc' . substr($seed, 0, 10);
|
||||
}
|
||||
$baseId = substr($baseId, 0, 16);
|
||||
$userId = $baseId;
|
||||
|
||||
$existsStmt = $pdo->prepare("SELECT 1 FROM {$usersTable} WHERE LOWER(user_id) = LOWER(:user_id) LIMIT 1");
|
||||
$suffix = 1;
|
||||
while (true) {
|
||||
$existsStmt->execute([':user_id' => $userId]);
|
||||
if (!$existsStmt->fetchColumn()) {
|
||||
break;
|
||||
}
|
||||
$tail = sprintf('%02d', $suffix);
|
||||
$userId = substr($baseId, 0, 20 - strlen($tail)) . $tail;
|
||||
$suffix++;
|
||||
if ($suffix > 99) {
|
||||
$userId = 'oidc' . bin2hex(random_bytes(4));
|
||||
$userId = substr($userId, 0, 20);
|
||||
}
|
||||
}
|
||||
|
||||
$userNm = $name ?: ($email ?: $userId);
|
||||
$rawPhone = $userInfo->phone_number ?? '';
|
||||
$digits = preg_replace('/\D/', '', $rawPhone);
|
||||
if (strlen($digits) === 11) {
|
||||
$telNo = substr($digits, 0, 3) . '-' . substr($digits, 3, 4) . '-' . substr($digits, 7, 4);
|
||||
} elseif (strlen($digits) === 10) {
|
||||
$telNo = substr($digits, 0, 3) . '-' . substr($digits, 3, 3) . '-' . substr($digits, 6, 4);
|
||||
} else {
|
||||
$telNo = '000-0000-0000';
|
||||
}
|
||||
|
||||
$insert = $pdo->prepare("
|
||||
INSERT INTO {$usersTable} (
|
||||
member_id, user_id, user_pw, user_nm,
|
||||
dept_nm, posit_nm, tel_no, email,
|
||||
auth_bc, use_yn, rmks,
|
||||
cid, cdt, mid, mdt, oidc_sub
|
||||
) VALUES (
|
||||
:member_id, :user_id, NULL, :user_nm,
|
||||
:dept_nm, :posit_nm, :tel_no, :email,
|
||||
:auth_bc, 'Y', :rmks,
|
||||
:cid, CURRENT_TIMESTAMP, :mid, CURRENT_TIMESTAMP, :oidc_sub
|
||||
)
|
||||
");
|
||||
$insert->execute([
|
||||
':member_id' => $defaultMemberId,
|
||||
':user_id' => $userId,
|
||||
':user_nm' => $userNm,
|
||||
':dept_nm' => $userInfo->department ?? null,
|
||||
':posit_nm' => $userInfo->title ?? null,
|
||||
':tel_no' => $telNo,
|
||||
':email' => $email,
|
||||
':auth_bc' => $defaultAuth,
|
||||
':rmks' => 'OIDC auto-registered',
|
||||
':cid' => $userId,
|
||||
':mid' => $userId,
|
||||
':oidc_sub' => $sub
|
||||
]);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT * FROM {$usersTable}
|
||||
WHERE LOWER(user_id) = LOWER(:user_id)
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':user_id' => $userId]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// 2. oidc_sub 업데이트 (최초 연동 시)
|
||||
if (empty($user['oidc_sub']) && $sub) {
|
||||
$upd = $pdo->prepare("UPDATE kngil.users SET oidc_sub = :sub WHERE user_id = :id");
|
||||
$upd = $pdo->prepare("UPDATE {$usersTable} SET oidc_sub = :sub WHERE user_id = :id");
|
||||
$upd->execute([':sub' => $sub, ':id' => $user['user_id']]);
|
||||
}
|
||||
|
||||
@@ -63,20 +261,42 @@ try {
|
||||
'dept_nm' => $user['dept_nm'] ?? null,
|
||||
'tel_no' => $user['tel_no'] ?? null,
|
||||
'email' => $user['email'] ?? null,
|
||||
'idp_name' => $name ?: null,
|
||||
'idp_email' => $email ?? null,
|
||||
'idp_id_token' => $idToken ?? null,
|
||||
'idp_access_token' => $accessToken ?? null,
|
||||
'idp_claims' => $jwtClaims ?? null,
|
||||
'oidc_mode' => true // OIDC 로그인을 나타내는 플래그
|
||||
];
|
||||
|
||||
// 로그인 완료 후 부모 창에 알리고 종료
|
||||
session_write_close();
|
||||
|
||||
// 로그인 완료 후 부모 창에 알리고 종료 (팝업이 아닐 경우 메인으로 이동)
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<script>
|
||||
if (window.opener) {
|
||||
window.opener.postMessage({ type: 'OIDC_LOGIN_SUCCESS' }, window.location.origin);
|
||||
}
|
||||
window.close();
|
||||
(function () {
|
||||
const target = '/kngil/skin/index.php';
|
||||
if (window.opener && !window.opener.closed) {
|
||||
try {
|
||||
window.opener.postMessage({ type: 'OIDC_LOGIN_SUCCESS' }, window.location.origin);
|
||||
} catch (e) {
|
||||
// 팝업 차단/보안 정책으로 실패할 수 있어 무시합니다.
|
||||
}
|
||||
window.close();
|
||||
setTimeout(function () {
|
||||
window.location.href = target;
|
||||
}, 300);
|
||||
} else {
|
||||
window.location.href = target;
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<noscript>
|
||||
<a href="/kngil/skin/index.php">메인으로 이동</a>
|
||||
</noscript>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
|
||||
Reference in New Issue
Block a user