本記事は、フレームワークなしでPHPを用いてセッション認証機能を構築する(Ⅲ.開発<part1>)の続きです。
Ⅲ.開発<part2>
- Authクラスの作成
- ロジックとルーティングを管理するコントローラーの作成
- 汎用関数をまとめたファイルの作成
1.Authクラスの作成
/classes/Auth.php
<?php require_once ( dirname(__FILE__) . '/../database/dbconnect.php');
require_once ( dirname(__FILE__) . '/../functions.php');
/** * Authクラス(認証関連のフィールドとメソッドの集まり)
* ■フィールド
* $email、$username、$password、$confirm_password、$varidate_err
* * ■メソッド
* ①ユーザー作成(createUser)
* ②バリデーション(validate)
* ③サインアップ(signUp)
* ④サインイン(signIn)
* ⑤Eメールでユーザー検索(searchUserBasedEmail)
* ⑥セッションから認証状態のチェック(checkIsAuthenticated)
* ⑦サインアウト(signOut)
*/
class Auth
{
// オブジェクト指向について
// アクセス修飾子(private、protected、public)でカプセル化
private $email;
private $username;
private $password;
private $confirm_password;
private $varidate_err = [];
/** * コンストラクタ(インスタンス生成時に動的に値を渡して、フィールドに値をセットできる)
* マジックメソッド(特殊な状況下で実行されるメソッド)
* */
public function __construct($email, $password, $username, $confirm_password)
{ //初期化 $this->email = $email;
$this->password = $password;
$this->username = $username;
$this->confirm_password = $confirm_password;
}
/**
* phpDoc
*/
/**
* @return bool $hasCreated
*/
// ①ユーザー作成(createUser)
public function createUser()
{
/**
* ;DELETE FROM user--"などのsqlインジェクション対策
* プレースホルダでSQL文の可変値を「$1」「$2」あるいは「?」などの特別な文字列で設定しておき、後から埋め込む
*/
$sql = 'INSERT INTO users (name, email, password) VALUES (?, ?, ?)';
$hasCreated = false;
try {
// PDOStatementオブジェクト->prepare
$stmt = connectToDatabase()->prepare($sql);
// executeはプリペアドステートメントを実行する。成功した場合に true を、失敗した場合に false を返す。
$hasCreated = $stmt->execute([$this->username, $this->email, password_hash($this->password,PASSWORD_DEFAULT)]); // パスワードはハッシュ化
} catch(\Exception $e) {
echo h($e);
}
return $hasCreated;
}
/**
* @param string $authenticationOperation
* @return array $varidate_err
*/
/**
* ②バリデーション(validate)
*/
public function validate($authenticationOperation)
{
if(empty($this->email)) {
$this->varidate_err['email_blank'] = 'メールアドレスを入力してください。';
}
if ($authenticationOperation === "signin") {
if(empty($this->password)) {
$this->varidate_err['password_blank'] = 'パスワードを入力してください。';
}
} else {
if(empty($this->username)) {
$this->varidate_err['username_blank'] = 'ユーザ名を入力してください。';
}
// 正規表現(preg_match)
if (!preg_match("/\A[a-z\d]{8,100}+\z/i", $this->password)) {
$this->varidate_err['password_invalid'] = 'パスワードは英数字8文字以上100文字以下にしてください。';
}
if ($this->password !== $this->confirm_password) {
$this->varidate_err['confirm_password_invalid'] = '確認用パスワードと異なっています。';
}
}
return $this->varidate_err;
}
/**
* @param bool $hasCreated
* @param string $email
* @return void
*/
// ③サインアップ(signUp)
public static function signUp($hasCreated, $email) // static
{
if(!$hasCreated) {
$_SESSION['err']['user_registration_failure'] = 'ユーザー登録に失敗しました';
header('Location: ../views/signup.php');
exit();
}
/**
* $thisは、自分自身のオブジェクトを指し、インスタンス化した際、クラス内のメンバ変数やメソッドにアクセスする際に使用
* self:: は、自クラスを示します。クラス定数、static変数については、インスタンス化せずに使用します。そのため、$thisは使用せず、代わりにselfを使用します。
*/
$newUser = self::searchUserBasedEmail($email);
session_regenerate_id(true); // セッションハイジャック対策(「セッション」を窃取し、本人に成り代わって通信を行うというサイバー攻撃 = なりすまし)のため、必ずセッションIDを再生成する
$_SESSION['signin_user'] = $newUser;
unset($_SESSION['err']); // サインインできたら、セッションのエラーを消去する
header('Location: ../views/mypage.php');
}
/**
* ④サインイン(signIn)
* @return void
*/
public function signIn()
{
$target_user = $this->searchUserBasedEmail($this->email); // ユーザーをemailから検索して取得
// emailが一致するユーザーがいなかったら早期リターン
if (count($target_user) === 0) {
$_SESSION['err']['email_invalid'] = 'メールアドレスが不正です。';
header('Location: ../views/signin.php');
exit();
}
// パスワードが不一致なら早期リターン(password_verifyはハッシュされたパスワードとベタがきのパスワードが正しいかチェックできる)
if (!password_verify($this->password, $target_user['password'])) {
header('Location: ../views/signin.php');
exit();
}
// emailとパスワードが一致した場合はサインイン
session_regenerate_id(true); // セッションハイジャック対策(「セッション」を窃取し、本人に成り代わって通信を行うというサイバー攻撃 = なりすまし)のため、必ずセッションIDを再生成する
$_SESSION['signin_user'] = $target_user;
unset($_SESSION['err']); // サインインできたら、セッションのエラーを消去する(unset = 変数や配列の特定の要素を削除)
header('Location: ../views/mypage.php');
}
/**
* @param string $email
* @return array|bool $user|false
*/
// ⑤Eメールでユーザー検索(searchUserBasedEmail)
public static function searchUserBasedEmail($email)
{
$sql = 'SELECT * FROM users WHERE email = ?';
try {
$stmt = connectToDatabase()->prepare($sql);
// execute(array|null)
$stmt->execute([$email]);
$user = $stmt->fetch(); // fetchメソッドでSQLの結果を返すことが可能
return $user;
} catch(\Exception $e) {
echo h($e);
return false;
}
}
/**
* @return bool $isAuthenticated
*/
// ⑥セッションから認証状態のチェック(checkIsAuthenticated)
public static function checkIsAuthenticated()
{
$isAuthenticated = false;
// セッションにサインインユーザが入っていればtrue
if (isset($_SESSION['signin_user'])) {
$isAuthenticated = true;
}
return $isAuthenticated;
}
/**
* @return void
*/
// ⑦サインアウト(signOut)
public static function signOut()
{
//セッションを破棄($_SESSIONはphpからセッションデータにアクセスするためのスーパーグローバル変数)
$_SESSION = array();
session_destroy();
header('Location: ../views/signin.php');
}
}
2.ロジックとルーティングを管理するコントローラーの作成
サインアップに関する処理
/controllers/signup_controller.php
<?php
/** * サインアップ画面からマイページへのルーティングなどの処理
* ①csrfトークンの照合
* ②postされたusername、email、password、confirm_passwordのバリデーション
* ③ユーザー登録処理と認証 */ session_start(); require_once(dirname(__FILE__) . '/../classes/Auth.php');
/** * ①csrfトークンの照合
*/ if (!isset($_SESSION['csrf_token']) || filter_input(INPUT_POST, 'csrf_token') !== $_SESSION['csrf_token']) {
exit('不正なリクエスト');
}
unset($_SESSION['csrf_token']);
// ②postされたusername、email、password、confirm_passwordのバリデーション
$auth = new Auth(filter_input(INPUT_POST, 'email'), filter_input(INPUT_POST, 'password'), filter_input(INPUT_POST, 'username'), filter_input(INPUT_POST, 'password_conf'));
$signup_error = $auth->validate("signup");
if (count($signup_error) > 0) {
$_SESSION['err'] = $signup_error;
header('Location: ../views/signup.php');
exit();
}
// ③ユーザー登録処理と認証
$auth = new Auth(filter_input(INPUT_POST, 'email'), filter_input(INPUT_POST, 'password'), filter_input(INPUT_POST, 'username'), $confirm_password = null);
$hasCreated = $auth->createUser();
Auth::signUp($hasCreated, filter_input(INPUT_POST, 'email'));
サインインに関する処理
/controllers/signin_controller.php
<?php /** * サインイン画面からマイページへのルーティングなどの処理
* ①csrfトークンの照合 * ②postされたemailとpasswordのバリデーション
* ③サインイン処理
*/
// この位置で書くと、require_onceの読み込み先ファイルでもsessionが使用できる
session_start(); require_once(dirname(__FILE__) . '/../classes/Auth.php');
/** * ①csrfトークンの照合
* セッションにトークンがない、もしくはフォームで送信されたトークンと一致しない場合、処理を中止
*/ if (!isset($_SESSION['csrf_token']) || filter_input(INPUT_POST, 'csrf_token') !== $_SESSION['csrf_token']) { exit('不正なリクエスト'); }
unset($_SESSION['csrf_token']); // セッションの中のcsrf_tokenを削除(ワンタイム利用のため) フォーム画面以外からのセッションや、二重送信(フォーム再送信のconfirmからの送信)を対策
/** * ②postされたemailとpasswordのバリデーション(メールアドレス、パスワードが未入力でないかチェックする)
* Authクラスのインスタンス化(->)
*/
$auth = new Auth(filter_input(INPUT_POST, 'email'), filter_input(INPUT_POST, 'password'), $username = null, $confirm_password = null);
$signin_error = $auth->validate("signin");
// count
if (count($signin_error) > 0) {
$_SESSION['err'] = $signin_error;
header('Location: ../views/signin.php');
exit();
}
// ③サインイン処理
$auth->signIn();
サインアウトに関する処理
/controllers/signout_controller.php
<?php
/**
* マイページ画面からのサインアウト処理
* ①サインアウト処理
* ②セッションを削除
*/
session_start();
require_once(dirname(__FILE__) . '/../classes/Auth.php');
// サインアウトボタンを押してサインアウトを実行しているか
if (!$signout = filter_input(INPUT_POST, 'signout')) {
exit('不正なリクエストです。');
}
// ①サインアウト処理、②セッションを削除
Auth::signOut();
3.汎用関数をまとめたファイルの作成
/functions.php
<?php
/**
* @param string $str 対象の文字列
* @return string 処理された文字列
*/
/**
* xss対策(エスケープ処理)
* ユーザーが入力した値などをechoで出力する際にはエスケープ処理が必ず必要(フォーム等で入力された悪意のあるスクリプトの実行を無効化できる)
* htmlspecialchars( 変換対象, 変換パターン, 文字コード )
* ENT_QUOTESは特殊文字のうちシングルクォーテーションとダブルクォーテーションも変換対象に含めるようになる
* < → <
* > → >
* " → "
* laravelのbladeでは{{ name }}
*/
function h($str) {
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
/**
* @return string $csrf_token
*/
/**
* csrfトークンはCSRF対策用のワンタイムトークン
* csrfトークンはざっくりいうと、なりすましを防止する役割。基本的にPOSTリクエストには必須
*/
function generateCsrfToken() {
$csrf_token = bin2hex(random_bytes(32));
return $csrf_token;
}
今回は以上になります。
コメント