cakephp パスワードリマインダー機能の実装

cakephpでパスワードリマインダー機能を追加しました。あまり探してなかったので、フローとメモを記しておきます。

準備:

・Controller

UsersController.php

・Model

User.php

EmailSend.php

PasswordRemind.php

・View

forgot_password.ctp

reset_password.ctp

=========================================

流れ

①【forgot_password.ctp】 

*Controllerに渡すデータ値 

ユーザーネーム(メールアドレス)

これらをFormヘルパー:createの’action’ => ‘forgot_password’,で渡しています

================================================

②【UsersController.php】

*あるユーザーネームだけ取得します

function forgot_password(){

// フォームからポストされたデータがあるかどうか
if ($this->request->is(‘post’)) {
// 登録されているメールアドレスが存在するかどうか
$username = !empty($this->request->data[‘User’][‘username’])?$this->request->data[‘User’][‘username’]:”;  
$user_id = $this->User->userNameExists($username);  ・・・・・①

if(!empty($user_id)){ ・・・・②
// 存在すればURLを作成し、メール送信し、リセットパスワードを送信
//アクチベーション用のURLを作成
$url =
DS . strtolower($this->name) . // コントローラ
DS . ‘reset_password’ . // アクション
DS . $this->User->getPasswordRemindHash($user_id);
$url = Router::url( $url, true);
$this->EmailSend->sendPasswordRemindMail($username, $url, ‘forgot_password’);

$this->Session->setFlash(__(‘入力されたメールアドレス宛にパスワードリセットURLが記載されたメールを送信しました。メールを確認し記載されたURLにアクセスしてパスワードを新しいパスワードに更新してください。’));
return $this->redirect(array(‘controller’ => ‘users’,’action’ => ‘forgot_password’ ));
}else{
// 存在しなければ
$this->Session->setFlash(__(‘このメールアドレスは登録されていません。’));
}
}

=========================================================

①はModelのUser.phpにて、ユーザーIDでユーザー名(メールアドレス)が存在するかどうかを取得しています。

public function userNameExists($username) {
$result = $this->find(‘first’, array(
‘fields’ =>array(
$this->alias.’.id’,
),
‘conditions’ => array(
$this->alias.’.username’ => $username,
$this->alias.’.retired’ => false, ・・・・これは削除されていないよって意味だよ。削除されているユーザーを取得することはないのでここで指定しているんだよ。
),
‘recursive’ => -1, //Userのみ取得する
));
// 存在すればIDを返し、存在しなければfalseを返す
return !empty($result[‘User’][‘id’])?$result[‘User’][‘id’]:false;
}

*find関数 

*三項演算子 (条件式)?trueの時:falseの時;

===========================================================

【ここ重要!】

②はModel/User.phpのgetPasswordRemindHashアクションで

始めにユーザーネーム(メールアドレス)を取得し、次にハッシュ値を生成し、

そのユーザーネームに対するURLにハッシュをくっつけてます。

$this->log(“———-function getPasswordRemindHash–“.__FILE__.__LINE__.”\n”);
// ユーザIDの有無確認
if ($this->exists($user_id)) {
// 更新日時を取得しハッシュ化
$result = $this->find(‘first’, array(
‘conditions’ => array(
‘User.id’ => $user_id,
‘User.retired’ => false,
),
‘fields’ => array(
‘User.username’,
),
‘contain’ => array(),
‘recursive’ => -1, //Userのみ取得する
));
//modifiedによりハッシュを作成する。
$hash = $this->PasswordRemind->makeHash( $result[‘User’][‘username’]);
$this->PasswordRemind->saveHash($user_id, $hash);
return $hash;
}else{
//ユーザーが存在しなければfalse
return false;
}

====================================================

Model/PasswordRemind.php にて

public function makeHash($email = ”) {
//現在時刻とemailによりハッシュを作成する。
return Security::hash( $email . date(‘Y-m-d H:i:s’), ‘sha1’, true);
}

public function saveHash($user_id, $hash) {
$this->create();
$savedata = array(
‘user_id’ => $user_id,
‘hash’ => $hash,
‘created’ => date(‘Y-m-d H:i:s’), // 一応明示的に現在時刻をいれてあげる
);
return $this->save($savedata);
}

========================================================-

さあ、ここでEmailを送るのだが、いくつか注意点があります。(てか、いくつかエラーがでた)

ここではModel/EmailSend.php でメール送信を行っていますが、

まず、ソースは

function sendPasswordRemindMail($Email, $url, $template = null){

// メール送信

$email = new CakeEmail(‘password_remind’);
try {
$email->config(‘password_remind’);
// メーラー情報の削除 cake phpで作成されたことがバレバレになってしまうため。
$email->addHeaders(array(‘X-Mailer’ => false));
$email->emailPattern(‘/^[-+.\\w]+@[-a-z0-9]+(\\.[-a-z0-9]+)*\\.[a-z]{2,6}$/i’);
$email->to(array($Email));
$email->viewVars(compact(‘Email’));
$email->viewVars(compact(‘url’));
$email->template(‘refresh_password’, ‘default’);
$email->subject(Configure::read(‘MAIL_SUBJECT_HEADER’).Configure::read(‘MAIL_SUBJECT.password_remind’));

$result = $email->send();
$this->log(‘登録メール送信’.__FILE__.__LINE__);
} catch(Exception $e) {
try {
// “”で囲む処理を入れる
$arr = explode(‘@’,$Email);
$arr[0] = ‘”‘ .$arr[0] .'”‘ ;
$mail = $arr[0].’@’.$arr[1];

debug($e->getMessage());
$email->config(‘password_remind’);
// メーラー情報の削除 cake phpで作成されたことがバレバレになってしまうため。
$email->addHeaders(array(‘X-Mailer’ => false));
// “を許可する
$email->emailPattern(‘/^[-“+.\\w]+@[-a-z0-9]+(\\.[-a-z0-9]+)*\\.[a-z]{2,6}$/i’);
# $email->to(array($request_data[‘User’][‘username’]));
$email->to(array($mail));
$email->viewVars(compact(‘Email’));
$email->viewVars(compact(‘url’));

$email->template(‘refresh_password’, ‘default’);
$email->subject(Configure::read(‘MAIL_SUBJECT_HEADER’).Configure::read(‘MAIL_SUBJECT.password_remind’));

$result = $email->send();
$this->log(‘登録メール送信’.__FILE__.__LINE__);
} catch(Exception $e) {
debug($e->getMessage());
}
}
}

ですが、テンプレート指定や送信内容(POSTとかホストとか・・)は調べれば出てきます。

私がエラーで困っていた場所は、メールサーバーの部分で起きていた。

メールシステムの簡単なおさらい

 
どうやら開発で混乱している? ようなので……
 

ポート番号??

 
ポート番号ってのはTCP/IPに出てくる仕組みの一つで、
通信相手としてIPアドレスだけじゃなくてポート番号も指定することで
通信相手(プログラム)を特定する。
電話にたとえると家に掛けて『○○さんお願いします』と言うようなもの。
 
メールの送信(配送)に関係するポートはこの3つ
 
25 SMTP 通常のメール配送に使うポート メールサーバー間のメールのやり取りは今でもここが基本
587 submission SPAM対策で25番のかわりに使われるポート PCからサーバーにメールを渡すとき使う 通常は暗号化されないがオプションで暗号化も可能(STARTTLS)
465 SMTP over SSL 暗号化したSMTP通信を行うときのポート PCからサーバーにメールを渡すとき使う 常に暗号化される
 

サーバーどこ?

 
メールサーバー mail.parafamily.net
パラファミリー顧客の使うメール機能を一手に引き受けるサーバー
ポートは 25 587 465 が空いている
25 このサーバーで管理するメールアドレス宛のメールのみ受け取る
(ただしサーバー内部から接続して送信すると、外部サーバーへもメールを出せる)
465 SMTP認証が通った場合のみメールを受け取る
587 暗号化(STARTTLS)利用可能、SMTP認証が通った場合のみメールを受け取る
 
それ以外のサーバー(cs1とか)
これらのサーバーでもSMTPは動作しているが、送信専用である
25 サーバー内部からしか接続を受け付けない 外部サーバーへもメールを出せる
 
というわけで、WebシステムからSMTPでメールを出したい場合は、
localhost:25 へ接続してメールを出すのがよい。
(メールサーバーを経由しようとすると認証が必要なため面倒)

ここは処理とかじゃなく、メールサーバーも関係してくるので注意して欲しい。(泣)

========================================================

さて、ここで第一段階がおわりました。第二段階は送られたURLがリンクできるか、

パスワードと確認用パスワードが一致し、登録できるかの処理をいたします。

=========================================================

【UserController.php】 function reset_password(){}

// ハッシュのチェック ただしまだ完了フラグ(falseにする)は立てない
//ハッシュ文字列の情報があったら、Formから取得する
if(empty($in_hash) and !empty($this->request->data[‘User’][‘in_hash’])) {
$in_hash = $this->request->data[‘User’][‘in_hash’];
}
$user_id = $this->User->PasswordRemind->checkHash($in_hash, null, false); ・・・①
//$user_id = $this->User->getUserName($user_id);
if (empty($user_id)) {
throw new NotFoundException(__(‘Invalid Page’));
}
//↑↑↑↑Postデータが渡されるのは、パスワードと確認用パスワードなので、user_idは先に取得しておく必要がある。
// ポストデータがあれば保存をする(保存ボタンが押された場合以下の処理がはしる)
if ($this->request->is(‘post’)) {

$password = null;
$password_confirm = null;
if(!empty($this->request->data[‘User’][‘password’])) {
$password = $this->request->data[‘User’][‘password’];
}
if(!empty($this->request->data[‘User’][‘password_confirm’])) {
$password_confirm = $this->request->data[‘User’][‘password_confirm’];
}
$save = array(
‘id’ => $user_id,
‘password’ => $password,
‘password_confirm’ => $password_confirm,
);

$fieldList = array(
‘id’,
‘password’,
‘password_confirm’,
);

$this->User->id = $user_id;
//保存する
if($this->User->save($save,true,$fieldList)){
//保存成功したらハッシュを削除させるためにチェックを回す
$this->User->PasswordRemind->checkHash($in_hash);
$this->Session->setFlash(__(‘パスワードの更新に成功しました’));
// パスワード変更したんだから、ログイン画面がいいと思う
return $this->redirect(array(‘controller’ => ‘users’,’action’ => ‘login’));
}else{
$this->Session->setFlash(__(‘パスワードの更新に失敗しました’));
$errors = $this->User->validationErrors;

}
// これしちゃだめ→ unset($this->request->data[‘User’]);
} else {
// メールのURLからアクセスしたとき(POSTではない)
$this->request->data[‘User’][‘in_hash’] = $in_hash;
// 画面に$this->Form->hidden(‘in_hash’);を作る。
}
}

