第12回 パーサー活用と認証機能

Ola-UI構築チュートリアル

第12回 パーサー活用で、利用できる人だけ利用可能なOla-UIに!― 認証機能で組織内限定公開を実現

はじめに

前回(第11回)では、Ollamaの進化に連動したOla-UIの無限の可能性―対応LLM無限、自前トレーニング、マルチモーダル対応―について解説しました。技術的な拡張性と将来展望をお伝えしました。

第12回となる今回は、Ola-UIに認証機能を追加し、組織内限定公開を実現する方法に焦点を当てます。

今回のテーマ: 「パーサー(Parser)活用で、利用できる人だけ利用可能なOla-UIに!」

これまでのOla-UIは、基本的に誰でもアクセス可能な構成でした。しかし、実際の業務運用では、社内ナレッジや機密情報を含むシステムを、無制限に公開することはできません。

そこで登場するのが、PHPベースの認証パーサーです。

このパーサーが実現すること: 1. ユーザー認証(ログイン)の実装 2. 認証なしではアクセス不可 3. 既存のOla-UIファイルを変更せずに保護 4. セッション管理で快適な操作

たった2つのPHPファイル(login.php + parser.php)を追加するだけで、Ola-UIが組織内限定の安全なシステムに変わります。

その仕組みと導入方法を、実践的に解説していきます。


1. なぜ認証機能が必要なのか?

1-1. 無認証公開のリスク

これまでの構成では、Ola-UIのURLを知っている人なら誰でもアクセス可能でした。

【第6回のDDNS設定後】
URL: https://your-ddns-domain.com:8080/

問題点:
- インターネット上の誰でもアクセス可能
- 社内ナレッジが外部に漏洩するリスク
- 悪意ある利用者による攻撃の可能性

リスクシナリオ

【シナリオ1: 機密情報の漏洩】
ナレッジセットに「顧客リスト」「製品原価」「契約条件」など
の機密情報が含まれている
→ 外部からアクセスされると情報漏洩

【シナリオ2: 無制限な利用】
社外の第三者が大量のリクエストを送信
→ サーバー負荷増大、正規ユーザーが使えなくなる

【シナリオ3: 誤情報の拡散】
社内専用の情報(開発中製品、未発表施策など)が
偶然外部に公開されてしまう
→ ビジネス上の損失

1-2. 認証機能で実現すること

パーサーによる認証機能を導入すると、これらのリスクを大幅に低減できます。

【認証後の構成】
URL: https://your-ddns-domain.com:8080/parser.php

アクセスフロー:
1. ユーザーがアクセス
2. ログイン画面が表示される
3. ID/パスワードを入力
4. 認証成功 → Ola-UIの画面へ
5. 認証失敗 → ログイン画面に戻る

効果:
- 社内メンバーのみアクセス可能
- 機密情報の保護
- 利用者の特定が可能(ログ機能追加可能)

1-3. 組織における認証の重要性

コンプライアンス対応

【ISO 27001(情報セキュリティ)】
- アクセス制御: 誰がアクセスできるかを管理
- 認証・認可: 正当なユーザーのみ利用可能

【個人情報保護法】
- 個人情報を含むシステムは厳格な管理が必要
- 認証なしの公開は違反のリスク

【企業内規定】
- 機密情報への不正アクセス防止
- 監査証跡の確保

実用上のメリット

【メリット1: 安心して情報を搭載できる】
認証があれば、社内の詳細な情報もナレッジに追加可能

【メリット2: 部門別アカウント管理】
営業部、法務部、技術部など、部門ごとにアカウントを発行
→ 利用状況の把握が可能

【メリット3: 監査対応】
「誰が、いつ、何にアクセスしたか」のログを記録可能
→ コンプライアンス監査に対応

2. パーサーの仕組みを理解する

2-1. パーサーとは?

Parser(パーサー) = 解析・中継プログラム

Ola-UIの場合、パーサーは「認証のゲート役」として機能します。

【通常のアクセス(認証なし)】
ブラウザ → index_t.html → chat_api_t.php → Ollama

