PHP框架设计入门之二:管理用户

PHP框架设计入门之二:管理用户                                                                                                                                                                                                                                                                                                                        ----------------------------------------
In part 1, we covered the basic class structure of the framework andlaid out the scope of the project. This part adds session handling toour application and illustrates ways of managing users.

这是PHP应用程序框架设计系列教程的第二部分。在第一部分,我们已经介绍框架的基础类结构,并展示了项目的大体。这一部分,我们将在程序中添加会话处理功能,并演示管理用户的各种方法。

Sessions
会话
引用:
HTTP is a stateless protocol and, as such, does not maintain anyinformation about the connections made to the server. This meansthat,  with HTTP alone, the web server cannot know anything about theusers connected to your web application and will treats each pagerequest as a new connection. Apache/PHP gets around this limitation byoffering support for sessions. Conceptually sessions are a fairlysimple idea.  The first time a web user connects to the server, he isassigned a unique  ID. The web server maintains session information ina file, which can be located using this ID. The user also has tomaintain this ID with each connection to the server. This is typicallydone by storing the ID in a cookie, which is sent back to the server aspart of the typical Request-Response1sequence. If the user does not allow cookies, the session ID can alsobe sent to the server with each page request using the query string(the part of the URL after the  “?”). Because the web client isdisconnected, the web server will expire sessions after predefinedperiods of inactivity.
HTTP是一种无状态的协议,正因为如此,它没有包含任何与服务器连接的相关信息。这就意味着,HTTP是孤立的,web服务器并不知道用户与你web程序相连接的任何信息,并且服务器会将每个页面请求视为一个新的连接。Apache/PHP通过提供对会话的支持来避开这一限制。从概念上来说,会话是相当简单的。在一个用户第一次连接到服务器的时候,他被分配一个唯一的ID。web服务器在一个文件中维护会话信息(译注:即把会话信息存储到文件中),于是可以通过这个ID来定位用户信息。用户同样会在每次连接中维护这个ID。最典型的作法,就是将ID存储在cookie中,之后,这个ID会作为请求-应答序列的一部分发回给服务器。如果用户不允许使用cookie,会话ID同样可以在请求每个页面时,通过query字符串(即URL中?以后的部分)发回给服务器。因为web客户端会断开连接,所以web服务器会在一定周期后,使那些不活动的会话信息过期。
引用:
        We will not go over configuring Apache/PHP in this article butwill utilize sessions to maintain user information in our application.It is assumed that session support is already enabled and configured onyour server. We will pick up where we left off in part 1 of this serieswhen we described the system base class. You may recall that the firstline in class_system.phpis session_start(),  which starts a new user session if none exists ordoes nothing otherwise. Depending on how your server is configured,this will cause the session ID to be saved in the client’s cookie fileor passed as part of the URL. The session ID is always available to youby calling the build in function session_id().  With these tools athand, we can now build a web application that can authenticate a userand maintain the user’s information as he is browsing the differentpages on the site. Without sessions, we would have to prompt the userfor their login credentials every single time they request page.

我们不想在这篇文章中过多地谈论Apache/PHP的配置,除了利用会话来维护用户信息。我们假设会话支持功能已经开启,并在你的服务器上配置好了。我们将直接从本序列教程第一部分谈论系统基础类时,被我们搁在一边的地方谈起。你可能还记得class_system.php的第一行是session_start(),这一句的作用是,如果不存在会话信息,则开始一个新的用户会话,否则不做其他的事情。根据你服务器的配置,开始会话的时候,会话ID会被保存在客户端的cookie里或者作为URL的一部分进行传递。当你调用内建的session_id()函数时,总可以得到会话ID。通过这些工具,我们现在可以建立一个web应用程序,它可以对用户进行验证,并且能够在用户浏览网站不同页面的时候去维护用户的信息。如果没有会话,那么用户每一次请求页面的时候,我们就不得不提醒用户进行登录。
引用:
         So what will we want to store in the session? Let’s start withthe obvious like the user’s name. If you take a look at  class_user.phpyou will see the rest of the data being stored. When this file isincluded, the first thing that is checked is whether a user is loggedin (default session values are set if the users id is not set). Notethat the session_start() must have already been called before we startplaying with the $_SESSION array which contains all our session data.The UserID will be used to identify the user in our database (whichshould already be accessible after part one of this series). The Rolewill be used to determine whether the user has sufficient privileges toaccess certain features of the application. The LoggedIn flag will beused to determine if the user has already been authenticated and thePersistent flag will be used to determine whether the user wants toautomatically be logged in based on their cookie content.
