【PHP】CSRFの仕組み

セッションの値と比較して検証しているのね。理解できました。

<?php

session_start();

$toke_byte = openssl_random_pseudo_bytes(16);
$csrf_token = bin2hex($toke_byte);

$_SESSION['csrf_token'] = $csrf_token;
?>

<!DOCTYPE html>
<html>
<body>
    <form action="contact.php" method="POST">
        <div>
            <label for="name">お名前:</label>
            <input type="text" id="name" name="name" />
        </div>
        <div>
            <label for="email">メールアドレス:</label>
            <input type="email" id="email" name="email" />
        </div>
        <div>
            <label for="message">お問い合わせ本文</label>
            <textarea id="message" name="message"></textarea>
        </div>
        <input type="hidden" name="csrf_token" value="<?php echo $csrf_token; ?>" />
        <button type="submit">送信</button>
    </form>
</body>
</html>
session_start();
// ワンタイムトークンの一致を確認
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
  // トークンが一致しなかった場合
  die('お問い合わせの送信に失敗しました');
} else {
    echo('認証に成功しました。');
}

[LINE] Messaging APIで、テキスト以外のレスポンスを送る

modelをTextMessageではなく、TemplateMessageに変更する
公式ドキュメントを参考にする
https://developers.line.biz/en/reference/messaging-api/#imagemap-message
SDKのモデル一覧はこちら
https://github.com/line/line-bot-sdk-php/blob/master/examples/KitchenSink/src/LINEBot/KitchenSink/EventHandler/MessageHandler/TextMessageHandler.php

  $template = array('type'    => 'confirm',
                  'text'    => 'テキストメッセージ。最大240文字',
                  'actions' => array(
                                 array('type'=>'message', 'label'=>'yes', 'text'=>'yesを押しました' ),
                                 array('type'=>'message', 'label'=>'no',  'text'=>'noを押しました' )
                                )
                );
  $message = new \LINE\Clients\MessagingApi\Model\TemplateMessage(['type' => 'template','altText'  => '代替テキスト','template' => $template]);
  $request = new \LINE\Clients\MessagingApi\Model\ReplyMessageRequest([
    'replyToken' => $replyToken,
    'messages' => [$message],
  ]);
  $response = $messagingApi->replyMessage($request);

LINE側の挙動は確認できたので、あとはMySQLのCRUDとChatGPTのレスポンスの確認

LINE Messaging APIで複数レスポンスする

複数のメッセージを送信したい場合は、messagesを配列にする。

$message = new \LINE\Clients\MessagingApi\Model\TextMessage(['type' => 'text','text' => $message->{"text"}]);
$userId = new \LINE\Clients\MessagingApi\Model\TextMessage(['type' => 'text','text' => $userId]);
$request = new \LINE\Clients\MessagingApi\Model\ReplyMessageRequest([
    'replyToken' => $replyToken,
    'messages' => [$message, $userId],
]);
$response = $messagingApi->replyMessage($request);

1つのリクエストでreplyTokenを複数作ることはできないので、以下のようにレスポンスを複数は書けない。この場合、最初のレスポンスしか表示されない。

$message = new \LINE\Clients\MessagingApi\Model\TextMessage(['type' => 'text','text' => $message->{"text"}]);
$request = new \LINE\Clients\MessagingApi\Model\ReplyMessageRequest([
    'replyToken' => $replyToken,
    'messages' => [$message],
]);
$response = $messagingApi->replyMessage($request);

$userId = new \LINE\Clients\MessagingApi\Model\TextMessage(['type' => 'text','text' => $userId]);
$request = new \LINE\Clients\MessagingApi\Model\ReplyMessageRequest([
    'replyToken' => $replyToken,
    'messages' => [$userId],
]);
$response = $messagingApi->replyMessage($request);

LINE Messaging APIでUserIDを取得する

webhookイベントオブジェクトの構造を参考にUserIDを取得する

### webhookイベントオブジェクト
https://developers.line.biz/ja/reference/messaging-api/#webhook-event-objects
これを見ると、sourceのuserIdがあるので、これをキーに出来そう

{
  "destination": "xxxxxxxxxx",
  "events": [
    {
      "type": "message",
      "message": {
        "type": "text",
        "id": "14353798921116",
        "text": "Hello, world"
      },
      "timestamp": 1625665242211,
      "source": {
        "type": "user",
        "userId": "U80696558e1aa831..."
      },
      "replyToken": "757913772c4646b784d4b7ce46d12671",
      "mode": "active",
      "webhookEventId": "01FZ74A0TDDPYRVKNK77XKC3ZR",
      "deliveryContext": {
        "isRedelivery": false
      }
    },
    {
      "type": "follow",
      "timestamp": 1625665242214,
      "source": {
        "type": "user",
        "userId": "Ufc729a925b3abef..."
      },
      "replyToken": "bb173f4d9cf64aed9d408ab4e36339ad",
      "mode": "active",
      "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK",
      "deliveryContext": {
        "isRedelivery": false
      }
    },
    {
      "type": "unfollow",
      "timestamp": 1625665242215,
      "source": {
        "type": "user",
        "userId": "Ubbd4f124aee5113..."
      },
      "mode": "active",
      "webhookEventId": "01FZ74B5Y0F4TNKA5SCAVKPEDM",
      "deliveryContext": {
        "isRedelivery": false
      }
    }
  ]
}