①ではcheckHashアクションで

================================================================

public function checkHash($hash, $time = null, $checking = true) {
if(empty($time)){
// 指定された時間がなければ
// 30分前の時間を取得
$time = date(‘Y-m-d H:i:s’, strtotime(date(‘Y-m-d H:i:s’) . ‘ -30minute’));
}
$hash = $this->find(‘first’, array(
‘fields’ => array(
‘PasswordRemind.id’,
‘PasswordRemind.user_id’,
),
‘conditions’ => array(
‘PasswordRemind.hash’ => $hash,
‘PasswordRemind.deleted’ => false,
‘PasswordRemind.created >’ => $time, //指定された時間よりも新しいhashの中で存在するかどうかのチェック
),
‘contain’ => array(),
));
// 存在すればUser_idを返し、存在しなければfalseを返す
if(!empty($hash[‘PasswordRemind’][‘id’])){
if($checking){
// checkingがtrueならば、同時に削除フラグを立てる
$savedata = array(
‘id’ => $hash[‘PasswordRemind’][‘id’],
‘deleted’ => true,
);
$this->save($savedata);
}
return $hash[‘PasswordRemind’][‘user_id’];
}else{
return false;
}
}

=============================================================

以上になります。第二段階のポイントはsave();ではなく、saveFields();関数で、

必要なデータやカラムを取得しているところとcheckHashアクションで保存前は、

削除フラグを立てず、保存した後に削除フラグをたてる(始めはfalseで、保存時にtrueにする)ところがポイントです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください