// 
//     Licensed to the Apache Software Foundation (ASF) under one
//     or more contributor license agreements.  See the NOTICE file
//     distributed with this work for additional information
//     regarding copyright ownership.  The ASF licenses this file
//     to you under the Apache License, Version 2.0 (the
//     "License"); you may not use this file except in compliance
//     with the License.  You may obtain a copy of the License at
// 
//       http://www.apache.org/licenses/LICENSE-2.0
// 
//     Unless required by applicable law or agreed to in writing,
//     software distributed under the License is distributed on an
//     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//     KIND, either express or implied.  See the License for the
//     specific language governing permissions and limitations
//     under the License.
//

= 使用 PHP 创建数据库驱动的应用程序
:jbake-type: tutorial
:jbake-tags: tutorials 
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: 使用 PHP 创建数据库驱动的应用程序 - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, 使用 PHP 创建数据库驱动的应用程序

= 第 5 课：添加安全功能。实现应用程序用户登录
:jbake-type: tutorial
:jbake-tags: tutorials 
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: 第 5 课：添加安全功能。实现应用程序用户登录 - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, 第 5 课：添加安全功能。实现应用程序用户登录



1. link:wish-list-tutorial-main-page.html[+使用 PHP 创建数据库驱动的应用程序 - 主页+]
2. 
创建数据库

1. link:wish-list-lesson1.html[+创建 MySQL 数据库+]
2. link:wish-list-oracle-lesson1.html[+创建 Oracle 数据库表+]
3. link:wish-list-lesson2.html[+设计应用程序。从数据库读取数据+]
4. link:wish-list-lesson3.html[+创建新的应用程序用户+]
5. link:wish-list-lesson4.html[+优化代码+]
6. 
*=> 添加安全功能。实现应用程序用户登录*

7. link:wish-list-lesson6.html[+在数据库中添加新的愿望+]
8. link:wish-list-lesson7.html[+更新和删除数据库中的条目+]
9. link:wish-list-lesson8.html[+使用 CSS 技术改进应用程序的外观+]
10. link:wish-list-lesson9.html[+将应用程序部署到远程 Web 服务器+]

image::images/netbeans-stamp-80-74-73.png[title="此页上的内容适用于 NetBeans IDE 7.2、7.3、7.4 和 8.0"]

在本课中，将为许愿者实现登录功能。这会影响以下文件：

*  ``index.php`` 
*  ``createNewWisher.php`` 
*  ``editWishlist.php`` 
*  ``db.php`` 

实现登录功能包括以下步骤：

1. <<savingWisherIDInSessionUponCreation,在创建许愿者后，在会话中保存许愿者 ID>>
2. <<validateWisherLogon,验证尝试编辑愿望列表的用户是否登录>>
3. <<logonFromIndexPage,许愿者从 index.php 页中登录>>

当前文档是“在适用于 PHP 的 NetBeans IDE 中创建 CRUD 应用程序”教程的一部分。



== 来自上一课的应用程序源代码

MySQL 用户：单击link:https://netbeans.org/files/documents/4/1930/lesson4.zip[+此处+]以下载源代码，该代码反映了在完成上一课之后的项目状态。

Oracle 数据库用户：单击link:https://netbeans.org/projects/www/downloads/download/php%252Foracle-lesson4.zip[+此处+]以下载源代码，该代码反映了在完成上一课之后的项目状态。


== 创建后在会话中保存许愿者 ID

link:http://us2.php.net/manual/en/ref.session.php[+会话+]是持久性存储，可用于将信息从一个页面传输到另一个页面，而无需使用 link:wish-list-lesson5.html#htmlForm[+HTML 输入窗体+]。可通过预定义的 PHP 数组  ``$_SESSION``  支持该功能。

为安全起见，在创建新的许愿者后，该许愿者将自动登录而无需填写窗体。因此，您需要修改  ``createNewWisher.php``  文件以实现以下功能：

* 将新许愿者添加到数据库中。
* 打开会话。
* 在会话中存储许愿者的名字。
* 在将许愿者重定向到  ``editWishList.php``  页时，传输会话中的许愿者名字。

在  ``createNewWisher.php``  文件中，找到以下行：


[source,java]
----

WishDB::getInstance()->create_wisher($_POST["user"], $_POST["password"]);
----

然后，在其下输入以下代码块：


[source,java]
----

session_start();
$_SESSION['user'] = $_POST['user'];
----

该代码块启动一个会话，这意味着打开  ``$_SESSION``  数组以输入或检索数据。然后，该代码在  ``$_SESSION``  数组中添加一个元素。添加的元素包含一个值和标识符（键）。该值是新创建的许愿者名字，标识符是 "user"。然后，程序将许愿者重定向到  ``editWishList.php``  页。


== 验证用户登录

