'HS256', 'typ' => 'JWT']; $segments = []; $segments[] = base64url_encode(json_encode($header)); $segments[] = base64url_encode(json_encode($payload)); $signing_input = implode('.', $segments); $signature = hash_hmac('sha256', $signing_input, $secret, true); $segments[] = base64url_encode($signature); return implode('.', $segments); } /* ========================= cURL JSON 요청 ========================= */ function curl_json($url, $method = 'GET', $headers = [], $body = null) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => $body, CURLOPT_TIMEOUT => 10 ]); $response = curl_exec($ch); $err = curl_error($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($err) { throw new Exception($err); } return [$code, $response]; } try { /* ========================= 1️⃣ 매직링크 발급 요청 ========================= */ if ($mode === 'request') { $rawPhone = $input['phone'] ?? ''; $phone = preg_replace('/[^0-9]/', '', $rawPhone); if (!$phone) { echo json_encode([ 'status' => 'error', 'message' => '휴대폰 번호를 입력하세요.' ]); exit; } // JWT payload (3분 유효) $payload = [ 'system' => $SYSTEM, 'iat' => time(), 'exp' => time() + 180 ]; $jwt = create_jwt($payload, $SECRET_KEY); [$code, $res] = curl_json( $AUTH_SERVER . '/auth/sentinel', 'POST', [ 'Authorization: Bearer ' . $jwt, 'Content-Type: application/json' ], json_encode([ 'phoneNumber' => $phone ]) ); $result = json_decode($res, true); if (empty($result['token'])) { throw new Exception('Sentinel token 발급 실패'); } // ✅ token 반드시 저장 $_SESSION['sms_login'] = [ 'phone' => $phone, 'token' => $result['token'] ]; echo json_encode([ 'status' => 'success' ]); exit; } /* ========================= 2️⃣ 매직링크 상태 확인 → 자동 로그인 ========================= */ if ($mode === 'status') { if ( empty($_SESSION['sms_login']['token']) || empty($_SESSION['sms_login']['phone']) ) { echo json_encode(['status' => 'pending']); exit; } $token = $_SESSION['sms_login']['token']; $phone = $_SESSION['sms_login']['phone']; $payload = [ 'system' => $SYSTEM, 'iat' => time(), 'exp' => time() + 180 ]; $jwt = create_jwt($payload, $SECRET_KEY); [$code, $res] = curl_json( $AUTH_SERVER . '/auth/status?token=' . $token, 'GET', [ 'Authorization: Bearer ' . $jwt ] ); $data = json_decode($res, true); // 🔴 인증 완료 시 로그인 처리 if (!empty($data['loggedIn'])) { $stmt = $pdo->prepare(" SELECT member_id, user_id, user_nm, auth_bc FROM kngil.users WHERE REPLACE(tel_no, '-', '') = :phone AND use_yn = 'Y' LIMIT 1 "); $stmt->execute([':phone' => $phone]); $user = $stmt->fetch(PDO::FETCH_ASSOC); if (!$user) { throw new Exception('해당 번호로 등록된 사용자 없음'); } // ✅ 기존 PW 로그인과 동일 $_SESSION['login'] = [ 'member_id' => $user['member_id'], 'user_id' => $user['user_id'], 'user_nm' => $user['user_nm'], 'auth_bc' => $user['auth_bc'] ]; unset($_SESSION['sms_login']); echo json_encode([ 'status' => 'success', 'message' => '자동 로그인 완료' ], JSON_UNESCAPED_UNICODE); exit; } echo json_encode(['status' => 'pending']); exit; } } catch (Exception $e) { echo json_encode([ 'error' => true, 'message' => $e->getMessage() ], JSON_UNESCAPED_UNICODE); }