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にする)ところがポイントです。