質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.20%

ログイン認証について

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,694

ssk

score 288

ログイン認証ができません。。

$hashesをvar_dumpすると

array(1) { ["08011111111"]=> string(12) "$2y$10$BOqI." }

と、データベースから情報は取れているようです。
ユーザ認証できないのは何が原因でしょうか?

【参考URL】
http://qiita.com/mpyw/items/bb8305ba196f5105be15

<?php include('header.php'); ?>
<?php
require_once __DIR__ . '/functions.php';
require_unlogined_session();
ini_set('display_errors', true);
error_reporting(E_ALL);
//ユーザから受け取ったユーザ名とパスワード
    $tel_mail = filter_input(INPUT_POST, 'tel_mail');
    $password = filter_input(INPUT_POST, 'password');

try{
    //データベース接続
    include('./conf/db_con.php');
    $sql ='SELECT * FROM user WHERE (tel=:tel_mail OR mail=:tel_mail)';
    $stmt=$dbh->prepare($sql);
    $data[':tel_mail'] = $tel_mail;
    $stmt->execute($data);
    $dbh = null;
    //1行ずつ取り出し
    $rec = $stmt->fetch(PDO::FETCH_ASSOC);
    // 事前に生成したユーザごとのパスワードハッシュの配列
    $hashes = [
        $tel_mail => $rec['password'],
    ]; 
}catch(Exception $e){
    print 'ただいま障害により大変ご迷惑をお掛けしております。';
    exit();
}

// POSTメソッドのときのみ実行
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (
        validate_token(filter_input(INPUT_POST, 'token')) &&
        password_verify(
            $password,
            isset($hashes[$tel_mail])
                ? $hashes[$tel_mail]
                : '$2y$10$abcdefghijklmnopqrstuv' // ユーザ名が存在しないときだけ極端に速くなるのを防ぐ
        )
    ) {
        // 認証が成功したとき
        // セッションIDの追跡を防ぐ
        session_regenerate_id(true);
        // ユーザ名をセット
        session_start();//合言葉を決める
        $_SESSION['login']=1;//ログインOKの証拠を残す
        $_SESSION['user_id']=$rec['user_id'];
        // ログイン完了後に / に遷移
        header('Location: /index.php');
        exit;
    }
    // 認証が失敗したとき
    // 「403 Forbidden」
    http_response_code(403);
}

header('Content-Type: text/html; charset=UTF-8');

?>
<main id="regist">
<div class="container">
    <section class="container">
    <?php var_dump($hashes[$tel_mail]); ?>
      <form class="form-horizontal" action="" method="post">
    <fieldset>
        <div class="clearfix"></div><br>
        <div class="input-group input-group-lg">
        <span class="input-group-addon"><i class="glyphicon glyphicon-user red"></i></span>
        <input type="text" class="form-control" placeholder="電話番号またはメールアドレス" name="tel_mail">
        </div>
        <div class="clearfix"></div><br>
        <div class="input-group input-group-lg">
        <span class="input-group-addon"><i class="glyphicon glyphicon-lock red"></i></span>
        <input type="password" class="form-control" placeholder="パスワード" name="password">
        </div>
        <div class="clearfix"></div>
       <div class="col-sm-12">
                    <button type="submit" class="btn btn-default center-block">ログイン</button>
        </div>
    </fieldset>
    <input type="hidden" name="token" value="<?=h(generate_token())?>">
</form>
<?php if (http_response_code() === 403): ?>
<p style="color: red;">ユーザ名またはパスワードが違います</p>
<?php endif; ?>
    </section>
</div>
</main>
<?php include('footer.php'); ?>

functions.php

<?php

/**
 * ログイン状態によってリダイレクトを行うsession_startのラッパー関数
 * 初回時または失敗時にはヘッダを送信してexitする
 */
function require_unlogined_session()
{
    // セッション開始
    @session_start();
    // ログインしていれば / に遷移
    if (isset($_SESSION['user_id'])) {
        header('Location: /');
        exit;
    }
}
function require_logined_session()
{
    // セッション開始
    @session_start();
    // ログインしていなければ /login.php に遷移
    if (!isset($_SESSION['user_id'])) {
        header('Location: ./login.php');
        exit;
    }
}

/**
 * CSRFトークンの生成
 *
 * @return string トークン
 */
function generate_token()
{
    // セッションIDからハッシュを生成
    return hash('sha256', session_id());
}

/**
 * CSRFトークンの検証
 *
 * @param string $token
 * @return bool 検証結果
 */
function validate_token($token)
{
    // 送信されてきた$tokenがこちらで生成したハッシュと一致するか検証
    return $token === generate_token();
}

/**
 * htmlspecialcharsのラッパー関数
 *
 * @param string $str
 * @return string
 */