那么,我们应该在会话中存储什么信息呢?我们一下子就可以想到如用户名这类信息。如果你看一下class_user.php,你会看到其他要存储的数据。(在程序中)include这个文件的时候,首先会检查用户是否登录(如果没有用户id,那么会设置一个默认的会话值)。注意,session_start()必须在我们使用$_SESSION数组之前调用,$_SESSION数组包含所有我们的会话数据。UserID用来标识存储在我们数据库中的用户(如果您已经完成了本系列教程的第一部分,那么这个数据库中的数据应该可以访问了)。Role(角色)是用来检测用户是否有足够的权限去访问程序中的某一部分功能。LoggedIn标识用来检测用户是否通过验证,Persistent标识用来检测用户是否想依靠他们的cookie内容自动进行登录。
复制PHP内容到剪贴板
PHP代码:
//session has not been established
        if (!isset($_SESSION['UserID']) ) {
                    set_session_defaults();
        }

        //reset session values
        function set_session_defaults() {
                    $_SESSION['UserID'] = '0';          //User ID in Database
                    $_SESSION['Login'] = '';            //Login Name
                    $_SESSION['UserName'] = '';         //User Name
                    $_SESSION['Role'] = '0';            //Role

                    $_SESSION['LoggedIn'] = false;      //is user logged in
                    $_SESSION['Persistent'] = false;    //is persistent cookie set
        }
 User Data

用户数据

    We store all the user data in our database in table tblUsers.  Thistable can be created using the following SQL statement (mySQL only).

我们将所有的用户数据存储到数据库的tblUsers表,这个表可以使用下面的SQL语句来创建(仅限MySQL)
复制PHP内容到剪贴板
PHP代码:
CREATE TABLE `tblUsers` (
  `UserID` int(10) unsigned NOT NULL auto_increment,
  `Login` varchar(50) NOT NULL default '',
  `Password` varchar(32) NOT NULL default '',
  `Role` int(10) unsigned NOT NULL default '1',
  `Email` varchar(100) NOT NULL default '',
  `RegisterDate` date default '0000-00-00',
  `LastLogon` date default '0000-00-00',
  `SessionID` varchar(32) default '',
  `SessionIP` varchar(15) default '',
  `FirstName` varchar(50) default NULL,
  `LastName` varchar(50) default NULL,
  PRIMARY KEY  (`UserID`),
  UNIQUE KEY `Email` (`Email`),
  UNIQUE KEY `Login` (`Login`)
) TYPE=MyISAM COMMENT='Registered Users';


