今回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.userModel
とconfig.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を何かにセットしているようですが、ここはよくわからないですね。
では、続き読んでいきます。