【パーサー経由のアクセス(認証あり)】
ブラウザ → parser.php(認証チェック)
         ↓ (認証OK)
         → index_t.html → chat_api_t.php → Ollama
         ↓ (認証NG)
         → login.php(ログイン画面)

2-2. パーサーの構成要素

今回使用するパーサーシステムは、2つのPHPファイルで構成されます。

ファイル構成

Ola-UIルートディレクトリ/
├── parser.php          # メインパーサー(認証チェック+ページ表示)
├── login.php           # ログイン画面
├── userdata/
│   └── users.csv       # ユーザー情報(自動生成)
└── ola-59/             # 既存のOla-UIファイル群
    ├── index_t.html
    ├── chat_api_t.php
    └── ...

各ファイルの役割

1. login.php(ログイン画面)

<?php
// 機能:
// - ログイン画面の表示
// - ユーザー名/パスワードの受け取り
// - users.csv との照合
// - 認証成功 → parser.php へリダイレクト
// - 認証失敗 → エラーメッセージ表示

// 初期設定:
// - テストユーザー: admin / 1234 (自動作成)
?>

2. parser.php(メインパーサー)

<?php
session_start();

// 機能1: 認証チェック
if (!isset($_SESSION['authenticated'])) {
    header('Location: login.php');
    exit();
}

// 機能2: 対象ファイルの読み込み
$target = $_GET['target'] ?? 'index_t.html';
include($target);

// 機能3: リンク書き換え
// すべてのリンクを parser.php?target=... 形式に変換
// → 認証状態を維持したままページ遷移
?>

3. users.csv(ユーザーデータ)

admin,1234
sales_user,pass123
legal_user,pass456
tech_support,pass789

2-3. 動作フロー詳細

初回アクセス時

1. ユーザーが https://your-domain.com/parser.php にアクセス

2. parser.php:
   - セッションチェック
   - $_SESSION['authenticated'] が未設定
   → login.php へリダイレクト

3. login.php:
   - ログイン画面を表示
   - ユーザー名/パスワードの入力フォーム

4. ユーザーが入力して送信

5. login.php:
   - POSTデータを受け取る
   - users.csv と照合
   - 一致 → $_SESSION['authenticated'] = true
         → parser.php?target=index_t.html へリダイレクト
   - 不一致 → エラーメッセージ表示

6. parser.php:
   - セッションチェック → OK
   - index_t.html を読み込み
   - リンクを parser.php?target=... に書き換え
   - ブラウザへ出力

7. ユーザーは Ola-UI のチャット画面を利用

2回目以降のアクセス

1. ユーザーが https://your-domain.com/parser.php にアクセス

2. parser.php:
   - セッションチェック
   - $_SESSION['authenticated'] = true (前回ログイン済み)
   → そのまま index_t.html を表示

3. ログイン画面をスキップして即座に利用可能

ログアウト

ブラウザを閉じる or セッションタイムアウト(デフォルト24分)
→ $_SESSION が破棄される
→ 次回アクセス時に再度ログインが必要

3. パーサーの導入手順(ステップバイステップ)

3-1. 前提条件

3-2. ステップ1: パーサーファイルの配置

parser.php の作成

# Ola-UIルートディレクトリに配置
cd /path/to/ola-ui
nano parser.php

parser.php の内容:

<?php
session_start();

// 1. 認証チェック
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
    header('Location: login.php');
    exit();
}

// 2. 設定
$base_folder_name = 'ola-59';  // 既存のOla-UIフォルダ名
$base_dir = __DIR__ . '/' . $base_folder_name . '/';
$base_url = $base_folder_name . '/';

// 3. 対象ファイルの取得
$file_param = $_GET['target'] ?? 'index_t.html';

// パラメータ分離処理(例: index_t.html?param=value)
$query_start = strpos($file_param, '?');
if ($query_start !== false) {
    $file = substr($file_param, 0, $query_start);
    $query_str = substr($file_param, $query_start + 1);
    parse_str($query_str, $params);
    $_GET = array_merge($_GET, $params);
} else {
    $file = $file_param;
}