引用:
This statement creates a bare-bones user table. Most of the fields are selfexplanatory. We need the UserID field to uniquely identify each user.The Login field, which must also be unique, stores the user's desiredlogin name. The Password field stores the MD5 hash of the user'spassword. We are not storing the actual password for security andprivacy reasons. Instead we can compare the MD5 hash of the passwordentered with the value stored in this table to authenticate the user.The user's Role will be used to assign the user to a permission group.Finaly, we will use the LastLogon, SessionID, and SessionIP fields totrack the user's usage of our system including the last time the userlogged in, the last PHP session ID the user had, and the IP address ofthe user's host. These fields are updated each time the usersuccessfully logs in using the _updateRecord() function in the usersystem class. These fields are also used for security in preventingcross-site scripting attacks.
这个语句建立了一个大概的用户表。大多数字段不言自明。我们用UserID这个字段来唯一标识每个用户。Login字段同样也必须是唯一的,存储用户使用的登录名。Password字段用来存储用户密码的MD5散列值。我们没有存储实际的密码是因为安全和隐私的原因。我们可以拿用户输入的密码的MD5散列值与数据表中的进行对比来验证用户。用户角色用来将用户分配到一个许可组。最后,我们用LastLogon, SessionID和SessionIP字段来跟踪用户对系统的使用情况,包括用户最后登录时间,用户最后使用的会话ID,用户机器的IP地址。用户每次成功登录后,会调用user系统类中的_updateRecord()函数来更新这些字段值。这些字段同时也可以用来保证安全性,保证不受XSS(跨站脚本)攻击。
复制PHP内容到剪贴板
PHP代码:
//Update session data on the server
function _updateRecord () {
    $session = $this->db->quote(session_id());
    $ip      = $this->db->quote($_SERVER['REMOTE_ADDR']);

    $sql = "UPDATE tblUsers SET
                LastLogon = CURRENT_DATE,
                SessionID = $session,
                SessionIP = $ip
            WHERE UserID = $this->id";
    $this->db->query($sql);
}
Security Issues
Authentication

验证

Now that we understand the various security issues involved, lets lookat the code for authenticating a user. The login() function accepts alogin name and password and returns a Boolean reply to indicatesuccess. As stated above, we must assume that the values passed intothe function came from an untrusted source and use the quote() functionto avoid problems. The complete code is provided below.

现在我们已经了解了各种相关的安全问题,下面我们来看一看验证用户的代码。login()函数接收一个登录名和密码,返回一个Boolean(布尔值)来标明是否正确。正如上面所说的,我们必须假定传入函数中的值是来自于不可靠的来源,用quote()函数来避免问题。完整的代码如下:
复制PHP内容到剪贴板
PHP代码:
//Login a user with name and pw.
//Returns Boolean
function login($username, $password) {
    $md5pw    = md5($password);
    $username = $this->db->quote($username);
    $password = $this->db->quote($password);
    $sql = "SELECT * FROM tblUsers WHERE
            Login = $username AND
            Password = md5($password)";

    $result = $this->db->getRow($sql);

    //check if pw is correct again (prevent sql injection)
    if ($result and $result['Password'] == $md5pw) {
        $this->_setSession($result);
        $this->_updateRecord();     //update session info in db
        return true;
    } else {
        set_session_defaults();
        return false;
    }
}


To logout, we have to clear the session variables on the server as well asthe session cookies on the client. We also have to close the session.The code below does just that.

用户注销的时候,我们要清理在服务器上的会话变量,还有在客户端的会话cookie。我们还要关闭会话。代码如下:
复制PHP内容到剪贴板
PHP代码:
//Logout the current user (reset session)
function logout() {
    $_SESSION = array();                //clear session
    unset($_COOKIE[session_name()]);    //clear cookie
    session_destroy();                  //kill the session
}


Inevery page that requires authentication, we can simply check thesession to see if they user is logged in or we can check the user'srole to see if the user has sufficient privileges. The role is definedas a number with the larger numbers indicating more rights. The codebelow checks to see if the users has enough rights using the role.

在每一个页面都要求验证,我们可以简单地检查一下会话,看用户是否已经登录,或者我们可以检查用户角色,看用户是否有足够的权利。角色被定义为一个数字(译者注:即用数字来表明角色),更大的数字意味着更多的权利,下面的代码使用角色来检查用户是否有足够的权利。
复制PHP内容到剪贴板
PHP代码:
//check if user has enough permissions
//$role is the minimum level required for entry
//Returns Boolean
function checkPerm($role) {
    if ($_SESSION['LoggedIn']) {
        if ($_SESSION['Role']>=$role) {
            return true;
        } else {
            return false;
        }
    } else {
        return false;
    }
}
Login/Logout Interface