当用户访问  ``editWishList.php``  页时，应用程序应确认刚才在  ``createNewWisher.php``  页上注册的同一个人访问了该页。

实现该功能包括两个步骤：

* <<retrievingUserNameFromSession,从会话中检索许愿者的名字>>
* <<redirectingNotLoggedInUserToIndexPage,如果从会话中检索许愿者名字失败，将用户重定向到 index.php>>


=== 从会话中检索许愿者的名字

将  ``editWishList.php``  的 PHP 块中的默认代码替换为以下内容：

[source,java]
----

session_start();
if (array_key_exists("user", $_SESSION)) {
    echo "Hello " . $_SESSION['user'];
}
----

该代码块打开  ``$_SESSION``  数组以检索数据，并验证  ``$_SESSION``  是否包含具有 "user" 标识符的元素。如果检查成功，该代码将输出欢迎消息。

检查是否正确实现会话：

1. 运行  ``createNewWisher.php``  文件，然后创建一个新的许愿者，例如，Jack。
 ``editWishList.php``  打开，并显示 Hello Jack。
2. 在浏览器中清除会话 Cookie，或者结束会话并从 IDE 中运行  ``editWishList.php`` 。
 ``editWishList.php``  文件打开并显示 Hello，因为没有通过会话传输任何用户。这是不正确的，因为它允许未登录和未注册的人创建或编辑愿望列表。为了避免出现该问题，需要将用户重定向到  ``index.php``  页。


=== 重定向未登录的用户

将以下代码块添加到  ``editWishList.php``  中的  ``if``  子句之下：

[source,java]
----

else {
   header('Location: index.php');
   exit;
}
----

该代码将用户重定向到 index.php 页并取消 PHP 代码执行。

要检查是否正确实现了该功能，请运行  ``editWishList.php``  文件。预期的结果是打开  ``index.php``  页。


== 从 index.php 页中登录

从 index.php 页中登录包括两个步骤：

* <<logonForm,在 HTML 输入窗体中输入用户的名字和口令，并将用于验证的数据提交到 index.php 页。>>
* <<logonValidation,验证登录>>


=== 用于在 index.php 上登录的 HTML 窗体

在  ``index.php``  文件中，在结束  ``</body>``  标记前输入以下代码：

[source,xml]
----

<form name="logon" action="index.php" method="POST" >
    Username: <input type="text" name="user">
    Password  <input type="password" name="userpassword">
    <input type="submit" value="Edit My Wish List">
</form>
----

*注：*您可以忽略来自 HTML 验证器的警告。

该代码显示一个 link:wish-list-lesson3.html#htmlForm[+HTML 窗体+]，用于在文本字段中输入用户的名字和口令。当用户单击 "Edit My Wish List" 时，数据将传输到同一页，即 index.php。


=== 登录验证

登录验证包括：

* <<checkWhereUserCameFrom,检查从中重定向用户的位置>>。
* <<verifyCredentials,验证用户的名字和口令>>。
* 将用户名保存到会话中并将用户重定向到 editWishList.php 页或<<displayErrorMessage,显示一条错误消息。>>

用户可以在启动应用程序时访问  ``index.php``  页，从 <<validateWisherLogon,editWishList.php>> 页中进行访问，或者在输入名字和口令后从  ``index.php``  页中重定向时访问该页。

由于仅在最后一种情况下使用 link:http://www.htmlcodetutorial.com/forms/_FORM_METHOD.html[+HTML 请求方法+] POST，因此，您始终可以了解用户访问  ``index.php``  时所在的位置。

在 index.php 文件中，使用以下代码在 HTML 块上面创建一个 <?php ?> 块：

[source,php]
----

<?php

require_once("Includes/db.php");
$logonSuccess = false;// verify user's credentials
if ($_SERVER['REQUEST_METHOD'] == "POST") {
    $logonSuccess = (WishDB::getInstance()->verify_wisher_credentials($_POST['user'], $_POST['userpassword']));
    if ($logonSuccess == true) {
        session_start();
        $_SESSION['user'] = $_POST['user'];
        header('Location: editWishList.php');
        exit;
    }
}
?>

----

代码块顶部允许使用  ``db.php``  文件，并使用  ``false``  值初始化  ``$logonSuccess``  变量。如果验证成功，该值将变为  ``true`` 。

