2
1

More than 5 years have passed since last update.

CakePHPのソースコードを見てたら、FormAuthenticateが面白かった

Last updated at Posted at 2017-08-13

今回CakePHPのソースコードを読んでみて、面白いところがあったので、切り取って参照してみようと思う。

FormAuthenticate.phpである。
これから変化するかもしれないので、以下にコピペしようかと思う。

namespace Cake\Auth;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
/**
 * An authentication adapter for AuthComponent. Provides the ability to authenticate using POST
 * data. Can be used by configuring AuthComponent to use it via the AuthComponent::$authenticate config.
 *
 * ```
 *  $this->Auth->authenticate = [
 *      'Form' => [
 *          'finder' => ['auth' => ['some_finder_option' => 'some_value']]
 *      ]
 *  ]
 * ```
 *
 * When configuring FormAuthenticate you can pass in config to which fields, model and additional conditions
 * are used. See FormAuthenticate::$_config for more information.
 *
 * @see \Cake\Controller\Component\AuthComponent::$authenticate
 */
class FormAuthenticate extends BaseAuthenticate
{
    /**
     * Checks the fields to ensure they are supplied.
     *
     * @param \Cake\Http\ServerRequest $request The request that contains login information.
     * @param array $fields The fields to be checked.
     * @return bool False if the fields have not been supplied. True if they exist.
     */
    protected function _checkFields(ServerRequest $request, array $fields)
    {
        foreach ([$fields['username'], $fields['password']] as $field) {
            $value = $request->getData($field);
            if (empty($value) || !is_string($value)) {
                return false;
            }
        }
        return true;
    }
    /**
     * Authenticates the identity contained in a request. Will use the `config.userModel`, and `config.fields`
     * to find POST data that is used to find a matching record in the `config.userModel`. Will return false if
     * there is no post data, either username or password is missing, or if the scope conditions have not been met.
     *
     * @param \Cake\Http\ServerRequest $request The request that contains login information.
     * @param \Cake\Http\Response $response Unused response object.
     * @return mixed False on login failure. An array of User data on success.
     */
    public function authenticate(ServerRequest $request, Response $response)
    {
        $fields = $this->_config['fields'];
        if (!$this->_checkFields($request, $fields)) {
            return false;
        }
        return $this->_findUser(
            $request->getData($fields['username']),
            $request->getData($fields['password'])
        );
    }
}

An authentication adapter for AuthComponent. Provides the ability to authenticate using POST data. Can be used by configuring AuthComponent to use it via the AuthComponent::$authenticate config.

AuthComponentの認証アダプタです。POSTデータを利用して、認証する能力を供給するオブジェクトです。AuthComponent::$authenticateの構成を利用して、AuthComponentを構成します。

以下の関数では、フィールドにデータが供給されているかどうかをチェックします。

    protected function _checkFields(ServerRequest $request, array $fields)
    {
        foreach ([$fields['username'], $fields['password']] as $field) {
            $value = $request->getData($field);
            if (empty($value) || !is_string($value)) {
                return false;
            }
        }
        return true;
    }

\$requestに、ServerRequestの型で、サーバへのリクエストが格納されています。\$fieldsは、ハッシュの形式です。\$fieldsから'username'と'password'がキーのものを取り出し、一つ一つ、\$fieldに格納します。
一つ一つ格納した\$fieldによって、

$request->getData($field);

によって、リクエストの中に、\$fieldに相当するものが存在するかどうかをチェックします。
そして、それがセットされていれば、trueを返し、そうでなければ、falseを返します。
ここのロジックでは、'username'と'password'が両方セットされていた時のみ、trueを返し、どちらかがセットされていない場合は、falseを返します。
ここでチェックしているのは、'username'と'password'がセットされているということであり、認証しているわけではありません。ちなみに、これは、以下の認証メソッドの内部のみでしか使用されていないです。

    public function authenticate(ServerRequest $request, Response $response)
    {
        $fields = $this->_config['fields'];
        if (!$this->_checkFields($request, $fields)) {
            return false;
        }

        return $this->_findUser(
            $request->getData($fields['username']),
            $request->getData($fields['password'])
        );
    }

Authenticates the identity contained in a request. Will use the config.userModel, and config.fields to find POST data that is used to find a matching record in the config.userModel. Will return false if there is no post data, either username or password is missing, or if the scope conditions have not been met.

リクエストに含まれる個人を認証します。config.userModelconfig.fieldsを使用して、POSTデータがconfig.userModelに含まれているかどうかを調べるのに使われます。usernameかpasswordが失われているなど、POSTデータが存在しない場合や、条件が合わない場合は、falseが返されます。
 ちなみに、条件が合った場合は、_findUser()関数が返す、配列を返すようです。

では、_findUser()関数で何が呼ばれているのでしょうか?タグジャンプをしてみると、以下のメソッドに呼びます。

    protected function _findUser($username, $password = null)
    {
        $result = $this->_query($username)->first();

        if (empty($result)) {
            return false;
        }

        $passwordField = $this->_config['fields']['password'];
        if ($password !== null) {
            $hasher = $this->passwordHasher();
            $hashedPassword = $result->get($passwordField);
            if (!$hasher->check($password, $hashedPassword)) {
                return false;
            }

            $this->_needsPasswordRehash = $hasher->needsRehash($hashedPassword);
            $result->unsetProperty($passwordField);
        }
        $hidden = $result->getHidden();
        if ($password === null && in_array($passwordField, $hidden)) {
            $key = array_search($passwordField, $hidden);
            unset($hidden[$key]);
            $result->setHidden($hidden);
        }

        return $result->toArray();
    }

$result = $this->_query($username)->first();

によって、指定のusernameにおいての、データベースにおいての最初の結果を取得します。

        if (empty($result)) {
            return false;
        }

によって、usernameがデータベースにセットされていなかった場合は、falseを返します。

これ以降がややこしいルーチンになっていますが、以下の説明を読んでからソース読んだ方が良さそうです。

Input passwords will be hashed even when a user doesn't exist. This helps mitigate timing attacks that are attempting to find valid usernames.

ユーザーが存在しない場合でも、パスワードはハッシュされます。これは、有効なユーザー名を発見しようと試行するタイミング攻撃を緩和します。
そう書かれていますが、ユーザーが存在しない場合は、return falseで、falseを返して、パスワードをハッシュしていないように見えます。

        $passwordField = $this->_config['fields']['password'];
        if ($password !== null) {
            $hasher = $this->passwordHasher();
            $hashedPassword = $result->get($passwordField);
            if (!$hasher->check($password, $hashedPassword)) {
                return false;
            }

            $this->_needsPasswordRehash = $hasher->needsRehash($hashedPassword);
            $result->unsetProperty($passwordField);
        }

_configからパスワードフィールドを取得し、パスワードをハッシャーにかけてチェックしています。パスワードはそのまま保存すると、漏洩した時に大変なことになるから、必ずハッシュをかけるんですね。

        $hidden = $result->getHidden();
        if ($password === null && in_array($passwordField, $hidden)) {
            $key = array_search($passwordField, $hidden);
            unset($hidden[$key]);
            $result->setHidden($hidden);
        }

ここで、\$passwordがnullであるなら、hiddenのパスワードフィールドを取り出し、\$hiddenを何かにセットしているようですが、ここはよくわからないですね。
では、続き読んでいきます。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1