PHP|Redisを使ったセッション管理(TLSなし・TLSあり)

今回の記事ではRedisをセッションデータストアとして使用する方法を学習していきます。本記事ではRedisサーバと別のRedisクライアントサーバの2台のセットアップが完了していることを前提に解説していきます。これらサーバのセットアップがまだの方は以下の記事を学習してからこちらに戻ってきてください。

あわせて読みたい
Redisサーバとクライアント(phpredis)のインストールと設定 今回はインメモリデータベースのRedisのインストール手順を学習していきます。Redisサーバ用とRedisクライアントサーバ用にEC2インスタンスを2台使用して、TLSで通信で...
目次

RedisをPHPのセッションDBとして使う場合

PHPのセッションにRedisを使用する場合、2つ方法があります。

選択肢1)RedisサーバーのTLSを使用せず、内部ネットワークでのみ接続許可して使用する。

PHPでセッションストアをRedisに指定する場合、TLSに必要なTLSの各証明書をPHPの設定ファイルに記述する方法がないのでデフォルト設定としてRedis+TLSを指定することはできません。なので、TLS無しで安全に通信を行うためには内部ネットワークのみでRedisサーバを使用する必要があります。Redis側でIPの制限やセキュリティーグループを使用してRedisサーバにアクセスできるサーバを制限する必要があります。

Redisをパブリックアクセス可能な状態(0.0.0.0)にしてパスワード指定もIP制限もない場合は誰でもRedisサーバにアクセスすることができてしまうので、こうした制限の追加は必須となります。

選択肢2)PHP側でSessionHandlerInterface のクラスの実装をしてTLSを使用する。

SessionHandlerInterfaceの実装をする際にTLSの各種証明書の設定ができます。ランタイム時にこの実装をセッションハンドラーに渡すことでRedisとはTLSを使った通信が可能になります。

それぞれの設定方法を見ていきましょう。

TLS無しでRedisをセッションDBとして設定する

まずは、Redisサーバ側の設定を行います。

Redisサーバ側の設定

Redisの設定ファイルが以下になるように変更してください。動作確認用の為パスワードはデフォルトのものをそのまま使用しています。

【Redis設定ファイル】
/etc/redis6/redis6.conf

#bind 127.0.0.1 -::1
bind 0.0.0.0

#protected-mode yes
protected-mode no

port 6379

# requirepass foobared
requirepass foobared

【Redisの再起動】
systemctl restart redis6

Redisの再起動がうまくいかない場合はログを確認してみましょう。
/var/log/redis6/redis6.log

クライアントサーバの設定

次にクライアントサーバ側のPHPの設定ファイルを変更していきます。

PHP-FPMの設定ファイル

PHP-FPMのファイルを以下のように変更します。セッションハンドラーにRedisを指定し、保存先にRedisサーバのドメインとパスワードを指定します。

【PHP-FPMの設定ファイル】
/etc/php-fpm.d/www.conf

;php_value[session.save_handler] = files
;php_value[session.save_path]    = /var/lib/php/session
php_value[session.save_handler] = redis
php_value[session.save_path]    = "tcp://Redisサーバのドメイン又はIP:6379?auth=foobared"

【PHP-FPMを再起動】
sudo systemctl restart php-fpm

編集するファイルはphp-fpmの設定ファイルです。変更する設定ファイルは/etc/php.iniでは無いことに注意しましょう。

設定反映の確認

phpinfo();を使ってReidsがセッションに設定されたことを確認しましょう。

phpinfo.php
<?php
echo phpinfo();
?>
> 以下の様に表示されれば正しく設定されています。
-
session.save_handler	redis	redis
session.save_path	tcp://Redisサーバのドメイン又はIP:6379	tcp://Redisサーバのドメイン又はIP:6379
-

クライアントから非TLSでRedisサーバにアクセス

PHP-FPMのセッションハンドラーにredisを設定してあるので、session_start() 関数が実行されると自動的にPHPのセッションIDをキーとしてRedis上にデータを作成します。

session.php
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

//セッションの開始
session_start();

//データをセッションに格納
$_SESSION['user'] = 'User';
echo "session set<br/>";

//セッションからデータを取得
$user = $_SESSION['user'];
echo "Data from Session: ".$user;
?>

格納されたデータが取得・表示できていれば成功です。

Redisサーバ側でのセッションデータの確認

Redisはデフォルトでは16個のDBが利用可能で、デフォルトでは0番目のDBが使用されます。セッションキーは次のような “PHPREDIS_SESSION:ランダムな文字列” のフォーマットになります。

【Redis CLIを起動】
sudo redis6-cli

【パスワードを入力】
AUTH foobared

【使用されているデータベースを表示】
INFO keyspace
# Keyspace
db0:keys=2,expires=1,avg_ttl=1424898

【キーの表示】
KEYS *
1) "test_key"
2) "PHPREDIS_SESSION:s8na0poilbcqe2ebj0mjr5b5r7"

【セッションキーからセッションデータを表示】
get PHPREDIS_SESSION:s8na0poilbcqe2ebj0mjr5b5r7
"user|s:5:\"User\";"

クライアントのPHP側でセッションに格納したデータが表示されれば成功です。

セッションキーが見つからない場合は次のようにデータベースの番号を変更しながら全てのキーを表示してみましょう。

SELECT 1 でDBを切り替える
KEYS * で全てのキーを表示してみる

上記の例ではTLSを使用していないので、Redisの設定ファイルのbind項目でIPを制限するかセキュリリティーグループでRedisサーバにアクセスできるサーバを制限する必要がありますが、ここでは紹介していません。各自で試してみましょう。

