技術記事に戻る

Node.js Expressによるセキュアなメディア配信サーバーの構築手法と実装の全貌

Node.jsExpressAPI

Node.js Expressによるセキュアなメディア配信サーバーの構築手法と実装の全貌

ソースコード: https://github.com/hayate-hu6/local-media-viewer.git

はじめに

PCから画像一覧を利用するだけでなく、スマホやタブレットで閲覧できれば便利です。そこでローカルネットワーク内でのアクセス制限を実装しました。 今回はバックエンドの中核である server.js の実装について詳細に見ていきます。 このファイルはわずか160行程度ですが、Webサーバーに必要な要素(ルーティング、ミドルウェア、セキュリティ、API)が凝縮されています。

Expressサーバーの基本セットアップ

まず、必要なモジュールのインポートとサーバーの初期化部分です。

const express = require('express');
const fs = require('fs');
const path = require('path');
const cookieParser = require('cookie-parser');
const crypto = require('crypto');

const app = express();
const PORT = 3000;

ここでは標準的な fs (ファイルシステム), path (パス操作) に加え、cookie-parser でクッキーの解析を、crypto でランダムなパスワード生成を行っています。

設定ファイルの読み込み戦略

アプリケーションの柔軟性を高めるため、設定値(メディアフォルダのパスなど)は外部ファイル config.json に切り出されています。

const CONFIG_PATH = path.join(__dirname, 'config.json');
let CONFIG = { mediaPath: '../m' }; // デフォルト値

if (fs.existsSync(CONFIG_PATH)) {
    try {
        const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
        CONFIG = JSON.parse(raw);
    } catch (e) {
        console.error("Error reading config.json, using defaults.");
    }
}

fs.existsSync でファイルの存在を確認し、存在する場合のみ読み込むという堅実な実装です。JSONのパースエラー (JSON.parse) も考慮して try-catch ブロックで囲むことで、設定ファイルが壊れていてもサーバーがクラッシュせず、デフォルト設定で起動するように配慮されています。

IPアドレスフィルタリング(簡易ファイアウォール)

このアプリは「ローカルネットワーク内での利用」を前提としているため、外部(インターネット)からの意図しないアクセスを遮断するためのミドルウェアが実装されています。

function isPrivateIP(ip) {
    if (ip === '::1' || ip === '127.0.0.1') return true; // ローカルループバック
    if (ip.startsWith('::ffff:')) ip = ip.substring(7); // IPv4射影アドレス対応
    
    // IPv4のプライベートIP範囲チェック
    const parts = ip.split('.');
    if (parts.length !== 4) return false;
    const p0 = parseInt(parts[0], 10);
    const p1 = parseInt(parts[1], 10);
    
    if (p0 === 10) return true; // 10.x.x.x
    if (p0 === 172 && p1 >= 16 && p1 <= 31) return true; // 172.16-31.x.x
    if (p0 === 192 && p1 === 168) return true; // 192.168.x.x
    
    return false;
}

この isPrivateIP 関数は、リクエスト元のIPアドレスがプライベートIPアドレス(自宅や社内LANのアドレス)かどうかを判定します。Expressのミドルウェアとして以下のように組み込まれています。

app.use((req, res, next) => {
    const clientIP = req.connection.remoteAddress || req.socket.remoteAddress;
    if (isPrivateIP(clientIP)) {
        next(); // 許可
    } else {
        console.warn(`Blocked access from: ${clientIP}`);
        res.status(403).send('Access Denied: Local Network Only');
    }
});

next() を呼ぶことで次の処理に進み、条件を満たさない場合はその場で 403 Forbidden を返してリクエストを終了させます。これにより、誤ってポートを開放してしまった場合でも、最低限の防御壁として機能します。

静的ファイルの配信と保護

このアプリケーションには「公開エリア」と「保護エリア」の2つが存在します。

1. 公開エリア (/public)

ログイン画面を表示するためのHTMLやJSは、誰でもアクセスできる必要があります。

app.use(express.static(path.join(__dirname, 'public')));

express.static を使うことで、public フォルダ内のファイル(index.htmlapp.js)は認証なしで配信されます。ユーザーはまずこの index.html にアクセスし、ログイン画面を見ることになります。

2. 保護エリア (/media)

一方で、個人の写真や動画が入ったフォルダは、認証済みユーザー以外には見せたくありません。

app.use('/media', requireAuth, express.static(MEDIA_ROOT));

ここで重要なのが、第2引数に渡している requireAuth ミドルウェアです。/media 以下のファイルにアクセスがあった場合、まず requireAuth が実行され、認証チェック(Cookieの確認)が行われます。そこで許可された場合のみ、第3引数の express.static が実行され、実際の画像ファイルが返されます。

このように、Expressではミドルウェアをチェーンさせることで、「認証付きの静的ファイル配信」を非常に簡潔に記述できるのが大きな利点です。

まとめ

今回は server.js の基盤となる部分を解説しました。 IPフィルタリングによる水際対策、設定ファイルによる柔軟性、そしてExpressのミドルウェア機能を活用したアクセス制御。これらは小規模なアプリケーションであってもセキュリティと保守性を高めるために非常に重要なパターンです。

次回は、ここで登場した requireAuth の中身、つまり「認証システム」の具体的な実装について深掘りします。

次回: DB不要!Node.js CryptoモジュールとHttpOnly Cookieで実現する安全な簡易認証システム


同シリーズ記事