登录/注销的接口
引用:
Now that we have a framework for handling sessions and user accounts,we need an interface to allow the user to login and out. Using ourframework, creating this interface should be fairly easy. Let us startwith the simpler logout.php page which will be used to log a user out.This page has no content to display to the user and simply redirectsthe user to the index page after having logged him out.
现在我们已经有一个处理会话和用户账号的框架了,我们需要一个接口,这个接口允许用户登录和注销。使用我们的框架,建立这样的一个接口应该是十分简单的。下面我们就从比较简单的logout.php页面开始,这个页面用来注销用户。这个页面没有任何内容展现给用户,只是在注销用户以后,简单将用户重定向到index页面。
复制PHP内容到剪贴板
PHP代码:
define('NO_DB', 1);
define('NO_PRINT', 1);
include "include/class_system.php";

class Page extends SystemBase {
    function init() {
        $this->user->logout();
        $this->redirect("index.php");
    }
}

$p = new Page();


引用:
First we define the NO_DB and NO_PRINT constants to optimize the loading time of this page (asdescribed in Part 1 of this series). Now all we have to do is use theuser class to log the user out and redirect to another page in thepage's initialization event.
首先,我们定义NO_DB和NO_PRINT常量来优化加载这个页面的时间(正如我们在本系列教程中第一部分描述的那样)。现在,我们要做的所有事情,就是使用user类来注销用户,并在页面初始化事件中重定向到另外的页面。
引用:
The login.php page will need an interface and we will use the system'sform handling abilities to simplify the implementation process.   Details of how this works will be described in Parts 3 and 4 of thisseries. For now, all we need to know is that we need an HTML form thatis linked the application logic. The form is provided below.
这个login.php页面需要一个接口,我们使用系统的表单处理能力简化处理的实现过程。至于这个过程是如何运作的,我们将会在本系列教程的第三和第四部分详细介绍。现在呢,我们所需要知道的全部事情,就是我们需要一个HTML表单,这个表单与应用程序的逻辑相连接。表单代码如下:
复制PHP内容到剪贴板
PHP代码:
<form action="<?=$_SERVER['PHP_SELF']?>" method="POST" name="<?=$formname?>">
 <input type="hidden" name="__FORMSTATE" value="<?=$_POST['__FORMSTATE']?>">

 <table>
   <tr>
     <td>Username:
     <td><input type="text" name="txtUser" value="<?=$_POST['txtUser']?>"></td>
   </tr>
   <tr>
     <td>Password:
     <td><input type="password" name="txtPW" value="<?=$_POST['txtPW']?>"></td>
   </tr>
   <tr>
     <td colspan="2">
       <input type="checkbox" name="chkPersistant" <?=$persistant?>>
       Remember me on this computer
     </td>
   </tr>
   <tr style="text-align: center; color: red; font-weight: bold">
     <td colspan="2">
       <?=$error?>
     </td>
   </tr>
   <tr>
     <td colspan="2">
       <input type="submit" name="Login" value="Login">
       <input type="reset" name="Reset" value="Clear">
     </td>
   </tr>
 </table>
</form>

引用:
Now we need the code to log a user in. This code sample demonstrates how touse the system framework to load the above form into a page template,handle the form events, and use the user class to authenticate the user.
现在我们来编写用户登录的代码。这个代码演示了如何使用系统框架将上面的表单加载到一个页面模板中,以及处理表单事件、使用user类来验证用户。
复制PHP内容到剪贴板
PHP代码:
class Page extends SystemBase {
    function init() {
        $this->form = new FormTemplate("login.frm.php", "frmLogin");
        $this->page->set("title","Login page");
        if (!isset($_POST['txtUser']) && $name=getCookie("persistantName")) {
            $this->form->set("persistant","checked");
            $_POST['txtUser']=$name;
        }
    }

