Как использовать MD5 с AuthComponent в CakePHP 3.9

#php #cakephp #cakephp-3.0

#php #cakephp #cakephp-3.0

Вопрос:

Я работаю над переносом веб-приложения, разработанного с использованием CakePHP 1.2, я переношу его на версию 3.9.4. Все пароли пользователей в базе данных хэшируются с использованием MD5, но версия 3.9.4 использует bcrypt в качестве хэшера по умолчанию. Я хочу использовать MD5, чтобы я мог аутентифицировать пользователей с помощью MD5. Я просмотрел эту статью и использовал класс LegacyPasswordHasher, как указано в статье, но он не работает. Вот как я настроил AuthComponent в AppController.

 $this->loadComponent('Auth', [
            'authenticate' => ['Form' => [
                'passwordHasher' => [
                    'className' => 'Legacy',
                ],
                'fields' => [
                    'username' => 'email',
                    'password' => 'password',
                ],
            ]],
            'loginAction' => [
                'controller' => 'Users',
                'action' => 'login',
            ],
        ]);
 

И вот мой класс LegcayPasswordHasher:

 <?php

namespace AppAuth;

use CakeAuthAbstractPasswordHasher;

class LegacyPasswordHasher extends AbstractPasswordHasher {
    public function hash($password) {
        return md5($password);
    }

    public function check($password, $hashedPassword) {
        $userPassword = md5($password);
        return $userPassword === $hashedPassword;
    }
}
 