ソースコード上ではこのように取得する

$message = $jsonObj->{"events"}[0]->{"message"}; 
$userID = $jsonObj->{"events"}[0]->{"source"}->{"userId"}; 

さあ、mysqlの構造をどうするかですね。
その前にレスポンスのバリエーションを確認したい。

line-bot-sdk-phpとreplyToken

LINE Messaging APIを使用した開発をやります。
$ php composer.phar require linecorp/line-bot-sdk
$ php composer.phar require guzzlehttp/guzzle

こちらでは上手くいかない

require("vendor/autoload.php");

use LINE\LINEBot\Constant\HTTPHeader;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use LINE\LINEBot;

$channel_access_token = 'xxxx';
$channel_secret = 'xxxx';

$http_client = new CurlHTTPClient($channel_access_token);
$bot = new LINEBot($http_client, ['channelSecret' => $channel_secret]);
$signature = $_SERVER['HTTP_' . HTTPHeader::LINE_SIGNATURE];
$http_request_body = file_get_contents('php://input');
$events = $bot->parseEventRequest($http_request_body, $signature);
$event = $events[0];

$reply_token = $event->getReplyToken();
$reply_text = $event->getText();
$bot->replyText($reply_token, $reply_text);

Githubを見ながら少し内容を変更します。SDKのバージョンによってライブラリのclass構成が変わっているようです。
https://github.com/line/line-bot-sdk-php
replyTokenは、{“events”}[0]に入っている。line-bot-sdk-phpのsampleを見ても全然わからなかったので、凄い時間がかったw

ini_set("display_errors", 1);
error_reporting(E_ALL);
require("vendor/autoload.php");

$jsonString = file_get_contents('php://input'); error_log($jsonString); 
$jsonObj = json_decode($jsonString); $message = $jsonObj->{"events"}[0]->{"message"}; 
$replyToken = $jsonObj->{"events"}[0]->{"replyToken"};

$client = new \GuzzleHttp\Client();
$config = new \LINE\Clients\MessagingApi\Configuration();
$config->setAccessToken('');
$messagingApi = new \LINE\Clients\MessagingApi\Api\MessagingApiApi(
  client: $client,
  config: $config,
);

$message = new \LINE\Clients\MessagingApi\Model\TextMessage(['type' => 'text','text' => $message->{"text"}]);
$request = new \LINE\Clients\MessagingApi\Model\ReplyMessageRequest([
    'replyToken' => $replyToken,
    'messages' => [$message],
]);
$response = $messagingApi->replyMessage($request);

Ubuntu22.04にPHPとMySQLをインストールする

$ sudo apt update

### PHP
$ sudo apt install php libapache2-mod-php php-mysql
$ php -v
PHP 8.1.2-1ubuntu2.14 (cli) (built: Aug 18 2023 11:41:11) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.2, Copyright (c) Zend Technologies
with Zend OPcache v8.1.2-1ubuntu2.14, Copyright (c), by Zend Technologies

### MySQL
$ sudo apt install mysql-server

### composer.phar
$ curl -sS https://getcomposer.org/installer | php

処理に時間がかかるデータベースへの挿入とリロード処理を同時実行したい

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
	<?php
		$rel = $_GET['reload'];
		echo $rel;
		if($rel == 1) {
			echo ($_SERVER['PHP_SELF'].'?reloaded' .'<br/>');
			sleep(1);
			echo '<script type="text/JavaScript"> location.href = "/test.php"</script>';
			
		} else {
			echo ($_SERVER['PHP_SELF'].'?reloaded2' .'<br/>');

		}
		
	?>
	<a href="test.php?reload=1">リロード</a>
</body>
</html>

リロードしたら処理が更新されてしまうから、非同期で取得、送信したい。

PHPのワンタイムパッド

// 暗号化したい文字列
$str = "保つのに時があり,捨てるのに時がある。";

// ワンタイムパッド
$pad = [
  12,20,148,22,87,91,239,187,206,215,103,207,192,46,75,243,
  204,61,121,210,145,167,108,78,166,129,109,239,138,134,150,196,
  217,63,158,201,204,66,181,198,54,0,0,130,163,212,57,167,
  169,115,170,50,109,116,173,177,252,242,233,3,33,28,139,73,
];

function convert($str, $pad){
	$res = "";
	for($i=0; $i<strlen($str); $i++) {
		$c = ord(substr($str, $i, 1));
		$cx = $c ^ $pad[$i];

		$res .= chr($cx);
	}
	return $res;
}

$enc = convert($str, $pad);
echo "暗号化した文字列:{$enc}\n";

$dec = convert($enc, $pad);
echo "復号化した文字列:{$dec}\n";

PHPの乱数

for ($i = 0; $i < 10; $i++){
	print(mt_rand().'<br>');
}

for ($i = 0; $i < 10; $i++){
	print(mt_rand(1, 6).'<br>');
}

1772070118
2025234455
909739598
265570849
205762700
1249037110
503159177
1157558449
1270711055
1797668593
5
3
1
4
3
2
4
3
2
6

西暦から年齢に変換

$now = date('Ymd');

$birthday = "1990-07-01";
$birthday = str_replace("-", "", $birthday);

$age = floor(($now - $birthday) / 10000);
echo $age . "歳";

$ php app.php
33歳