CakePHP用户认证实例 示例:简单的用户认证 Section 1 Big Picture 如果你刚刚接触CakePHP,你会被这个例子所吸引从而将这些代码复制到你的关键业务场景或者敏感数据处理应用中去。注意,本章讨论的是Cake的内部工作机制,并不是应用的安全模块。我不确定我是不是提供了一些显而易见的安全漏洞,这个例子的目的是展示Cake内部是如何处理安全问题的,你可以如何为自己的应用创建独立的“防弹衣”
Cake的访问控制是基于内置的ACL引擎,但是用户的认证以及认证信息的持久化是怎么样的呢?
我们发现对于时下的各种应用系统,用户认证模块是各不相同。有些喜欢使用哈希编码后的密文密码,有的喜欢使用LDAP认证,并且对于每个系统,User model总是有着些许差别的。我们将这部分工作留给了你。这会不会在未来的版本中改变呢?现在我们还不是很确定,因为就目前的情况来看,将用户认证模块纳入框架还不是很值得的一件事情,创建你自己的用户认证系统是非常简单的。
你需要做3件事情:
认证用户的方式(通常为校验用户的标识,比如用户名/密码组合) 跟踪用户访问情况的方式(通常使用Session) 校验用户是否已经认证通过的方式(通常是和Session进行交互) 这个示例中,我们会为一个客户管理系统创建简单的用户认证系统。这样一个虚拟项目是用来管理客户联系信息和相关的客户记录等。除了少数的公共的视图用来展示客户姓名和职务以外的所有功能都必须通过用户认证后才能访问。
我们从如何验证那些试图访问系统的用户开始。通过认证的用户信息会被Cake Session Component存储在PHP session中。我们从session中取到用户信息后就可以判断哪些*作是该用户可以执行的。
有件事情需要注意——认证并不是访问控制的同义词。我们现在所做的事情是检查用户是不是真的是他所宣称的那个用户,并且允许他访问相应部分的功能。如果你希望更加具体地调节这种访问,请参考前面的ACL章节。我们觉得ACL比较适合那样的场景,不过现在我们还是只关注最简单的用户认证。
我也要再表达一次,这并不代表这个例子只能服务于最基本应用安全。我们只是希望给你提供足够的蔬菜让你建立自己的“防弹衣”。
Section 2 认证与持久化 首先,我们需要一种将用户信息持久化的方式。这个客户管理系统中我们使用数据库方式来持久化用户信息:
Table 'users', Fictional Client Management System Database CREATE TABLE `users` ( `id` int(11) NOT NULL auto_increment, `username` varchar(255) NOT NULL, `password` varchar(32) NOT NULL, `first_name` varchar(255) NOT NULL, `last_name` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) 非常简单是吧?我们的User model也同样的简单:
<?php class User extends AppModel { var $name = 'User'; } ?> 第一个要完成的是登陆的view和action。这能给用户提供一个登陆的入口,同时也为系统提供了处理用户信息判断是否可以访问系统的机会。使用HTML helper可以很简单的创建该Form:
/app/views/users/login.thtml <?if ($error): ?> <p>The login credentials you supplied could not be recognized. Please try again.</p> <? endif; ?> <form action="<?php echo $html->url('/users/login'); ?>" method="post"> <div> <label for="username">Username:</label> <?php echo $html->input('User/username', array('size' => 20)); ?> </div> <div> <label for="password">Password:</label> <?php echo $html->password('User/password', array('size' => 20)); ?> </div> <div> <?php echo $html->submit('Login'); ?> </div> </form> /app/views/users/login.thtml <?if ($error): ?> <p>The login credentials you supplied could not be recognized. Please try again.</p> <? endif; ?>
<form action="<?php echo $html->url('/users/login'); ?>" method="post"> <div> <label for="username">Username:</label> <?php echo $html->input('User/username', array('size' => 20)); ?> </div> <div> <label for="password">Password:</label> <?php echo $html->password('User/password', array('size' => 20)); ?> </div> <div> <?php echo $html->submit('Login'); ?> </div> </form> 对应这个简单的视图(view),还需要一个action(/users/login),代码如下所示:
/app/controllers/users_controller.php (partial) <?php class UsersController extends AppController { function login() { $this->set('error', false); // If a user has submitted form data: if (!empty($this->data)) { // First, let's see if there are any users in the database // with the username supplied by the user using the form: $someone = $this->User->findByUsername($this->data['User']['username']); // At this point, $someone is full of user data, or its empty. // Let's compare the form-submitted password with the one in // the database. if(!emptyempty($someone['User']['password']) && $someone['User']['password'] == $this->data['User']['password']) { // Note: hopefully your password in the DB is hashed, // so your comparison might look more like: // md5($this->data['User']['password']) == ... // This means they were the same. We can now build some basic // session information to remember this user as 'logged-in'. $this->Session->write('User', $someone['User']); // Now that we have them stored in a session, forward them on // to a landing page for the application. $this->redirect('/clients'); } // Else, they supplied incorrect data: else { // Remember the $error var in the view? Let's set that to true: $this->set('error', true); } } } function logout() { // Redirect users to this action if they click on a Logout button. // All we need to do here is trash the session information: $this->Session->delete('User'); // And we should probably forward them somewhere, too... $this->redirect('/'); } } ?> /app/controllers/users_controller.php (partial) <?php class UsersController extends AppController { function login() { //Don't show the error message if no data has been submitted. $this->set('error', false); // If a user has submitted form data: if (!empty($this->data)) { // First, let's see if there are any users in the database // with the username supplied by the user using the form: $someone = $this->User->findByUsername($this->data['User']['username']); // At this point, $someone is full of user data, or its empty. // Let's compare the form-submitted password with the one in // the database. if(!empty($someone['User']['password']) && $someone['User']['password'] == $this->data['User']['password']) { // Note: hopefully your password in the DB is hashed, // so your comparison might look more like: // md5($this->data['User']['password']) == ... // This means they were the same. We can now build some basic // session information to remember this user as 'logged-in'. $this->Session->write('User', $someone['User']); // Now that we have them stored in a session, forward them on // to a landing page for the application. $this->redirect('/clients'); } // Else, they supplied incorrect data: else { // Remember the $error var in the view? Let's set that to true: $this->set('error', true); } } }
function logout() { // Redirect users to this action if they click on a Logout button. // All we need to do here is trash the session information:
$this->Session->delete('User');
// And we should probably forward them somewhere, too... $this->redirect('/'); } } ?> 还不是很坏:如果你写的简炼点,代码应该不会超过20行。这个action的结果有这样两种: 1 用户通过认证,将信息存入Session,并转向到系统首页 2 未通过认证,返回到登陆页面,并显示相关错误信息。
Section 3 访问校验 现在我们可以认证用户了,让我们使系统可以踢除那些不登陆就希望自己访问非公共内容的用户。
一种办法就是在controller中添加一个函数来检查session状态。
/app/app_controller.php <?php class AppController extends Controller { function checkSession() { // If the session info hasn't been set... if (!$this->Session->check('User')) { // Force the user to login $this->redirect('/users/login'); exit(); } } } ?> /app/app_controller.php <?php class AppController extends Controller { function checkSession() { // If the session info hasn't been set... if (!$this->Session->check('User')) { // Force the user to login $this->redirect('/users/login'); exit(); } } } ?> 现在你拥有了一个可以确保未经登陆的用户不能访问系统受限内容的函数了,你可以在任意级别来控制,下面是一些例子:
强制所有action都必须经过认证 <?php class NotesController extends AppController { // Don't want non-authenticated users looking at any of the actions // in this controller? Use a beforeFilter to have Cake run checkSession // before any action logic. function beforeFilter() { $this->checkSession(); } } ?> 在单独的action中要求认证 <?php class NotesController extends AppController { function publicNotes($clientID) { // Public access to this action is okay... } function edit($noteId) { // But you only want authenticated users to access this action. $this->checkSession(); } } ?> 强制所有action都必须经过认证 <?php class NotesController extends AppController { // Don't want non-authenticated users looking at any of the actions // in this controller? Use a beforeFilter to have Cake run checkSession // before any action logic.
function beforeFilter() { $this->checkSession(); } } ?>
在单独的action中要求认证 <?php class NotesController extends AppController { function publicNotes($clientID) { // Public access to this action is okay... }
function edit($noteId) { // But you only want authenticated users to access this action. $this->checkSession(); } } ?> 你已经掌握了一些基础知识,可以开始实现自定义或者高级功能。我们建议整合Cake的ACL控制是个不错的开始
|