Это UserAuthComponent из старой системы. Находится в app/Controller/Component .

 class UserAuthComponent extends Component {
    /**
     * This component uses following components
     *
     * @var array
     */
    var $components = array('Session', 'Cookie', 'RequestHandler', 'ControllerList');
    /**
     * configur key
     *
     * @var string
     */
    var $configureKey='User';

    function initialize(Controller $controller) {

    }

    function __construct(ComponentCollection $collection, $settings = array()) {
        parent::__construct($collection, $settings);
    }

    function startup(Controller $controller = null) {

    }
    /**
     * Called before the controller action.  You can use this method to configure and customize components
     * or perform logic that needs to happen before each controller action.
     *
     * @param object $c current controller object
     * @return void
     */
    function beforeFilter(amp;$c) {
        UsermgmtInIt($this);
        $user = $this->__getActiveUser();
        $pageRedirect = $c->Session->read('permission_error_redirect');
        $c->Session->delete('permission_error_redirect');
        $controller = $c->params['controller'];
        $action = $c->params['action'];
        $actionUrl = $controller.'/'.$action;
        $requested= (isset($c->params['requested']) amp;amp; $c->params['requested']==1) ? true : false;
        $permissionFree=array('users/login', 'users/logout', 'users/register', 'users/userVerification', 'users/forgotPassword', 'users/activatePassword', 'pages/display', 'users/accessDenied', 'users/emailVerification','users/admin_login','schools/garminApi','schools/viewActivitymy','schools/viewbonuspointupdate','students/viewclassstudentleader','students/viewclassstudentleaderexport','schools/viewpointsupdate');
        $access =str_replace(' ','',ucwords(str_replace('_',' ',$controller))).'/'.$action;
        $allControllers=$this->ControllerList->getControllerWithMethods();
        $errorPage=false;
        if (!in_array($access, $allControllers)) {
            $errorPage=true;
        }
        if(  $action == "viewActivitymy" || $action == "viewActivitymycron" || $action == "viewActivitymycronfitbit"|| $action == "viewActivityfitbit" || $action=="viewPointactivity" || $action =="viewActivitymycron_manual" || $action =="viewbonuspointupdate" || $action =="viewpointsupdate" || $action =="viewclassstudentleader" || $action =="viewclassstudentleaderexport"){
            
        }else{
        if ((empty($pageRedirect) || $actionUrl!='users/login') amp;amp; !$requested amp;amp; !in_array($actionUrl, $permissionFree) amp;amp; !$errorPage) {
            App::import("Model", "UserGroup");
            $userGroupModel = new UserGroup;
            if (!$this->isLogged()) {
                if (!$userGroupModel->isGuestAccess($controller, $action)) {
                    $c->log('permission: actionUrl-'.$actionUrl, LOG_DEBUG);
                    $c->Session->write('permission_error_redirect','/users/login');
                    $c->Session->setFlash('You need to be signed in to view this page.');
                    $cUrl = '/'.$c->params->url;
                    if(!empty($_SERVER['QUERY_STRING'])) {
                        $rUrl = $_SERVER['REQUEST_URI'];
                        $pos =strpos($rUrl, $cUrl);
                        $cUrl=substr($rUrl, $pos, strlen($rUrl));
                    }
                    $c->Session->write('OriginAfterLogin', $cUrl);
                    $c->redirect('/login');
                }
            } else {
                if (!$userGroupModel->isUserGroupAccess($controller, $action, $this->getGroupId())) {
                    $c->log('permission: actionUrl-'.$actionUrl, LOG_DEBUG);
                    $c->Session->write('permission_error_redirect','/users/login');
                     $c->redirect('/accessDenied');
                }
            }
        }
        }
    }
    /**
     * Used to check whether user is logged in or not
     *
     * @access public
     * @return boolean
     */
    public function isLogged() {
        return ($this->getUserId() !== null);
    }
    /**
     * Used to get user from session
     *
     * @access public
     * @return array
     */
    public function getUser() {
        return $this->Session->read('UserAuth');
    }
    /**
     * Used to get user id from session
     *
     * @access public
     * @return integer
     */
    public function getUserId() {
        return $this->Session->read('UserAuth.User.id');
    }
    /**
     * Used to get group id from session
     *
     * @access public
     * @return integer
     */
    public function getGroupId() {
        return $this->Session->read('UserAuth.User.user_group_id');
    }
    /**
     * Used to get group name from session
     *
     * @access public
     * @return string
     */
    public function getGroupName() {
        return $this->Session->read('UserAuth.UserGroup.name');
    }
    /**
     * Used to check is admin logged in
     *
     * @access public
     * @return string
     */
    public function isAdmin() {
        $groupId = $this->Session->read('UserAuth.User.user_group_id');
        if($groupId==ADMIN_GROUP_ID) {
            return true;
        }
        return false;
    }
    /**
     * Used to check is guest logged in
     *
     * @access public
     * @return string
     */
    public function isGuest() {
        $groupId = $this->Session->read('UserAuth.User.user_group_id');
        if(empty($groupId)) {
            return true;
        }
        return false;
    }
    /**
     * Used to make password in hash format
     *
     * @access public
     * @param string $pass password of user
     * @return hash
     */
    public function makePassword($pass, $salt=null) {
        return md5(md5($pass).md5($salt));
    }
    /**
     * Used to make salt in hash format
     *
     * @access public
     * @return hash
     */
    public function makeSalt() {
        $rand = mt_rand(0, 32);
        $salt = md5($rand . time());
        return $salt;
    }
    /**
     * Used to maintain login session of user
     *
     * @access public
     * @param mixed $type possible values 'guest', 'cookie', user array
     * @param string $credentials credentials of cookie, default null
     * @return array
     */
    public function login($type = 'guest', $credentials = null) {
        $user=array();
        if (is_string($type) amp;amp; ($type=='guest' || $type=='cookie')) {
            App::import("Model", "User");
            $userModel = new User;
            $user = $userModel->authsomeLogin($type, $credentials);
        } elseif (is_array($type)) {
            $user =$type;
        }
        Configure::write($this->configureKey, $user);
        $this->Session->write('UserAuth', $user);
        return $user;
    }
    /**
     * Used to delete user session and cookie
     *
     * @access public
     * @return void
     */
    public function logout() {
        $this->Session->delete('UserAuth');
        Configure::write($this->configureKey, array());
        $this->Cookie->delete(LOGIN_COOKIE_NAME);
    }
    /**
     * Used to persist cookie for remember me functionality
     *
     * @access public
     * @param string $duration duration of cookie life time on user's machine
     * @return boolean
     */
    public function persist($duration = '2 weeks') {
        App::import("Model", "User");
        $userModel = new User;
        $token = $userModel->authsomePersist($this->getUserId(), $duration);
        $token = $token.':'.$duration;
        return $this->Cookie->write(
            LOGIN_COOKIE_NAME,
            $token,
            true, // encrypt = true
            $duration
        );
    }
    /**
     * Used to check user's session if user's session is not available then it tries to get login from cookie if it exist
     *
     * @access private
     * @return array
     */
    private function __getActiveUser() {
        $user = Configure::read($this->configureKey);
        if (!empty($user)) {
            return $user;
        }

        $this->__useSession() || $this->__useCookieToken() || $this->__useGuestAccount();

        $user = Configure::read($this->configureKey);
        if (is_null($user)) {
            throw new Exception(
                'Unable to initilize user'
            );
        }
        return $user;
    }
    /**
     * Used to get user from session
     *
     * @access private
     * @return boolean
     */
    private function __useSession() {
        $user = $this->getUser();
        if (!$user) {
            return false;
        }
        Configure::write($this->configureKey, $user);
        return true;
    }
    /**
     * Used to get login from cookie
     *
     * @access private
     * @return boolean
     */
    private function __useCookieToken() {
        $token = $this->Cookie->read(LOGIN_COOKIE_NAME);
        if (!$token) {
            return false;
        }
        $user=false;
        // Extract the duration appendix from the token
        if(strpos($token, ":") !==false) {
            $tokenParts = split(':', $token);
            $duration = array_pop($tokenParts);
            $token = join(':', $tokenParts);
            $user = $this->login('cookie', compact('token', 'duration'));
            // Delete the cookie once its been used
        }
        $this->Cookie->delete(LOGIN_COOKIE_NAME);
        if (!$user) {
            return;
        }
        $this->persist($duration);
        return (bool)$user;
    }
    /**
     * Used to get login as guest
     *
     * @access private
     * @return array
     */
    private function __useGuestAccount() {
        return $this->login('guest');
    }
}
 

И это User Модель app/Mode/ , расположенная в старой кодовой базе.

 App::uses('AppModel', 'Model');
App::uses('CakeEmail', 'Network/Email');

class User extends AppModel {

    /**
     * This model belongs to following models
     *
     * @var array
     */
    var $belongsTo = array('UserGroup');
    /**
     * This model has following models
     *
     * @var array
     */
    var $hasMany = array('LoginToken'=>array('className'=>'LoginToken','limit' =>1));
    /**
     * model validation array
     *
     * @var array
     */
    var $validate = array();
    /**
     * UsetAuth component object
     *
     * @var object
     */
    var $userAuth;
    /**
     * model validation array
     *
     * @var array
     */
    function LoginValidate() {
        $validate1 = array(
                'email'=> array(
                    'mustNotEmpty'=>array(
                        'rule' => 'notEmpty',
                        'message'=> 'Please enter email or username')
                    ),
                'password'=>array(
                    'mustNotEmpty'=>array(
                        'rule' => 'notEmpty',
                        'message'=> 'Please enter password')
                    )
            );
        $this->validate=$validate1;
        return $this->validates();
    }
    /**
     * model validation array
     *
     * @var array
     */
    function RegisterValidate() {
        $validate1 = array(
                //"user_group_id" => array(
                    //'rule' => array('comparison', '!=', 0),
                    //'message'=> 'Please select group'),
                'school_name'=> array(
                    'mustNotEmpty'=>array(
                        'rule' => 'notEmpty',
                        'message'=> 'Please enter school name',
                        'last'=>true),
                    'mustUnique'=>array(
                        'rule' =>'isUnique',
                        'message' =>'This school name already taken',
                    'last'=>true),
                    // 'mustBeLonger'=>array(
                    //  'rule' => array('minLength', 4),
                    //  'message'=> 'Username must be greater than 3 characters',
                    //  'last'=>true),
                    ),
                'first_name'=> array(
                    'mustNotEmpty'=>array(
                        'rule' => 'notEmpty',
                        'message'=> 'Please enter administrator first name')
                    ),
                'last_name'=> array(
                    'mustNotEmpty'=>array(
                        'rule' => 'notEmpty',
                        'on' => 'create',
                        'message'=> 'Please enter administrator last name')
                    ),
                'email'=> array(
                    'mustNotEmpty'=>array(
                        'rule' => 'notEmpty',
                        'message'=> 'Please enter email',
                        'last'=>true),
                    'mustBeEmail'=> array(
                        'rule' => array('email'),
                        'message' => 'Please enter valid email',
                        'last'=>true),
                    'mustUnique'=>array(
                        'rule' =>'isUnique',
                        'message' =>'This email is already registered',
                        )
                    ),
                'oldpassword'=>array(
                    'mustNotEmpty'=>array(
                        'rule' => 'notEmpty',
                        'message'=> 'Please enter old password',
                        'last'=>true),
                    'mustMatch'=>array(
                        'rule' => array('verifyOldPass'),
                        'message' => 'Please enter correct old password'),
                    ),
                'password'=>array(
                    'mustNotEmpty'=>array(
                        'rule' => 'notEmpty',
                        'message'=> 'Please enter password',
                        'on' => 'create',
                        'last'=>true),
                    'mustBeLonger'=>array(
                        'rule' => array('minLength', 6),
                        'message'=> 'Password must be greater than 5 characters',
                        'on' => 'create',
                        'last'=>true),
                    'mustMatch'=>array(
                        'rule' => array('verifies'),
                        'message' => 'Both passwords must match'),
                        //'on' => 'create'
                    ),
                'number_of_student'=> array(
                    'mustNotEmpty'=>array(
                        'rule' => 'notEmpty',
                        'message'=> 'Please enter number of student')
                    ),
                'tracker'=> array(
                    'mustNotEmpty'=>array(
                        'rule' => 'notEmpty',
                        'message'=> 'Please enter tracker number')
                    ),
                'captcha'=>array(
                    'mustMatch'=>array(
                        'rule' => array('recaptchaValidate'),
                        'message' => ''),
                    )
            );
        $this->validate=$validate1;
        return $this->validates();
    }
    /**
     * Used to validate captcha
     *
     * @access public
     * @return boolean
     */
    public function recaptchaValidate() {
        App::import("Vendor", "recaptcha/recaptchalib");
        $recaptcha_challenge_field = (isset($_POST['recaptcha_challenge_field'])) ? $_POST['recaptcha_challenge_field'] : "";
        $recaptcha_response_field = (isset($_POST['recaptcha_response_field'])) ? $_POST['recaptcha_response_field'] : "";
        $resp = recaptcha_check_answer(PRIVATE_KEY_FROM_RECAPTCHA, $_SERVER['REMOTE_ADDR'], $recaptcha_challenge_field, $recaptcha_response_field);
        $error = $resp->error;
        if(!$resp->is_valid) {
            $this->validationErrors['captcha'][0]=$error;
        }
        return true;
    }
    /**
     * Used to match passwords
     *
     * @access public
     * @return boolean
     */
    public function verifies() {
        return ($this->data['User']['password']===$this->data['User']['cpassword']);
    }
    /**
     * Used to match old password
     *
     * @access public
     * @return boolean
     */
    public function verifyOldPass() {
        $userId = $this->userAuth->getUserId();
        $user = $this->findById($userId);
        $oldpass=$this->userAuth->makePassword($this->data['User']['oldpassword'], $user['User']['salt']);
        return ($user['User']['password']===$oldpass);
    }
    /**
     * Used to send registration mail to user
     *
     * @access public
     * @param array $user user detail array
     * @return void
     */
    public function sendRegistrationMail($user) {
    
        // $user['User']['email'];
        // send email to newly created user
        $email = new CakeEmail();
        $userId=$user['User']['id'];
        $email->config('smtp');
        $fromConfig = 'pradeep.singh@udaantechnologies.com';
        $fromNameConfig = 'Pradeep';
        $email->from(array( $fromConfig => $fromNameConfig));
        $email->sender(array( $fromConfig => $fromNameConfig));
        $email->to($user['User']['email']);
        $email->subject('Your registration is complete');
        //$email->transport('Debug');
        $body="Welcome ".$user['User']['first_name'].", Thank you for your registration on ".'http://localhost/garmin/register'." nn Thanks,n".EMAIL_FROM_NAME;
        try{
            $result = $email->send($body);
        } catch (Exception $ex) {
            // we could not send the email, ignore it
            $result="Could not send registration email to userid-".$userId;
        }
        $this->log($result, LOG_DEBUG);
    }
    /**
     * Used to send email verification mail to user
     *
     * @access public
     * @param array $user user detail array
     * @return void
     */
    public function sendVerificationMail($user) {
        $userId=$user['User']['id'];
        $email = new CakeEmail();
        $email->config('smtp');
        $fromConfig = 'pradeep.singh@udaantechnologies.com';
        $fromNameConfig = 'Pradeep';
        $email->from(array( $fromConfig => $fromNameConfig));
        $email->sender(array( $fromConfig => $fromNameConfig));
        $email->to($user['User']['email']);
        $email->subject('Email Verification Mail');
        $activate_key = $this->getActivationKey($user['User']['password']);
        $link = Router::url("/userVerification?ident=$userIdamp;activate=$activate_key",true);
        $body="Hi, Click the link below to complete your registration nn ".$link;
        try{
            $result = $email->send($body);
        } catch (Exception $ex){
            // we could not send the email, ignore it
            $result="Could not send verification email to userid-".$userId;
        }
        $this->log($result, LOG_DEBUG);
    }
    /**
     * Used to generate activation key
     *
     * @access public
     * @param string $password user password
     * @return hash
     */
    public function getActivationKey($password) {
        $salt = Configure::read ( "Security.salt" );
        return md5(md5($password).$salt);
    }
    /**
     * Used to send forgot password mail to user
     *
     * @access public
     * @param array $user user detail
     * @return void
     */
    public function forgotPassword($user) {
        $userId=$user['User']['id'];
        $email = new CakeEmail();
        $fromConfig = EMAIL_FROM_ADDRESS;
        $fromNameConfig = EMAIL_FROM_NAME;
        $email->from(array( $fromConfig => $fromNameConfig));
        $email->sender(array( $fromConfig => $fromNameConfig));
        $email->to($user['User']['email']);
        $email->subject(EMAIL_FROM_NAME.': Request to Reset Your Password');
        $activate_key = $this->getActivationKey($user['User']['password']);
        $link = Router::url("/activatePassword?ident=$userIdamp;activate=$activate_key",true);
        $body= "Welcome ".$user['User']['first_name'].", let's help you get signed in

You have requested to have your password reset on ".EMAIL_FROM_NAME.". Please click the link below to reset your password now :

".$link."


If above link does not work please copy and paste the URL link (above) into your browser address bar to get to the Page to reset password

Choose a password you can remember and please keep it secure.

Thanks,n".

EMAIL_FROM_NAME;
        try{
            $result = $email->send($body);
        } catch (Exception $ex){
            // we could not send the email, ignore it
            $result="Could not send forgot password email to userid-".$userId;
        }
        $this->log($result, LOG_DEBUG);
    }
    /**
     * Used to mark cookie used
     *
     * @access public
     * @param string $type
     * @param string $credentials
     * @return array
     */
    public function authsomeLogin($type, $credentials = array()) {
        switch ($type) {
            case 'guest':
                // You can return any non-null value here, if you don't
                // have a guest account, just return an empty array
                return array();
            case 'cookie':
                $loginToken=false;
                if(strpos($credentials['token'], ":") !==false) {
                    list($token, $userId) = split(':', $credentials['token']);
                    $duration = $credentials['duration'];

                    $loginToken = $this->LoginToken->find('first', array(
                        'conditions' => array(
                            'user_id' => $userId,
                            'token' => $token,
                            'duration' => $duration,
                            'used' => false,
                            'expires <=' => date('Y-m-d H:i:s', strtotime($duration)),
                        ),
                        'contain' => false
                    ));
                }
                if (!$loginToken) {
                    return false;
                }
                $loginToken['LoginToken']['used'] = true;
                $this->LoginToken->save($loginToken);

                $conditions = array(
                    'User.id' => $loginToken['LoginToken']['user_id']
                );
            break;
            default:
                return array();
        }
        return $this->find('first', compact('conditions'));
    }
    /**
     * Used to generate cookie token
     *
     * @access public
     * @param integer $userId user id
     * @param string $duration cookie persist life time
     * @return string
     */
    public function authsomePersist($userId, $duration) {
        $token = md5(uniqid(mt_rand(), true));
        $this->LoginToken->create(array(
            'user_id' => $userId,
            'token' => $token,
            'duration' => $duration,
            'expires' => date('Y-m-d H:i:s', strtotime($duration)),
        ));
        $this->LoginToken->save();
        return "${token}:${userId}";
    }
    /**
     * Used to get name by user id
     *
     * @access public
     * @param integer $userId user id
     * @return string
     */
    public function getNameById($userId) {
        $res = $this->findById($userId);
        $name=(!empty($res)) ? ($res['User']['first_name'].' '.$res['User']['last_name']) : '';
        return $name;
    }
    /**
     * Used to check users by group id
     *
     * @access public
     * @param integer $groupId user id
     * @return boolean
     */
    public function isUserAssociatedWithGroup($groupId) {
        $res = $this->find('count', array('conditions'=>array('User.user_group_id'=>$groupId)));
        if(!empty($res)) {
            return true;
        }
        return false;
    }
}
 