    function handleFormEvents() {
        if (isset($_POST["Login"])) {
            if($this->user->login($_POST['txtUser'],$_POST['txtPW'])) {
                if (isset($_POST['chkPersistant'])) {
                    sendCookie("persistantName", $_POST['txtUser']);
                } else {
                    deleteCookie("persistantName");
                }
                $this->redirect($_SESSION['LastPage']);
            } else
                $this->form->set("error","Invalid Account");
        }
    }
}
$p = new Page();


引用:
Onpage initialization, the form is loaded into the page template, thepage's title is set and the user's login name is pre-entered into theinput field if the persistent cookie is set. The real work happens whenwe handle the form events (i.e. when the user presses a button tosubmit the page).  First we check if the login button was clicked. Thenwe use the login name and password submitted to authenticate the user.If authentication is successful,  we also set a cookie to remember theusers name for the next time. If the authentication fails, an error isdisplayed on the page.
在页面初始化时,表单被加载到页面模板中,页面的标题已经被设置好,如果有永久的cookie,那么用户的登录名还会被预先输入到输入框中。当我们处理表单的时候,我们就真正有事情要做了(比如:当用户按下一个按钮提交页面)。首先,我们检查登录按钮是否被点击了。然后,我们使用提交过来的登录名和密码来验证用户。如果验证成功,我们同时设置一个cookie来记住用户名以便下次使用。如果验证失败,则会在页面上显示一个错误。

Summary

总结
引用:
So far, we have laid the foundation for how our application will behaveand how the framework will be used. We added user managementcapabilities to our application and covered several security issues.Read the next part to see how to implement page templates and separateapplication logic from the presentation layer.
目前为止,我们已经谈论了我们的应用程序如何运转及框架如何使用的基本信息。我们为我们的应用程序添加了用户管理能力,并且谈论了几个安全问题。下一部分,将可以看到如何实现页面模板,从而将应用程序逻辑从表现层中分离出来。

 


安全问题

This seems like a logical place to address several security issues thatcome up when developing web applications. Since security is a majoraspect of user management, we need to be very careful not to leave anycareless bugs in this part of our code.

这一部分看起来应该来考虑几个在开发web应用程序会遇到的安全问题。因为安全性是用户管理的一个主要方面,我们得非常细心,不在我们这一部分的代码中留下任何因为粗心导致的bug。
引用:
The first issue that needs to be addressed is the potential for SQLinjection in any web application that uses posted web data to query adatabase. In our case, we use the login name and password supplied bythe user to query the database and authenticate the user. A malicioususer can submit SQL code as part of input field text and maypotentially achieve any of the following: 1) login without having avalid account, 2) determine the internal structure of our database or3) modify our database. The simplest example of this is the SQL codeused to test if the user is valid.
第一个要考虑的问题是,不管在任何web应用程序中都会遇到的——SQL注入攻击(SQL注入会发送web数据来进行数据库查询)。在我们的情况中,我们使用用户提供的登录名和密码来查询数据库进而验证用户。一个怀有恶意的用户可以提交SQL代码作为输入文本的一部分,从而可能达到下面的几个目的:1 不需要拥有有效的账号即可登录 2 探测我们数据库的内部结构 3 修改我们的数据库。下面是一个非常简单的例子,用来测试用户是否有效。
复制PHP内容到剪贴板
PHP代码:
$sql = "SELECT * FROM tblUsers
        WHERE Login = '$username' AND Password = md5('$password')";