一つのデータベース内のデータをすべて削除する場合は以下のコマンドを使用します。
FLUSHDB

TLSをオンにしてRedisをセッションDBとして設定する

Redisサーバ側の設定

Redisサーバ側の設定ファイルが以下になるように変更してください。

【Redis設定ファイル】
/etc/redis6/redis6.conf

#bind 127.0.0.1 -::1
bind 0.0.0.0

#protected-mode yes
protected-mode no

#port 6379
port 0

# tls-port 6379
tls-port 6379

# tls-cert-file redis.crt 
# tls-key-file redis.key
tls-cert-file /usr/local/etc/openssl/server-redis-rsa.crt
tls-key-file /usr/local/etc/openssl/server-redis-rsa.key

# tls-ca-cert-file ca.crt                                                                                                      
tls-ca-cert-file /usr/local/etc/openssl/ca.crt

# tls-auth-clients no
tls-auth-clients yes

# tls-protocols "TLSv1.2 TLSv1.3"
tls-protocols "TLSv1.2 TLSv1.3"

# requirepass foobared
requirepass foobared

【Redisの再起動】
sudo systemctl restart redis6

クライアントサーバ側の設定

セッションハンドラーインターフェースを実装してランタイムで使用するので、PHP-FPMに設定したsession.save_path の設定は不要なので空にしておきます。

【PHP-FPMの設定ファイル】
/etc/php-fpm.d/www.conf

;php_value[session.save_path]    = "tcp://Redisサーバのドメイン又はIP:6379?auth=foobared"
php_value[session.save_path]    = ""

【PHP-FPMを再起動】
sudo systemctl restart php-fpm

クライアントからTLSでRedisサーバにアクセス

SessionHandlerInterface の実装の例を以下に示します。

session_tls.php
<?php
error_reporting(E_ERROR);
ini_set('display_errors', 1);

class RedisSessionHandler implements SessionHandlerInterface {
    private Redis $redis;
    private string $prefix;
    private int $ttl;

    public function __construct(string $host = '127.0.0.1', int $port = 6379, string $password = '', string $prefix = 'PHPSESSID:', int $ttl = 1440) {
        $this->redis = new Redis();

        $context = [
            'stream' => [
                'verify_peer' => true,
                'cafile' => '/usr/local/etc/openssl/ca.crt',
                'local_cert' => '/usr/local/etc/openssl/client-redis-rsa.crt',
                'local_pk' => '/usr/local/etc/openssl/client-redis-rsa.key',
            ],
        ];

        $this->redis->connect($host, $port, 1.5, null, 0, 0, $context);

        if ($password !== '') {
            if (!$this->redis->auth($password)) {
                throw new RuntimeException("Redis authentication failed");
            }
        }

        $this->prefix = $prefix;
        $this->ttl = $ttl;
    }

    public function open($savePath, $sessionName): bool {
        return true;
    }

    public function close(): bool {
        return $this->redis->close();
    }

    public function read($id): string {
        $data = $this->redis->get($this->prefix . $id);
        return $data !== false ? $data : '';
    }

    public function write($id, $data): bool {
        return $this->redis->setex($this->prefix . $id, $this->ttl, $data);
    }

    public function destroy($id): bool {
        return $this->redis->del($this->prefix . $id) > 0;
    }

    public function gc($maxLifetime): int|false {
        return true;
    }
}

$handler = new RedisSessionHandler(
    host: 'tls://Redisサーバのドメイン名又はIP',
    port: 6379,
    password: 'foobared',
    prefix: 'PHPREDIS_SESSION:',
    ttl: 1800
);

session_set_save_handler($handler, true);
session_start();

//データをセッションに格納
$_SESSION['user'] = 'User-TLS';
echo "session set<br/>";

//セッションからデータを取得
$user = $_SESSION['user'];
echo "Data from Session: ".$user;
?>

格納されたデータが取得・表示できていれば成功です。

Redisサーバ側でのセッションデータの確認

TLSがオンでRedisサーバ上でRedis CLIを起動する場合は各証明書の指定が必要です。

【Redis CLIを起動】
sudo redis6-cli --tls \
--cert /usr/local/etc/openssl/client-redis-rsa.crt \
--key /usr/local/etc/openssl/client-redis-rsa.key \
--cacert /usr/local/etc/openssl/ca.crt \
-h 127.0.0.1 \
-p 6379

【パスワードを入力】
AUTH foobared

【使用されているデータベースを表示】
INFO keyspace
# Keyspace
db0:keys=2,expires=1,avg_ttl=1424898

【キーの表示】
KEYS *
1) "PHPREDIS_SESSION:s8na0poilbcqe2ebj0mjr5b5r7"

【セッションキーからセッションデータを表示】
GET PHPREDIS_SESSION:s8na0poilbcqe2ebj0mjr5b5r7
"user|s:8:\"User-TLS\";"

クライアントのPHP側でセッションに格納したデータが表示されれば成功です。

まとめ

今回の記事ではPHPのセッションストアにRedisを使う方法を学習しました。Redisの使い方には2種類ありましたね。TLSを設定せずに内部ネットワークのみで使用する方法とTLSを設定する方法がありました。共にパスワードを設定しておくとより安全です。それぞれRedisの設定ファイルの変更箇所も確認してきました。最後にセッションハンドラーインターフェースの実装例を示して、TLSを使用したRedisとの通信も学びました。これらのスキルを習得して是非セッション管理に役立てていきましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次