// 4. セキュリティチェック(ディレクトリトラバーサル対策)
$target_path = realpath($base_dir . $file);
if ($target_path === false || 
    strpos($target_path, realpath($base_dir)) !== 0 || 
    !file_exists($target_path)) {
    echo "Error: File not found or access denied.";
    exit;
}

// 5. ファイルの実行とバッファリング
$original_dir = getcwd();
chdir(dirname($target_path));

ob_start();
try {
    include basename($target_path);
} catch (Exception $e) {
    echo "Execution Error: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
}
$content = ob_get_clean();

chdir($original_dir);

// 6. リンク書き換え処理
$my_script = basename($_SERVER['SCRIPT_NAME']);
$script_dir = dirname($_SERVER['SCRIPT_NAME']);
if ($script_dir === '/' || $script_dir === '\\') {
    $parserUrl = '/' . $my_script;
} else {
    $parserUrl = $script_dir . '/' . $my_script;
}

// 7. base タグの挿入
$base_tag = '<base href="' . htmlspecialchars($base_url, ENT_QUOTES, 'UTF-8') . '">' . "\n";

if (stripos($content, '<head>') !== false) {
    $content = preg_replace(
        '/<head([^>]*)>/i',
        '<head$1>' . $base_tag,
        $content,
        1
    );
} else {
    $content = $base_tag . $content;
}

// 8. リンクの書き換え(<a href="...">)
$content = preg_replace_callback(
    '/(<a\b[^>]*\bhref=["\'])([^"\']+)(["\'])/i',
    function ($matches) use ($parserUrl) {
        $prefix = $matches[1];
        $href   = $matches[2];
        $suffix = $matches[3];

        // 外部リンクやアンカーは変更しない
        if (preg_match('/^(https?:|\/\/|#|javascript:|mailto:)/i', $href)) {
            return $matches[0];
        }

        $newHref = $parserUrl . '?target=' . rawurlencode($href);
        return $prefix . htmlspecialchars($newHref, ENT_QUOTES, 'UTF-8') . $suffix;
    },
    $content
);

// 9. フォームアクションの書き換え
$content = preg_replace_callback(
    '/(<form\b[^>]*\baction=["\'])([^"\']+)(["\'])/i',
    function ($matches) use ($parserUrl) {
        $prefix = $matches[1];
        $action = $matches[2];
        $suffix = $matches[3];

        if (preg_match('/^(https?:|\/\/)/i', $action)) {
            return $matches[0];
        }

        $newAction = $parserUrl . '?target=' . rawurlencode($action);
        return $prefix . htmlspecialchars($newAction, ENT_QUOTES, 'UTF-8') . $suffix;
    },
    $content
);

// 10. JavaScript location.href の書き換え
$content = preg_replace_callback(
    '/location\.href\s*=\s*["\']([^"\']+)["\']/i',
    function ($matches) use ($parserUrl) {
        $url = $matches[1];

        if (preg_match('/^(https?:|\/\/|\/|#)/i', $url)) {
            return $matches[0];
        }

        $newUrl = $parserUrl . '?target=' . rawurlencode($url);
        return "location.href = '" . addslashes($newUrl) . "'";
    },
    $content
);

echo $content;
?>

login.php の作成

nano login.php

login.php の内容:

<?php
$userdata_dir = 'userdata';
$csv_file = $userdata_dir . '/users.csv';

// ディレクトリとCSVが存在しない場合、自動作成
if (!file_exists($userdata_dir)) {
    mkdir($userdata_dir, 0777, true);
}
if (!file_exists($csv_file)) {
    // テストユーザー: admin / 1234
    $fp = fopen($csv_file, 'w');
    fputcsv($fp, ['admin', '1234']);
    fclose($fp);
}

