zoukankan      html  css  js  c++  java
  • PHP框架设计入门之二:管理用户

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

      会话

      HTTP是一种无状态的协议,正因为如此,它没有包含任何与服务器连 接的相关信息。这就意味着,HTTP是孤立的,web服务器并不知道用户与你web程序相连接的任何信息,并且服务器会将每个页面请求视为一个新的连接。 Apache/PHP通过提供对会话的支持来避开这一限制。从概念上来说,会话是相当简单的。在一个用户第一次连接到服务器的时候,他被分配一个唯一的 ID。web服务器在一个文件中维护会话信息(译注:即把会话信息存储到文件中),于是可以通过这个ID来定位用户信息。用户同样会在每次连接中维护这个 ID。最典型的作法,就是将ID存储在cookie中,之后,这个ID会作为请求-应答序列的一部分发回给服务器。如果用户不允许使用cookie,会话 ID同样可以在请求每个页面时,通过query字符串(即URL中?以后的部分)发回给服务器。因为web客户端会断开连接,所以web服务器会在一定周 期后,使那些不活动的会话信息过期。

      我们不想在这篇文章中过多地谈论Apache/PHP的配置,除了利用会话来维护用户信息。我们假 设会话支持功能已经开启,并在你的服务器上配置好了。我们将直接从本序列教程第一部分谈论系统基础类时,被我们搁在一边的地方谈起。你可能还记得 class_system.php的第一行是session_start(),这一句的作用是,如果不存在会话信息,则开始一个新的用户会话,否则不做其 他的事情。根据你服务器的配置,开始会话的时候,会话ID会被保存在客户端的cookie里或者作为URL的一部分进行传递。当你调用内建的 session_id()函数时,总可以得到会话ID。通过这些工具,我们现在可以建立一个web应用程序,它可以对用户进行验证,并且能够在用户浏览网 站不同页面的时候去维护用户的信息。如果没有会话,那么用户每一次请求页面的时候,我们就不得不提醒用户进行登录。

      那么,我们应该在会 话中存储什么信息呢?我们一下子就可以想到如用户名这类信息。如果你看一下class_user.php,你会看到其他要存储的数据。(在程序 中)include这个文件的时候,首先会检查用户是否登录(如果没有用户id,那么会设置一个默认的会话值)。注意,session_start()必 须在我们使用$_SESSION数组之前调用,$_SESSION数组包含所有我们的会话数据。UserID用来标识存储在我们数据库中的用户(如果您已 经完成了本系列教程的第一部分,那么这个数据库中的数据应该可以访问了)。Role(角色)是用来检测用户是否有足够的权限去访问程序中的某一部分功能。 LoggedIn标识用来检测用户是否通过验证,Persistent标识用来检测用户是否想依靠他们的cookie内容自动进行登录。

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [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
       }[/PHP]

      用户数据

      我们将所有的用户数据存储到数据库的tblUsers表,这个表可以使用下面的SQL语句来创建(仅限MySQL)

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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';

      这个语句建立了一个大概的用户表。大多数字段不言自明。我们用UserID这个字段来唯一标识每个用户。Login字段同样也必须是唯一的,存 储用户使用的登录名。Password字段用来存储用户密码的MD5散列值。我们没有存储实际的密码是因为安全和隐私的原因。我们可以拿用户输入的密码的 MD5散列值与数据表中的进行对比来验证用户。用户角色用来将用户分配到一个许可组。最后,我们用LastLogon, SessionID和SessionIP字段来跟踪用户对系统的使用情况,包括用户最后登录时间,用户最后使用的会话ID,用户机器的IP地址。用户每次 成功登录后,会调用user系统类中的_updateRecord()函数来更新这些字段值。这些字段同时也可以用来保证安全性,保证不受XSS(跨站脚 本)攻击。

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [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);
    }[/PHP]

      安全问题

      这一部分看起来应该来考虑几个在开发web应用程序会遇到的安全问题。因为安全性是用户管理的一个主要方面,我们得非常细心,不在我们这一部分的代码中留下任何因为粗心导致的bug。

       第一个要考虑的问题是,不管在任何web应用程序中都会遇到的——SQL注入攻击(SQL注入会发送web数据来进行数据库查询)。在我们的情况中,我 们使用用户提供的登录名和密码来查询数据库进而验证用户。一个怀有恶意的用户可以提交SQL代码作为输入文本的一部分,从而可能达到下面的几个目的:1 不需要拥有有效的账号即可登录 2 探测我们数据库的内部结构 3 修改我们的数据库。下面是一个非常简单的例子,用来测试用户是否有效。

     
    1
    2
    $sql = "SELECT * FROM tblUsers
            WHERE Login = '$username' AND Password = md5('$password')";

       设想一下,用户输入 admin'-- ,然后将密码框留空。服务器执行的SQL代码则为:SELECT * FROM tblUsers WHERE Login = 'admin'--' AND Password = md5('')。你是否发现问题了?代码不同时检查登录名和密码了,只是检查登录名,(因为)余下的部分被注释掉了。只要在表里面有一个admin用户, 这个查询就会返回一个肯定的回答。

      你该怎么样保护你自己的代码免受这种类型的威胁呢。第一步是检验任何从不可靠的来源(比如:用户)发 送到SQL服务器的数据。PEAR DB中的quote()函数为我们提供了这样的保护,这个函数可用于发送到SQL服务器的任何字符串。我们的login()函数(译注:该函数请见下文) 显示了我们可以采取的其他预防措施。在我们的代码中,我们在SQL服务器和PHP中(根据SQL服务器返回的记录)都检查了密码。这样的话,攻击必须同时 对SQL服务器和PHP都有效,才能使一个未验证的用户登录进去。你想说这杀伤力太大了吧?是的,也许吧。

      另一个问题是,我们必须警惕会话窃取和跨站脚本攻击(XSS)的可能性。我不想过多地谈论一个黑客冒 充其他已验证用户会话信息的各种方法,但确定的是那确实有可能。事实上,比起利用代码中的bug,许多基于社会工程学的方法更可以称得上是十分难解决的问 题。为了保护我们的用户不受这样的威胁,我们在用户每次登录的时候存储他的会话IP和会话ID。然后,当页面加载完成,我们就拿用户当前的会话ID和IP 地址和数据库中的值进行比对。如果不匹配,那么就破坏会话信息。这样子,如果一个黑客让一个受害者从一台机器上登录,然后试着从他自己的机器使用受害者的 活动会话,那么在他做出任何破坏之前会话就会被关闭。具体的实现代码如下:

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    [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();
        }
    }[/PHP]

    验证

      现在我们已经了解了各种相关的安全问题,下面我们来看一看验证用户的代码。login()函数接收一个登录名和密码,返回一 个Boolean(布尔值)来标明是否正确。正如上面所说的,我们必须假定传入函数中的值是来自于不可靠的来源,用quote()函数来避免问题。完整的 代码如下:

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    [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;
        }
    }[/PHP]

      用户注销的时候,我们要清理在服务器上的会话变量,还有在客户端的会话cookie。我们还要关闭会话。代码如下:

     
    1
    2
    3
    4
    5
    6
    [PHP]//Logout the current user (reset session)
    function logout() {
        $_SESSION = array();                //clear session
        unset($_COOKIE[session_name()]);    //clear cookie
        session_destroy();                  //kill the session
    }[/PHP]

      在每一个页面都要求验证,我们可以简单地检查一下会话,看用户是否已经登录,或者我们可以检查用户角色,看用户是否有足够的权利。角色被定义为一个数字(译者注:即用数字来表明角色),更大的数字意味着更多的权利,下面的代码使用角色来检查用户是否有足够的权利。

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [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;
        }
    }[/PHP]

      登录/注销的接口

       现在我们已经有一个处理会话和用户账号的框架了,我们需要一个接口,这个接口允许用户登录和注销。使用我们的框架,建立这样的一个接口应该是十分简单 的。下面我们就从比较简单的logout.php页面开始,这个页面用来注销用户。这个页面没有任何内容展现给用户,只是在注销用户以后,简单将用户重定 向到index页面。

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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();

      首先,我们定义NO_DB和NO_PRINT常量来优化加载这个页面的时间(正如我们在本系列教程中第一部分描述的那样)。现在,我们要做的所有事情,就是使用user类来注销用户,并在页面初始化事件中重定向到另外的页面。

       这个login.php页面需要一个接口,我们使用系统的表单处理能力简化处理的实现过程。至于这个过程是如何运作的,我们将会在本系列教程的第三和第 四部分详细介绍。现在呢,我们所需要知道的全部事情,就是我们需要一个HTML表单,这个表单与应用程序的逻辑相连接。表单代码如下:

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    [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>[/PHP]
  • 相关阅读:
    通用Struts2 配置文件Filterwebxml
    spring+hibernate+struts2应用mysql数据库乱码问题
    区域医疗卫生平台建设(五) HL7 RIM
    在昆山的日子终于要结束了
    医疗基本知识之医嘱篇(三)医嘱中药物、药品、处方的处理
    区域卫生信息平台建设(一)政策
    区域卫生信息平台建设(四)概念模型与逻辑模型
    区域卫生信息平台建设(二)基本功能
    区域卫生信息平台建设(三)数据模型基本概念
    平板电脑选择
  • 原文地址:https://www.cnblogs.com/firstdream/p/2343750.html
Copyright © 2011-2022 走看看