验证用户凭证的代码先检查请求方法是否为 POST。如果请求方法是 POST，则在提交<<logonForm,登录窗体>>后重定向用户。在这种情况下，代码块使用在登录窗体中输入的名字和口令调用  ``verify_wisher_credentials``  函数。

 ``verify_wisher_credentials``  函数（在<<verifyWisherCredentials,下一节>>中编写）检查  ``wishers``  表中是否存在用户和口令与<<logonForm,登录窗体>>中提交的值相匹配的记录。如果  ``verify_wisher_credentials``  函数返回  ``true`` ，则在数据库中注册一个具有指定名字和口令组合的许愿者。这表示验证成功，并且  ``$logonSuccess``  将值更改为  ``true`` 。在这种情况下，将启动一个会话并打开  ``$_SESSION``  数组。该代码在  ``$_SESSION``  数组中添加一个新元素。该元素包含一个值和标识符（键）。该值是许愿者的名字，标识符是 "user"。然后，该代码将用户重定向到  ``editWishList.php``  页以编辑愿望列表。

如果  ``verify_wisher_credentials``  函数返回  ``false`` ，则  ``$logonSuccess``  变量值保持为 false。将使用该变量值<<displayErrorMessage,显示错误消息>>。


=== verify_wisher_credentials 函数

要实现验证许愿者凭证的功能，您需要在  ``db.php``  文件的  ``WishDB``  类中添加一个新函数。该函数要求将名字和口令作为输入参数，并返回 0 或 1。

*对于 MySQL 数据库*，请输入以下代码块：

[source,java]
----

public function verify_wisher_credentials ($name, $password){$name = $this->real_escape_string($name);$password = $this->real_escape_string($password);$result = $this->query("SELECT 1 FROM wishers
 	           WHERE name = '" . $name . "' AND password = '" . $password . "'");
   return $result->data_seek(0);
}
----

*对于 Oracle 数据库*，请输入以下代码块（由于 OCI8 没有等效的  ``mysql_num_rows`` ，该代码是  ``get_wisher_id_by_name``  的修改形式）：


[source,java]
----

public function verify_wisher_credentials($name, $password) {
    $query = "SELECT 1 FROM wishers WHERE name = :name_bv AND password = :pwd_bv";
    $stid = oci_parse($this->con, $query);
    oci_bind_by_name($stid, ':name_bv', $name);
    oci_bind_by_name($stid, ':pwd_bv', $password);
    oci_execute($stid);
//Because name is a unique value I only expect one row
    $row = oci_fetch_array($stid, OCI_ASSOC);
    if ($row) 
        return true;
    else
        return false;
}
----

该代码块执行查询  ``"SELECT 1 FROM wishers WHERE Name = '" . $name . "'AND Password = '" . $password . "'"``  并返回满足指定查询的记录数。如果找到此类记录，该函数将返回  ``true`` 。如果在数据库中找不到此类记录，该函数将返回  ``false`` 。


=== 显示错误消息

要允许应用程序显示错误消息，请输入以下 <? php ?> 代码块（在  ``index.php``  的登录窗体中的输入字段之下、按钮之上）：

[source,php]
----

<?php
  if ($_SERVER["REQUEST_METHOD"] == "POST") { 
      if (!$logonSuccess)
          echo "Invalid name and/or password";
  }
?>
----
该代码块检查 $logonSuccess 变量值；如果该值为 false，则显示一条错误消息。


== 测试从 index.php 页中登录

检查登录功能在主页  ``index.php``  上是否正常工作：

1. 运行应用程序。
2. 在  ``index.php``  页上，在 "Username" 编辑框中输入 Tom，在 "Password" 编辑框中输入 Tim。
3. 按 "Edit My Wish List"。将显示一条错误消息（请注意，下面的浏览器窗口宽度减小为 600px，其中添加了一些换行符）： 
image::images/incorrectNamePasswordIndex.png[]
4. 在 "Username" 编辑框中输入 Tom，在 "Password" 编辑框中输入 tomcat。
5. 按 "Edit My Wish list"。将显示 editWishList.php 页： 
image::images/SuccessfulLogonOnIndexRedirectToEditWishList.png[]


== 完成当前课程后的应用程序源代码

MySQL 用户：单击link:https://netbeans.org/files/documents/4/1931/lesson5.zip[+此处+]以下载源代码，该代码反映了在完成课程后的项目状态。

Oracle 数据库用户：单击link:https://netbeans.org/projects/www/downloads/download/php%252Foracle-lesson5.zip[+此处+]以下载源代码，该代码反映了在完成课程后的项目状态。


== 后续步骤

link:wish-list-lesson4.html[+<< 上一课+]

link:wish-list-lesson6.html[+下一课 >>+]

link:wish-list-tutorial-main-page.html[+返回到教程主页+]


link:/about/contact_form.html?to=3&subject=Feedback:%20PHP%20Wish%20List%20CRUD%205:%20Implementing%20Security[+发送有关此教程的反馈意见+]


要发送意见和建议、获得支持以及随时了解 NetBeans IDE PHP 开发功能的最新开发情况，请link:../../../community/lists/top.html[+加入 users@php.netbeans.org 邮件列表+]。

link:../../trails/php.html[+返回至 PHP 学习资源+]