И это app/Controller пользовательский контроллер, расположенный в старой кодовой базе.

С нетерпением жду ваших указаний. Заранее спасибо.

Комментарии:

1. Помимо того факта, что вам действительно больше не следует использовать устаревший компонент аутентификации, и что для устаревших алгоритмов хеширования, которые не привязаны к устаревшим приложениям, вам следует использовать запасной хэшер для медленного переноса хэшей в безопасный алгоритм … что означает » не работает «? Что именно происходит, и что именно вы ожидаете, что произойдет вместо этого?

2. @ndm Я знаю, что не должен использовать устаревшие алгоритмы хеширования, но пока я придерживаюсь его использования, поскольку база данных содержит пользователей с хэшированным паролем md5. Я перенесу его на алгоритм хеширования по умолчанию, но сейчас мне нужно, чтобы он работал с MD5. «не работает» означает, что согласно документации я использую класс LegacyPasswordHasher, но он не аутентифицирует пользователей и не регистрирует их.

3. Какую настройку хеширования использует старое приложение? Устанавливает ли он явно MD5 в качестве алгоритма по умолчанию? Использует ли он какой-либо пользовательский код аутентификации? И вы на 100% уверены, что в базе данных есть простые хэши MD5? Даже в 1.2 по умолчанию использовался соленый хэш, и sha1 и sha256 были предпочтительнее md5 .

4. @ndm … Я считаю, что он использовал хэши md5 и использовал UserAuthComponent … вот изображение базы данных ibb.co/XzFQ8JH

5. Ну, это, конечно, похоже на соли пользователя / строки (и md5). Вы должны выяснить, так ли это, т.Е. Проверить, Что UserAuthComponent происходит с солями, и посмотреть, нужно ли вам / можете ли вы реплицировать его в своем приложении — с помощью хэшеров вам, скорее всего, придется обновлять базу данных, например, добавляя соль к паролю, чтобы у хэшера был доступ к обоими может соответствующим образом генерировать хэш для сравнения.