در این پست با یک مثال کاربردی به معرفی کامپوننت Authentication در فریمورک قدرتمند CakePHP خواهیم پرداخت. بصورت ساده یک مدل تصدیق هویت کاربر ایجاد میکنیم که برای ورود به بخش مدیریت، کاربر میباید وارد سیستم شود. در پست بعدی در مورد تصدیق هویت در کیک، با اضافه کردن دو کامپوننت ACL و Cookie سیستم پایدارتری ایجاد خواهیم کرد.
در این آموزش فرض شده است اصول کار با کیک را فراگرفتهاید. میتوانید کیک را نصب کنید، تنظیمات اولیه را مقداردهی کنید و Cake Bake (پختن کیک) را با کنسول انجام دهید و … اگر آماده نیستید به سری آموزشهای قبل (ساخت لینکدونی با CakePHP) رجوع کنید و بعدا برگردید. ضمنا از آخرین نسخه پایدار CakePHP و PHP 5 استفاده میکنیم.
خوشبختانه نصب و بکارگیری کامپوننت توکار auth سادهترین شکل ممکن برای داشتن یک سیستم ورود کاربر است. مانند همه کامپوننتها، با افزودن ‘Auth’ به پارامتر $components کامپوننت به کنترلر شما افزوده میشود. چون قصد داریم از این کامپوننت در دیگر کنترلرها نیز استفاده کنیم برای جلوگیری از تکرار، آن را به AppController اضافه میکنیم. بنابراین فایلی بنام app_controller.php در شاخه اصلی app تان با محتوای زیر ایجاد کنید:
class AppController extends Controller { var $components = array('Auth'); }
روشن است که چون دیگر کنترلرها فرزند AppController هستند خصوصیات آن را به ارث خواهند برد. بگذارید کمی در مورد قوانین توکار این کامپوننت بگویم. بطور پیشفرض این کامپوننت انتظار دارد شما جدولی بنام users با فیلدهایی بنام username و password داشته باشید. اما در برخی موارد خاص، پایگاه داده بدلایل امنیتی اجازه نمیدهد که برای یک فیلد نام password را انتخاب کنید. مشکلی نیست ما این عرف را برهم میزنیم و از نام دلخواه خودمان استفاده میکنیم. ابتدا یک جدول بصورت زیر بسازید:
CREATE TABLE users ( id integer auto_increment, email char(50), secretpass char(50), PRIMARY KEY (id) );
از email برای نام کاربری و secretpass برای کلمه عبور استفاده کردیم. در اینجا لازم است با تابع beforeFilter در کیک آشنا شوید. CakePHP یکسری توابع callbacks توکار دارد که با آنها میتوان منطقی را بعد یا قبل اجرای یک کنش اضافه کرد. BeforeFilter قبل از هر کنشی در کنترلر اجرا میشود. اما در اینجا چه استفاده ایی دارد؟ برای برخی از تنظیمات کامپوننت Auth نیاز داریم قبل از اجرای هر کنشی آنها را مقدار دهی کنیم
اولین موردی که باید به این تابع اضافه شود اعلام تغییر نام فیلدهای جدول users است (چون قوانین توکار این کامپوننت را رعایت نکردیم) بدین منظور کد زیر را مجددا به AppController اضافه میکنیم (دلیل این کار روشن است):
function beforeFilter() { $this->Auth->fields = array('username' => 'email', 'password' => 'secretpass'); }
حال این کامپوننت میتواند با نامهای جدید فیلدها کار کند. با ساخت این جدول، کنترلر و مدل و همچنین نمای login را برای این جدول bake کنید. به کنترلر users دو کنش login و logout را بصورت زیر اضافه کنید:
function login() { //Auth componet does it automatically } function logout() { $this->redirect($this->Auth->logout()); }
این کامپوننت بطور پیشفرض مسیر users/login (کنترلری بنام users و کنشی بنام login) را برای ورود در نظر میگیرد. میتوانید با تکه کد زیر در BeforeFilter این مسیر را تغییر دهید:
$this->Auth->loginAction = array('admin' => false, 'controller' => 'members', 'action' => 'login');
در اینجا ما همان مسیر پیشفرض را بکار میگیریم. همچنین بطور پیشفرض نیازی نیست برای پردازش فرم login.ctp کنش login را کدنویسی کنید خود کامپوننت این کار را بطور خودکار انجام میدهد بنابراین آن را در بالا خالی گذاشتیم. حال فرم ورود را در login.ctp بصورت زیر تعریف کنید:
if ($session->check('Message.auth')) { $session->flash('auth'); } echo $form->create('User', array('url' => array('controller' => 'users', 'action' => 'login'))); echo $form->input('email'); echo $form->input('secretpass', array('type' => 'password')); echo $form->submit('Login'); echo $form->end();
این کامپوننت برای رمزنگاری کلمه عبور از یک کلاس امنیتی استفاده میکند که این کلاس بصورت پیشفرض از SHA1 بهره میگیرد. برای تغییر متد رمزنگاری میباید setHash را برحسب md5 ،sha1 یا sha256 در اولین پارامتر مقداردهی کنید. ما از md5 استفاده میکنیم بنابراین BeforeFilter به صورت زیر خواهد بود:
function beforeFilter() { Security::setHash('md5'); $this->Auth->fields = array('username' => 'email', 'password' => 'secretpass'); }
توجه کنید! فایل core.php و مقدار امنیتی Security.salt را یادتان هست که در ابتدای تنظیمات کیک مقداردهی میکردید؟! درست حدس زدهاید، کلاس امنیتی برای رمزنگاری بیشتر، از این مقدار نیز استفاده میکند بنابراین دقت کنید اگر زمانی خواستید پروژه را از نو با مقادیر قدیمی رمزنگاری شدهی موجود در دیتابیس بنویسید این مقدار را مشابه پروژه قبل لحاظ کنید.
در اینجا برای تست نیاز دارید یک نام و کلمه عبور به دیتابیس اضافه کنید. ساخت یک فرم ثبت نام به منظور وارد کردن یک ردیف در جدول users ساده است اما سادهتر برای ما که تنها قصد تست برنامه را داریم چیست؟ اگر debug را در تنظیمات (core.php) دستکاری نکردید (برابر مقدار 2 تنظیم شده است) در مرورگر مسیر users/login را مرور کنید و نام و کلمه عبور را انتخاب کنید. در نوار زیرین مربوط به debug کلمه عبوری که وارد کردید بطور هش شده (رمزنگاری شده) مشاهده میشود میتوانید به phpMyAdmin بروید و این مقدار را بصورت دستی اضافه کنید. اگر هم برایتان دشوار است خط زیر را ایمپورت کنید. حال یک نام عبور alvani با کلمه عبور 123 دارید:
INSERT INTO 'users' ('id', 'email', 'secretpass') VALUES (1, 'alvani', 'f6b48e1ff9c21a8fe36a77c1ec839086');
نگران نباشید در آموزش بعدی که کامپوننت ACL هم وارد شد، فرم ثبت نام هم افزوده میشود تا آن زمان خودتان هم میتوانید با کمی کنجکاوی آن رابسازید.
نوبت آن رسیده است که دسترسی به برخی از کنشها را در برخی کنترلرها محدود کنیم. فرض کنید قسمت مدیریت بصورت users/account باشد. میخواهیم برنامه را طوری طراحی کنیم که کاربر با ورود موفق به سیستم به این بخش هدایت شود و همچنین کاربران دیگر بدون ورود مجاز به دیدن این کنش نباشند. کنش account را در کنترلر users ایجاد کنید.
از دو متد allow و deny بدین منظور استفاده میکنیم. فرض کنید میخواهیم دسترسی به کنش account را در کنترلر users محدود کنیم کافیست تکه کد زیر را به این کنترلر اضافه کنیم:
function beforeFilter(){ parent::beforeFilter(); $this->Auth->deny('account'); }
تکه کد بالا ابتدا خصوصیات تابع BeforeFilter کنترلر پدر (یعنی AppController را به ارث میبرد) سپس کنش account را محدود میکند. با انجام مراحل فوق تلاش برای مرور users/account شما را به users/login منتقل خواهد کرد. اما بعد از ورود موفق به کجا هدایت خواهید شد؟ این کامپوننت بطور خوردکار پس از ورود موفق شما را به صفحهایی که تلاش میکردید آن را ببینید هدایت می کند اما با کد زیر در BeforeFilter چنانچه نیاز باشد میتوانید کاربر را به کنترلر و کنش دلخواه هدایت کنید:
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'account');
تمرین مناسبی است که BeforeFilter ایی که در کنترلر users در بالا (به منظور محدود کردن و مجوز دادان) استفاده کردید را حذف کنید و با استفاده از foreach بصورت زیر، در BeforeFilter کنترلر AppController، تنها با تعریف کنشهای مجوز داده شده یا محدود شده در ابتدای هر کنترلر آنها را مشخص کنید. مسلما باید ابتدا $allowedActions و $deniedActions را در AppController تعریف کنید:
class AppController extends Controller { var $components = array('Auth'); public $allowedActions = array('index'); public $deniedActions = array(); function beforeFilter() { Security::setHash('md5'); $this->Auth->fields = array('username' => 'email', 'password' => 'secretpass'); foreach( $this->allowedActions as $allowAction ){ $this->Auth->allow( $allowAction ); } foreach( $this->deniedActions as $denyAction ){ $this->Auth->deny( $denyAction ); } } }
در اینجا من دسترسی به کنش index را برای همه کنترلرها آزاد تعریف کردم . از طرف دیگر سایر کنشها محدود شدهاند. حال میتوانم با مقداردهی مجدد $allowedActions و $deniedActions در ابتدای هر کنترلر کنشهایش را از لحاظ دسترسی تعریف کنم. برای مثال اگر بخواهم کنش register را در کنترلر users که بطور پیشفرض محدود است آزاد کنم به کنترلر users کد زیر را اضافه میکنم:
var $allowedActions = array('register');
حال تلاش برای نمایش users/register به صفحه users/login منتقل نخواهد شد. تا اینجای کار میتوانید کنشها را از لحاظ مجوز دسترسی به یک کاربر کنترل کنید اما این کامپوننت انعطاف پذیری خیلی بیشتری دارد در ادامه برخی از آنها بصورت مختصر ذکر میگردد.
چگونه خطاها را تغییر دهید؟ اگر دقت کرده باشید وقتی تلاش میکنید یک کنش محدود شده را ببینید و به Login منتقل میشود خطای عدم مجوز یا هنگامی که نام و کلمه عبورتان درست نیست خطای مربوطه در نمای login نمایش داده میشود با مقداردهی دو متغیر زیر در BeforeFilter میتوانید این خطاها را تغییر دهید:
$this->Auth->authError = "Access denied!"; $this->Auth->loginError = "That's not the right email or password!";
گاهی اوقات نیاز است شرط دیگری را هم برای ورود موفق تعریف کنیم. مثلا کاربری که علاوه بر نام و کلمه عبورش اکانتش فعال هم شده باشد. یک فیلد دیگر بنام active به جدول users اضافه میکنیم. اگر Y بود کاربر فعال اگر N غیرفعال. حال با کد زیر میتوانیم به کاربران فعال مجوز ورود بدهیم:
$this->Auth->userScope = array('User.active' => 'Y');
وقتی users/logout را برای خروج مرور میکنید مستقیم به users/login منتقل میشوید با مقداردهی کنترلر و کنش زیر میتوایند هدایت کاربر بعد از logout را تغییر دهید:
$this->Auth->logoutRedirect = array(Configure::read('Routing.admin') => false, 'controller' => 'members', 'action' => 'logout');
سایر متغیرهای کامپوننت authentication را میتوانید در مستندات کیک در اینجا دنبال کنید . در پستهای آینده این کامپوننت را با ACL و Cookie ترکیب میکنیم تا سیستمی با مجوزها و گروههای مختلف بسازیم. هرچند با امکانات کامپوننت authentication به تنهایی میتوان سیستمی با گروههای مختلف ایجاد کرد، نمونهاش را میتوانید اینجا بیابید. اما انعطاف پذیری ACL بخصوص در پروژههای بزرگ که کاربران در گروههای مختلف قرار دارند فوق العاده قویتر است.
هرگونه مشکلی در خصوص این کامپوننت دارید در کامنتها مطرح کنید حتما پاسخ خواهم داد بخصوص اینکه فکر میکنم در این پست بعضی از نکتهها از قلم افتادهاند و شاید نیاز به توضیح بیشتر باشد. موفق باشید، مرتضی الوانی اسفندماه 1387.
فرید
میبخشید میشه یه خورده راجع به Cake Bake توضیح بدین؟
March 13, 2009 at 6:13 pm
محمد صالح
مرتضی جان منتظرآموزش acl هستیم
March 27, 2009 at 10:06 am
ali786
سلام
آقا این acl چی شد پس ما منتظریم ها
راستی آقا ساختار کنترل پنل برای یه وبلاگ رو هم با کیک توضیح بده لطفا مثلا تا اینجا که من فهمیدم ما تو کنترلر ها مثلا post توابعی داریم که مربوط به مدیریت همین کنترلر هست و توابعی که مربوط به نمایش اطلاعات برای مخاطبان ، اما من می خوام بدونم چطور میشه یه کنترلر admin داشته باشیم و توابعی که مربوط به مدیریت بخش های مختلف هست رو اونجا تعریف کنیم که البته این مستلزم دستکاری قواعد نام گذاری در کیک هست مثلا انتخاب جدول مناسب از پایگاه داده برای هر یک از توابع که در کنترلر مدیریت تعریف شده یعنی هر تابع یک مدل و ….
راستی آقا اگه یه گره یا وبلاگ مستقل یا انجمن برای کیک تشکیل بدیم مفید تر نیست ؟؟
July 13, 2009 at 10:31 am
علیرضا
مرسی عزیزم
آموزشت خیلی کامل و عالیه.
ایشالله آموزش های بیشتر و پیشرفته تر بذاری :)
August 8, 2011 at 11:11 pm