引用:
Suppose the user enters admin'-- and leaves the password blank.  The SQL codeexecuted by the server is: SELECT * FROM tblUsers WHERE  Login ='admin'--' AND Password = md5(''). Do you see the problem?  Instead ofchecking the login name and password, the code only the checks thelogin name and the rest is commented out. As long as there is a useradmin in the table, the query will return a positive response.  You canread about other SQL injection exploits in David Litchfield's publication.
设想一下,用户输入 admin'-- ,然后将密码框留空。服务器执行的SQL代码则为:SELECT * FROM tblUsers WHERE Login = 'admin'--' AND Password = md5('')。你是否发现问题了?代码不同时检查登录名和密码了,只是检查登录名,(因为)余下的部分被注释掉了。只要在表里面有一个admin用户,这个查询就会返回一个肯定的回答。你可以通过阅读David Litchfield的文章(http://blackhat.com/presentation ... u-05-litchfield.pdf)来了解其他的SQL注入攻击。
引用:
How do you protect yourself from this kind of threat. The firststep is to validate any data sent to the SQL server that comes from anuntrusted source (i.e. the user). PEAR DB provides us with thisprotection using the quote() function which should be used on anystring sent to the SQL server. Our login() function shows otherprecautions that we can take. In the code, we actually check thepassword in both the SQL server and in PHP based on the recordreturned. This way, the exploit would have to work for both the SQLserver and PHP for an unauthorized user to get in.  Overkill you say?Well, maybe.
你该怎么样保护你自己的代码免受这种类型的威胁呢。第一步是检验任何从不可靠的来源(比如:用户)发送到SQL服务器的数据。PEAR DB中的quote()函数为我们提供了这样的保护,这个函数可用于发送到SQL服务器的任何字符串。我们的login()函数(译注:该函数请见下文)显示了我们可以采取的其他预防措施。在我们的代码中,我们在SQL服务器和PHP中(根据SQL服务器返回的记录)都检查了密码。这样的话,攻击必须同时对SQL服务器和PHP都有效,才能使一个未验证的用户登录进去。你想说这杀伤力太大了吧?是的,也许吧。
引用:
Another issue that we have to be aware of is the potential for sessionstealing and cross site scripting (XSS). I won't get into the variousways that a hacker can assume the session of another authenticated userbut rest assured that it is possible. In fact, many methods are basedon social engineering rather than bugs in the actual code so this canbe a fairly difficult problem to solve. In order to protect our usersfrom this threat,  we store the Session IP and Session ID of the usereach time he logs in. Then, when any page is loaded, the users currentSession ID and IP address are compared to the values in the database.If the values don't match then the session is destroyed. This way, if ahacker gets a victim to log in from one machine and then tries to usethat active session from his own machine, the session will be closedbefore any harm can be done. The code to implement this is bellow.
另一个问题是,我们必须警惕会话窃取和跨站脚本攻击(XSS)的可能性。我不想过多地谈论一个黑客冒充其他已验证用户会话信息的各种方法,但确定的是那确实有可能。事实上,比起利用代码中的bug,许多基于社会工程学的方法更可以称得上是十分难解决的问题。为了保护我们的用户不受这样的威胁,我们在用户每次登录的时候存储他的会话IP和会话ID。然后,当页面加载完成,我们就拿用户当前的会话ID和IP地址和数据库中的值进行比对。如果不匹配,那么就破坏会话信息。这样子,如果一个黑客让一个受害者从一台机器上登录,然后试着从他自己的机器使用受害者的活动会话,那么在他做出任何破坏之前会话就会被关闭。具体的实现代码如下:
复制PHP内容到剪贴板
PHP代码:
//check if the current session is valid (otherwise logout)
function _checkSession() {
    $login   = $this->db->quote($_SESSION['Login']);
    $role    = $this->db->quote($_SESSION['Role']);
    $session = $this->db->quote(session_id());
    $ip      = $this->db->quote($_SERVER['REMOTE_ADDR']);

    $sql = "SELECT * FROM tblUsers WHERE
            Login = $login AND
            Role = $role AND
            SessionID = $session AND
            SessionIP = $ip";

    $result = $this->db->getRow($sql);

    if ($result) {
        $this->_setSession($result);
    } else {
        $this->logout();
    }
}


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/farflight/archive/2008/04/01/2234615.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值