$error = '';

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $username = $_POST['username'] ?? '';
    $password = $_POST['password'] ?? '';

    // ユーザーデータ読み込み
    $users = [];
    if (file_exists($csv_file)) {
        if (($handle = fopen($csv_file, 'r')) !== FALSE) {
            while (($data = fgetcsv($handle)) !== FALSE) {
                if (isset($data[0]) && isset($data[1])) {
                    $users[$data[0]] = $data[1]; 
                }
            }
            fclose($handle);
        }
    }

    // 認証
    if (isset($users[$username]) && $users[$username] === $password) {
        // 認証成功
        session_start();
        $_SESSION['authenticated'] = true;
        $_SESSION['username'] = $username;  // ユーザー名も保存
        header('Location: parser.php?target=index_t.html');
        exit();
    } else {
        // 認証失敗
        $error = 'ユーザー名またはパスワードが正しくありません。<br>(テスト用初期設定: admin / 1234)';
    }
}
?>

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Ola-UI ログイン</title>
    <style>
        body { 
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        .login-container {
            background: white;
            padding: 40px;
            border-radius: 10px;
            box-shadow: 0 10px 25px rgba(0,0,0,0.2);
            width: 350px;
        }
        h1 {
            text-align: center;
            color: #333;
            margin-bottom: 30px;
            font-size: 24px;
        }
        .form-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            color: #555;
            font-weight: 500;
        }
        input[type="text"], input[type="password"] {
            width: 100%;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 5px;
            box-sizing: border-box;
            font-size: 14px;
        }
        input[type="text"]:focus, input[type="password"]:focus {
            outline: none;
            border-color: #667eea;
        }
        input[type="submit"] {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            font-weight: 600;
            transition: transform 0.2s;
        }
        input[type="submit"]:hover {
            transform: translateY(-2px);
        }
        .error {
            background: #fee;
            color: #c33;
            padding: 10px;
            border-radius: 5px;
            margin-bottom: 20px;
            text-align: center;
            font-size: 14px;
        }
        .logo {
            text-align: center;
            margin-bottom: 20px;
            font-size: 48px;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <div class="logo">🔒</div>
        <h1>Ola-UI ログイン</h1>
        <?php if ($error): ?>
            <div class="error"><?php echo $error; ?></div>
        <?php endif; ?>
        <form method="post">
            <div class="form-group">
                <label for="username">ユーザー名:</label>
                <input type="text" id="username" name="username" required autofocus>
            </div>
            <div class="form-group">
                <label for="password">パスワード:</label>
                <input type="password" id="password" name="password" required>
            </div>
            <input type="submit" value="ログイン">
        </form>
    </div>
</body>
</html>

3-3. ステップ2: ディレクトリ構造の確認

# 現在のディレクトリ構造を確認
ls -la

# 期待される構造
.
├── parser.php          # ★新規追加
├── login.php           # ★新規追加
├── userdata/           # ★自動作成される
│   └── users.csv
└── ola-59/             # 既存のフォルダ
    ├── index_t.html
    ├── chat_api_t.php
    ├── config.ini
    └── ...

3-4. ステップ3: 動作確認

テスト1: ログイン画面の表示

# PHPサーバーを起動(既に起動している場合は不要)
php -S localhost:8080

# ブラウザでアクセス
http://localhost:8080/parser.php

期待される動作: - ログイン画面が表示される - ユーザー名/パスワード入力フォームが表示される

テスト2: 認証成功

ユーザー名: admin
パスワード: 1234

送信 → Ola-UIのチャット画面が表示される

テスト3: 認証失敗

ユーザー名: test
パスワード: wrong

送信 → エラーメッセージが表示される
       「ユーザー名またはパスワードが正しくありません」

テスト4: セッション維持

1. ログイン成功後、別のページへ移動
   (例: ナレッジセット切り替え)

2. ページ遷移時もログイン状態が維持されることを確認

3. ブラウザを閉じて再度アクセス
   → 再度ログイン画面が表示される(正常)

4. ユーザー管理の実践

4-1. ユーザーの追加方法

手動追加(CSVファイル編集)

# users.csv を編集
nano userdata/users.csv

# ユーザーを追加(カンマ区切り)
admin,1234
sales_user,pass123
legal_user,pass456
tech_support,pass789
research_user,pass000

注意点: - パスワードは平文(プレーンテキスト)で保存される - 本番環境では、後述のハッシュ化対応を推奨

管理画面での追加(応用編)

add_user.php(管理者用)を作成:

<?php
session_start();

// 管理者権限チェック
if (!isset($_SESSION['authenticated']) || $_SESSION['username'] !== 'admin') {
    die('管理者権限が必要です');
}

$csv_file = 'userdata/users.csv';

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $new_username = $_POST['new_username'] ?? '';
    $new_password = $_POST['new_password'] ?? '';

    // 入力チェック
    if (empty($new_username) || empty($new_password)) {
        $message = 'エラー: ユーザー名とパスワードを入力してください';
    } else {
        // 重複チェック
        $users = [];
        if (file_exists($csv_file)) {
            $handle = fopen($csv_file, 'r');
            while (($data = fgetcsv($handle)) !== FALSE) {
                $users[] = $data;
            }
            fclose($handle);

            foreach ($users as $user) {
                if ($user[0] === $new_username) {
                    $message = 'エラー: このユーザー名は既に存在します';
                    break;
                }
            }
        }

        // 追加処理
        if (!isset($message)) {
            $fp = fopen($csv_file, 'a');
            fputcsv($fp, [$new_username, $new_password]);
            fclose($fp);
            $message = '成功: ユーザー「' . htmlspecialchars($new_username) . '」を追加しました';
        }
    }
}