function h($str)
{
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
?>

データベースはuserというテーブルで
tel
mail
password

と、構成されています。

【追記】
エラーは発生していません。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • omusoba

    2016/06/12 19:10 編集

    そうですね....答えを聞く限り、認証できないということはhttp_response_code(403);に進むということですよね?validate_tokenと password_verifyの結果自体はデバック時などに確認して結果はどうでしたか?

    キャンセル

  • ssk

    2016/06/13 14:53

    Kosuke_Shibuya様

    情報を追加しました。
    いかがでしょう、、よろしくお願い致します。

    キャンセル

  • ssk

    2016/06/13 14:54

    omusoba様

    http_response_code(403);に進むということですよね?
    ↑仰る通りです。

    キャンセル

回答 3

checkベストアンサー

0

header.phpのincludeはロジックが全て終わってからにしてください.ob_startが最初に実行されていない限り,認証などに絡むHTTPヘッダが正しく送信されなくなってしまいます.最初にロジックを全て固めて,HTMLはやるべきことを全てやってから出し始める,というのを徹底してください.


【修正案】

<?php

// エラーをデバッグのために全表示する
ini_set('display_errors', true);
error_reporting(E_ALL);

// 必要な関数群を読み込む
require_once __DIR__ . '/functions.php';

// 未ログインセッションを要求し,ログイン済みの場合は / に遷移する
require_unlogined_session();

// ユーザから受け取ったユーザ名,パスワード,CSRFトークン
$tel_mail = filter_input(INPUT_POST, 'tel_mail');
$password = filter_input(INPUT_POST, 'password');
$token    = filter_input(INPUT_POST, 'token');

// POSTメソッドのときのみ実行
if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    try {

        // データベースから情報取得
        // (提示されていないdb_con.phpの書き方が怪しいので敢えて書き直します)
        $pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // SQL実行時のエラーもPDOExceptionに変換する
        ]);
        $stmt = $pdo->prepare('SELECT * FROM user WHERE tel = :tel_mail OR mail = :tel_mail LIMIT 1');
        $stmt->execute(compact('tel_mail')); // compactは ['tel_mail' => $tel_mail] の省略形
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

    } catch (PDOException $e) {

        // PDOでエラーが発生したとき
        // テキストで「500 Internal Server Error」
        header('Content-Type: text/plain; charset=UTF-8');
        http_response_code(500);
        exit('データベース障害: ' . $e->getMessage());

    }

    // CSRFバリデーションおよびパスワードハッシュの検証
    // (ユーザ名が存在しないときだけ極端に速くなるのも防ぐ)
    if (
        validate_token($token) &&
        password_verify($password, $user ? $user['password'] : '$2y$10$abcdefghijklmnopqrstuv')
    ) {
        // 認証が成功したとき
        // セッションIDの追跡を防ぐ
        session_regenerate_id(true);
        $_SESSION['user_id'] = $user['user_id']; // (ログインの証拠はこれの存在を見ればOK)
        // ログイン完了後に / に遷移
        header('Location: /');
        exit;
    }

    // 認証が失敗したとき
    // 「403 Forbidden」
    http_response_code(403);

}

// HTMLとして表示
header('Content-Type: text/html; charset=UTF-8');

?>
<?php include('header.php'); ?>
<main id="regist">
    <div class="container">
        <section class="container">
            <form class="form-horizontal" action="" method="post">
                <fieldset>
                    <div class="clearfix"></div><br>
                    <div class="input-group input-group-lg">
                        <span class="input-group-addon"><i class="glyphicon glyphicon-user red"></i></span>
                        <input type="text" class="form-control" placeholder="電話番号またはメールアドレス" name="tel_mail" value="<?=h($tel_mail)?>">
                    </div>
                    <div class="clearfix"></div><br>
                    <div class="input-group input-group-lg">
                        <span class="input-group-addon"><i class="glyphicon glyphicon-lock red"></i></span>
                        <input type="password" class="form-control" placeholder="パスワード" name="password">
                    </div>
                    <div class="clearfix"></div>
                    <div class="col-sm-12">
                        <button type="submit" class="btn btn-default center-block">ログイン</button>
                    </div>
                </fieldset>
                <input type="hidden" name="token" value="<?=h(generate_token())?>">
            </form>
<?php if (http_response_code() === 403): ?>
        <p style="color: red;">ユーザ名またはパスワードが違います</p>
<?php endif; ?>
        </section>
    </div>
</main>
<?php include('footer.php'); ?>

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/13 21:33

    こちらの方法で実装することができました。
    <?php include('header.php'); ?>の記載が問題でした。

    キャンセル

0

array(1) { ["08011111111"]=> string(12) "$2y$10$BOqI." }

間違いなくこのパスワードは明らかにおかしいです。最低64文字以上になりはず。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/13 14:56

    DBのpasswordが8文字以内になっていました。
    64文字にしたところ、以下のようになりました。

    array(1) { ["08022222222"]=> string(60) "$2y$10$fkO3opYbOEKxwi6BmD5P0OPnUl2FaiyLY1wfcy/OVGmFMWgE4hIcu" }

    しかし、まだログインはできないです。

    キャンセル

  • 2016/06/13 21:32

    回答ありがとうございました><
    解決することができました。

    キャンセル

0

何でハッシュ値生成しましたか?
md5 でしょうか
SHA256 でしょうか
ファイルの文字コードと
DBテーブルの文字コードが違ったりすると
はじかれます。

切り分けとしては、
その辺でしょうかね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/13 21:32

    ご回答ありがとうございました。
    無事、実装することができました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.20%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる