当电子邮件和密码保存在不同的表中时,如何验证用户(基于cakephp)

3
一个表单接受电子邮件和密码。
<?= $this->Form->create() ?>
<?= $this->Form->control('email') ?>
<?= $this->Form->control('password') ?>
<?= $this->Form->button('Login') ?>
<?= $this->Form->end() ?>

电子邮件地址存储为用户ID,密码存储在密码表中。 地址是电子邮件表中存储实际电子邮件地址的属性。 密码存储在pw中的位置。
认证组件接收地址作为参数。
$this->loadComponent('Auth', [
        'authenticate' => [
            'Form' => [
                'fields' => [
                    //here we define what is compared to be authenticated
                    'username' => 'address',    
                    'password' => 'password'
                ]
            ]...

登录功能与普通相同:

public function login()
{
    if ($this->request->is('post')) {

        //PUT IN STUFF HERE
        $user = $this->Auth->identify();


        if ($user) {
            $user->last_login = Time::now();//dont put this above uif statement, will automatically create a default object
            $this->Auth->setUser($user);
            $this->Flash->success('You have been successfully logged in.');
            $this->log("Login success", 'debug');
          //redirect after login
            return $this->redirect($this->Auth->redirectUrl('/users/index'));
        }
        $this->Flash->error('Your username or password is incorrect.');
        $this->log("Login FAILURE", 'debug');
    }
}`

我认为,我们可以比较电子邮件地址,或者直接让表单查看相关类的“地址”属性。那么如何将身份验证指向另一个表中的属性呢?
谢谢。

为什么你使用地址作为用户名而不是电子邮件?另外,我的理解是你的电子邮件和密码存储在不同的表中,对吗? - Jason Joslin
有一个单独的电子邮件表。因此,在电子邮件表中,存在一个ID、地址和创建日期。密码存储在用户表中,电子邮件作为地址存储在电子邮件表中,并与电子邮件ID相关联(因为CakePHP需要这样)。 - mewc
2个回答

3
你需要创建自定义身份验证对象来实现这个功能。
在加载组件时。
$this->loadComponent('Auth', [
        'authenticate' => [
            'CustomForm' => [
                'fields' => [
                    'username' => 'address',// Field in your emails table
                    'password' => 'password',// Field in your users table
                    'myAssoc'=>'Users'// Custom Filed to get association
                ],
                'userModel' => 'Emails'
            ]...

在/src/Auth/文件夹中创建一个名为CustomFormAuthenticate.php的文件。
<?php

namespace App\Auth;

use Cake\Auth\FormAuthenticate;
use Cake\Utility\Inflector;

class CustomFormAuthenticate extends FormAuthenticate
{
    public function _findUser($username, $password = null)
    {
        $result = $this->_query($username);
        $myAssoc = false;
        if (!empty($this->_config['fields']['myAssoc'])) {
            $myAssoc = $this->_config['fields']['myAssoc'];
            $result->contain([$myAssoc]);
        }

        $result = $result->first();

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

        if ($password !== null) {
            $hasher = $this->passwordHasher();
            if($myAssoc !== false){
                $hashedPassword = $result->{Inflector::underscore(Inflector::singularize($myAssoc))}[$this->_config['fields']['password']];
            } else {
                $hashedPassword = $result->get($this->_config['fields']['password']);
            }

            if (!$hasher->check($password, $hashedPassword)) {
                return false;
            }

            $this->_needsPasswordRehash = $hasher->needsRehash($hashedPassword);
            $result->unsetProperty($this->_config['fields']['password']);
        }
        debug($result);
        return $result->toArray();
    }

}

请确保您在EmailTable.php中将模型Users与电子邮件关联。
$this->hasOne('Users', [
    'foreignKey' => 'email_id'
]);

在您的登录页面中
<?= $this->Form->create() ?>
<?= $this->Form->control('address') ?> // Field in your email table
<?= $this->Form->control('password') ?>// Field in your users table
<?= $this->Form->button('Login') ?>
<?= $this->Form->end() ?>

我已经测试过,对我来说可以正常工作。


使用包含/连接用户表的自定义查找器,并将密码字段设置为计算列或虚拟字段,难道不会更容易吗? - ndm
@ndm 它仍需要许多改进,我也是 :) - Aman Rawat

1
我建议使用一种不那么侵入式的方式,即使用包含/连接用户表的自定义查找器,并使用别名在主查询中设置password字段,或者在主实体作为虚拟字段中设置,这样内置的认证器就可以检索所需的数据,这是认证器所关心的所有内容。
例如,在您的EmailsTable类中添加一个像这样的查找器,它选择password字段的正确值:
public function findAuth(\Cake\ORM\Query $query, array $options)
{
    return
        $this
            ->find()
            ->select([
                'Emails.id',
                'Emails.address', // you may want to alias this one too
                'password' => 'Users.password'
            ])
            ->leftJoinWith('Users')
            ->where([
                // the options is always named `username`, this is
                // not affected by the `fields` configuration
                'Emails.address' => $options['username']
            ]);
}

使用这样的查找器,你只需要配置认证组件的fieldsuserModelfinder选项即可,例如:
$this->loadComponent('Auth', [
    'authenticate' => [
        'Form' => [
            'fields' => [
                // set the field to `email`, just like in your form
                'username' => 'email'
            ],
            'userModel' => 'Emails',
            'finder' => 'auth'
        ]
        // ...
    ]
]);

这个示例假设Emails通过belongsTohasOne关联与Users相关联,使用join策略。
还要注意,username字段设置为email,就像您的示例表单中一样,您也可以将两者都设置为address(或任何您喜欢的东西),它不会影响查找器查询,因为它创建了一个新的查询,并使用从请求数据中提取的用户名值通过配置的字段(提取的值始终将在$options数组的username键中传递,除非finder配置是已经有一个名为username的键的数组)。
另请参阅:

非常清楚。我正在弄清楚如何通过这种方式添加用户及其电子邮件,以便进行测试 - 没有哈希数据。 - mewc
不确定您 @mewc 的意思是什么,但这听起来像是一个新问题。 - ndm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接