// 現在のユーザー一覧を取得
$current_users = [];
if (file_exists($csv_file)) {
    $handle = fopen($csv_file, 'r');
    while (($data = fgetcsv($handle)) !== FALSE) {
        if (isset($data[0])) {
            $current_users[] = $data[0];
        }
    }
    fclose($handle);
}
?>

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ユーザー管理</title>
    <style>
        body { font-family: sans-serif; padding: 40px; background: #f5f5f5; }
        .container { max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; }
        h1 { color: #333; }
        .form-group { margin-bottom: 15px; }
        label { display: block; margin-bottom: 5px; font-weight: 500; }
        input[type="text"], input[type="password"] { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
        input[type="submit"] { padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 4px; cursor: pointer; }
        .message { padding: 10px; margin-bottom: 20px; border-radius: 4px; }
        .success { background: #d4edda; color: #155724; }
        .error { background: #f8d7da; color: #721c24; }
        .user-list { background: #f9f9f9; padding: 15px; border-radius: 4px; margin-top: 30px; }
        .user-list h2 { margin-top: 0; font-size: 18px; }
        .user-list ul { list-style: none; padding: 0; }
        .user-list li { padding: 5px 0; border-bottom: 1px solid #eee; }
    </style>
</head>
<body>
    <div class="container">
        <h1>ユーザー管理</h1>

        <?php if (isset($message)): ?>
            <div class="message <?php echo strpos($message, '成功') === 0 ? 'success' : 'error'; ?>">
                <?php echo $message; ?>
            </div>
        <?php endif; ?>

        <form method="post">
            <div class="form-group">
                <label for="new_username">新規ユーザー名:</label>
                <input type="text" id="new_username" name="new_username" required>
            </div>
            <div class="form-group">
                <label for="new_password">パスワード:</label>
                <input type="password" id="new_password" name="new_password" required>
            </div>
            <input type="submit" value="ユーザー追加">
        </form>

        <div class="user-list">
            <h2>現在のユーザー一覧(<?php echo count($current_users); ?>名)</h2>
            <ul>
                <?php foreach ($current_users as $user): ?>
                    <li>👤 <?php echo htmlspecialchars($user); ?></li>
                <?php endforeach; ?>
            </ul>
        </div>

        <p style="margin-top: 30px;">
            <a href="parser.php">← Ola-UIに戻る</a>
        </p>
    </div>
</body>
</html>

使い方:

# add_user.php を配置
# ブラウザでアクセス(adminでログイン後)
http://localhost:8080/add_user.php

# フォームに入力して「ユーザー追加」

4-2. 部門別アカウント管理

部門ごとのアカウント設計

【営業部門】
sales_manager  (営業マネージャー)
sales_user1    (営業担当1)
sales_user2    (営業担当2)

【法務部門】
legal_manager  (法務マネージャー)
legal_user1    (法務担当1)

【技術サポート部門】
support_manager (サポートマネージャー)
support_user1   (サポート担当1)
support_user2   (サポート担当2)

【管理者】
admin          (システム管理者)

パスワードポリシーの設定(推奨)

【ポリシー例】
- 最低8文字以上
- 英数字混在
- 定期的な変更(3ヶ月ごと)
- 推測されやすいパスワードの禁止
  (例: password, 12345678, company_name)

4-3. セキュリティ強化: パスワードハッシュ化

現状の問題:

users.csv:
admin,1234       # パスワードが平文で保存
sales_user,pass123  # 誰でも見えてしまう

改善版: password_hash() 使用

login_secure.php(ハッシュ化対応版):

<?php
// ... (前半は同じ)

// ユーザー認証(ハッシュ対応)
if (isset($users[$username])) {
    $stored_password = $users[$username];

    // ハッシュ化されているかチェック
    if (password_verify($password, $stored_password)) {
        // ハッシュ化パスワードで認証成功
        session_start();
        $_SESSION['authenticated'] = true;
        $_SESSION['username'] = $username;
        header('Location: parser.php?target=index_t.html');
        exit();
    } elseif ($stored_password === $password) {
        // 平文パスワード(旧形式)で認証成功
        // → ハッシュ化してCSVを更新
        $hashed = password_hash($password, PASSWORD_DEFAULT);

        // CSV全体を読み込み
        $all_users = [];
        $handle = fopen($csv_file, 'r');
        while (($data = fgetcsv($handle)) !== FALSE) {
            if ($data[0] === $username) {
                $data[1] = $hashed;  // ハッシュ化したパスワードに更新
            }
            $all_users[] = $data;
        }
        fclose($handle);

        // CSV全体を書き込み直す
        $fp = fopen($csv_file, 'w');
        foreach ($all_users as $user) {
            fputcsv($fp, $user);
        }
        fclose($fp);

        // 認証成功
        session_start();
        $_SESSION['authenticated'] = true;
        $_SESSION['username'] = $username;
        header('Location: parser.php?target=index_t.html');
        exit();
    } else {
        $error = 'ユーザー名またはパスワードが正しくありません。';
    }
} else {
    $error = 'ユーザー名またはパスワードが正しくありません。';
}
?>

ハッシュ化後の users.csv:

admin,$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi
sales_user,$2y$10$7KIWIrOq5fJQGqFVJQq1XuH.oKz5aM7Qz...

メリット: - CSVファイルが漏洩しても、パスワードは解読困難 - PHPのpassword_hash()は自動的にソルト追加 - セキュリティベストプラクティスに準拠


5. 実運用のベストプラクティス

5-1. アクセスログの記録

access_log.php(ログ記録機能)を parser.php に統合:

<?php
// parser.php の冒頭に追加

function log_access($username, $target) {
    $log_file = 'userdata/access.log';
    $timestamp = date('Y-m-d H:i:s');
    $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
    $log_entry = "[$timestamp] User: $username, IP: $ip, Target: $target\n";
    file_put_contents($log_file, $log_entry, FILE_APPEND);
}

// 認証チェック後に呼び出し
if (isset($_SESSION['authenticated'])) {
    $username = $_SESSION['username'] ?? 'unknown';
    $target = $_GET['target'] ?? 'index_t.html';
    log_access($username, $target);
}
?>

access.log の内容例:

[2026-01-30 10:15:23] User: admin, IP: 192.168.1.100, Target: index_t.html
[2026-01-30 10:16:45] User: sales_user, IP: 192.168.1.101, Target: index_t.html
[2026-01-30 10:20:12] User: legal_user, IP: 192.168.1.102, Target: index_t.html

ログの活用: - 利用状況の分析 - 不正アクセスの検出 - コンプライアンス監査対応

5-2. セッションタイムアウトの設定

session_config.php(セッション設定):

<?php
// login.php と parser.php の冒頭に追加

ini_set('session.gc_maxlifetime', 1800);  // 30分
ini_set('session.cookie_lifetime', 1800); // 30分

session_start();

// 最終アクセス時刻のチェック
if (isset($_SESSION['last_access'])) {
    $inactive = time() - $_SESSION['last_access'];
    if ($inactive > 1800) {  // 30分以上操作がない
        session_unset();
        session_destroy();
        header('Location: login.php?timeout=1');
        exit();
    }
}

$_SESSION['last_access'] = time();
?>

効果: - 30分間操作がない → 自動ログアウト - セキュリティリスクの低減

5-3. HTTPS必須化

http_to_https.php(HTTP→HTTPS自動転送):

<?php
// login.php と parser.php の最初に追加

if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
    $redirect_url = "https://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
    header("Location: $redirect_url", true, 301);
    exit();
}
?>

効果: - HTTPでアクセスされても自動的にHTTPSへリダイレクト - パスワードの盗聴リスクを防止

5-4. ログアウト機能の追加

logout.php(ログアウト処理):

<?php
session_start();
session_unset();
session_destroy();
header('Location: login.php?logout=1');
exit();
?>

index_t.html にログアウトボタンを追加:

<!-- ヘッダー部分に追加 -->
<div style="position: fixed; top: 10px; right: 10px; z-index: 1000;">
    <span>ログイン中: <?php echo htmlspecialchars($_SESSION['username']); ?></span>
    <a href="logout.php" style="margin-left: 10px; color: #f44; text-decoration: none;">ログアウト</a>
</div>

6. トラブルシューティング

Q1: ログイン画面が表示されない

原因: parser.php のパス設定が間違っている

解決策:

// parser.php の設定を確認
$base_folder_name = 'ola-59';  // ← フォルダ名が正しいか確認

// ディレクトリ構造を確認
ls -la

// 正しい構造:
.
├── parser.php
├── login.php
└── ola-59/  ← このフォルダ名と一致する必要がある
    └── index_t.html

Q2: ログイン後に「File not found」エラー

原因: index_t.html のパスが間違っている

解決策:

# ファイルの存在確認
ls ola-59/index_t.html

# 存在しない場合、配置を確認
# parser.php の設定を確認
$file_param = $_GET['target'] ?? 'index_t.html';  # ← ファイル名が正しいか

Q3: CSS/JSが読み込まれない

原因: base タグの設定が間違っている

解決策:

// parser.php の設定を確認
$base_url = $base_folder_name . '/';  // ← 末尾の / が必要

// ブラウザの開発者ツールで確認
// コンソールエラーを見る
// 404エラーが出ている場合、パスが間違っている

Q4: セッションが維持されない

原因: PHPのセッション設定が正しくない

解決策:

# PHPの設定を確認
php -i | grep session

# session.save_path が書き込み可能か確認
ls -ld /tmp

# 権限がない場合、変更
chmod 1777 /tmp

7. まとめと次回予告

今回のポイント

✅ 認証機能の実装
parser.php + login.php で簡単に認証を追加

✅ 既存ファイルの変更不要
Ola-UIの既存ファイルはそのまま、パーサーを追加するだけ

✅ セッション管理
ログイン状態を維持、快適な操作

✅ セキュリティ対策
パスワードハッシュ化、HTTPS必須化、アクセスログ

✅ ユーザー管理
部門別アカウント、管理画面での追加

認証機能がもたらす価値

【Before: 認証なし】
- 機密情報の公開を躊躇
- 限定的なナレッジのみ搭載
- セキュリティリスクが常に存在

【After: 認証あり】
- 社内の詳細情報も安心して搭載
- 部門別の専門ナレッジを拡充
- コンプライアンス要件を満たす
- 監査対応が可能

→ Ola-UIの実用性が飛躍的に向上

次回予告: 第13回

次回は「Ola-UIの運用とメンテナンス実践」をテーマにお届けします。

構築したシステムを長期的に運用し、価値を最大化する実践ノウハウを解説します。


参考資料

PHP セキュリティ

セッション管理


文字数: 約20,000字
想定読了時間: 40〜50分
実践時間: 2〜3時間(認証機能の導入と設定)


Ola-UIに認証機能を追加して、安全で実用的なシステムに進化させましょう!
組織の機密情報も安心して搭載できる、真の業務システムへ。

🔒🚀

📚 記事一覧に戻る