65.9K
CodeProject 正在变化。 阅读更多。
Home

登录项目 - 实现网站登录机制

starIconstarIconstarIconstarIconstarIcon

5.00/5 (13投票s)

2021年7月5日

CPOL

17分钟阅读

viewsIcon

19660

downloadIcon

415

本文提供了实现网站登录机制所需的代码,无需使用第三方软件。

1. 引言 目录

在过去几年中,我一直在为一个非营利慈善组织构建和维护一个网站。直到最近,该网站包含非专有信息,例如:

  • 一个包含开放时间、会议日期、路线等的主页。
  • 一个允许访问以下项目的菜单:
    • 一个即将举行的活动轮播。
    • 组织设施中玩的游戏规则。
    • 组织的官员和下属组织的官员。
    • 当前和未来月份的日历。
    • 赞助商列表。
  • 响应菜单选择的 HTML 页面。

总而言之,这是一个相对简单的网站,易于构建和维护。

然而,会员们要求添加会议记录、损益表、组织文件(例如,章程、细则等)、文件草稿等。会员们表示,他们希望这些项目保密,不对网站的普通访客开放。会员们的愿望需要重新设计网站。

2. 要求 目录

对网站的新部分提出了一些要求。

  • 除托管平台提供的软件外,不得使用任何第三方软件。这有效地排除了所有承诺开箱即用登录解决方案的软件(包括微软)。
  • 实施仅限于
    • HTML
    • CSS
    • JavaScript
    • PHP - 用于 JavaScript 和 SQL 之间的接口
    • SQL - 仅用于数据库访问
    • PNG - 用于图形对象

3. 概述 目录

最初具有简单目录结构的网站现在必须划分为公共和私人部分。公共部分命名为“Public”,私人部分命名为“Members”,包含两者共有项目的部分命名为“Common”。最终的目录结构如下所示:

directory structure

请注意,目录名为大写,文件名为小写(主机使用 UNIX 的变体,因此区分大小写)。

为了实现一些更改,网站的着陆页被转换为一个重定向页面,将访客带到 Public 目录中的 index.html。这是通过以下方式实现的:

  <!DOCTYPE html>
  <html lang="en">
    <head>
      <title>Redirect</title>
      <meta http-equiv="refresh" 
            content="0; URL=Public/index.html" />
    </head>
    <body>
    </body>
  </html>

4. 重新设计 目录

第一个任务是修订网站菜单。

old menu

唯一需要更改的是在菜单中添加一个“仅限会员”项目。

revised menu

当访客选择仅限会员项目时,他将被引导至 member_login.html 页面。

5. HTML 目录

本节介绍 member_login.html 及其组件。

5.1. member_login.html 页面 目录

此页面由一个主页面着陆 <div> 和五个模态弹出 <div> 组成,这些 <div> 完成大部分工作。以下是 member_login.html 页面概述。

overview

该页面的 HTML 中包含

    <link rel="stylesheet" 
          href="../../Common/CSS/w3.css" /> 

    <link rel="stylesheet" 
          href="../../Common/CSS/member_login.css"
          type="text/css" 
          media="screen" />
    ⋮
    <script src="../../Common/Scripts/SHA256.js"></script>
    <script src="../../Common/Scripts/cookies.js"></script>
    <script src="../../Common/Scripts/mock.js"></script>
    <script src="../../Common/Scripts/member_login.js"></script>
    ⋮
    <script>
      window.onload = 
        function ( )
          {
          MemberLogin.initialize_login ( );
          if ( MemberLogin.already_logged_in ( ) )
            {
            document.location = "../../Members/members_area.html";
            }
          };
    </script>

有两个 CSS 条目

  • member_login.css - 在此项目期间开发。
  • w3.css - 来自 W3.CSS [^]。该 CSS 在整个组织网站中使用。

有四个外部 JavaScript 文件

  • SHA256.js - 用于哈希密码
  • cookies.js - 用于操作 cookie 的实用程序
  • mock.js - 创建一个模拟数据库,见下文
  • member_login.js - 在此项目期间开发,用于支持登录过程

member_login.html 页面首次加载时,会调用 onload 处理程序。该处理程序首先调用 MemberLogininitialize_login ( ) 入口点来初始化 MemberLogin 使用的全局变量。然后,onload 处理程序确定访客是否已经登录。成功登录后,会创建一个会话 cookie。如果 cookie 已经存在,则访客已经登录,现在被引导到 members_area.html。如果 cookie 不存在,onload 处理程序退出,从而将访客置于 main_page <div> 中。

由于 cookie 是会话 cookie,如果访客登录但随后退出浏览器,会话 cookie 将被浏览器删除。在这种情况下,访客将被要求再次登录。

5.1.1. 主页面 <div> 目录

main page <div> 向访客展示

login landing div

如果 window.onload 事件处理程序 确定访客已登录,则不会进入 main_page <div>。如果进入 main page <div>,它将为访客提供登录会员区或注册的功能。

main_page <div>登录注册按钮的点击处理程序中出现

  <popup-name>.style.display='block'

这是用于使各种弹出窗口出现的机制。在弹出窗口本身中,在取消按钮的点击处理程序中,出现

  <this-popup-name>.style.display='none'

这会导致当前弹出窗口关闭,将访客返回到他来的弹出窗口。

main_page <div> 中,访客可以选择登录会员区、注册以登录或取消进一步的操作。

当访客在登录过程的任何页面上点击取消时,访客总是返回到 main_page <div>member_login.html 页面的原始推荐人。

如果访客点击注册,访客将看到 member_login.html 页面的 instruction_popup <div>;如果访客点击登录,访客将看到 member_login.html 页面的 login_popup <div>

5.1.2. instructions_popup <div> 目录

instructions_popup <div> 是一个美化性的补充,它指导访客如何进行注册和登录过程。(令人惊讶的是,有这么多访客在没有阅读这些说明的情况下就联系我寻求帮助。)

instructions_popup <div> 向访客展示

instruction member login page

如果访客点击继续,访客将被引导至 member_login.html 页面的 member_id_popup <div>

5.1.3. member_id_popup <div> 目录

当提出仅限会员区时,有人担心普通访客可能能够访问组织的专有区域。解决方案是要求登录。然而,仅仅要求访客提供用户名和密码并不能达到预期效果。

该组织在国家层面为每位会员提供一张编号会员卡。尽管会员卡每年重新签发,但会员识别码是恒定的。由于通常只有会员知道他们的会员识别码,因此该号码成为将用户名和密码限制为仅会员的关键。通过一个相对简单的过程,本地组织的会员识别码从国家数据库下载并插入到组织网站上维护的数据库中。

member_id_popup <div> 需要的就是这个号码。

member_id_popup <div> 向访客展示

member id member login page

当访客输入会员 ID 并点击验证时,登录过程会在组织网站的数据库中查找提供的 ID。查找结果有三种:ID 被识别,ID 未被识别,或 ID 已被使用。在后两种情况下,访客会看到以下其中一项:

verification failed member login page id in use member login page

如果 ID 验证成功,访客将看到 register_popup <div>

5.1.4. register_popup <div> 目录

register_popup <div> 向访客展示

registration member login page

对于用户 ID密码,访客可以输入任何字母数字字符序列('a' 到 'z';'A' 到 'Z';和 '0' 到 '9')。提供的字符字符串长度必须在 6 到 64 个字符之间,且不包含空格或特殊字符。

访客输入的验证由 member_login.js 中的两个正则表达式常量 USER_ID_REGEX 和 PASSWORD_REGEX 指导。通过更改这些常量,可以修改用户名或密码可接受所需的输入。(注意:某些字符可能会干扰 PHP 或 SQL 执行。)

注册过程通过调用 MemberLogin 中的 register ( ) 入口点完成。该方法验证访客输入的数据,如果成功,则将访客的凭据添加到组织的数据库中。如果注册成功或访客在 main page <div> 中点击了登录,访客将被引导到 member_login.html 页面的 login_popup <div>

5.1.5. login_popup <div> 目录

login_popup <div> 向访客展示

login member login page

登录过程通过调用 MemberLogin 中的 login ( ) 入口点完成。该方法根据组织数据库中的凭据验证访客输入的凭据。访客有三次尝试成功登录。成功登录后,他将被允许进入会员区。

由于各种原因,用户可能希望更改密码。如果用户点击忘记密码?,用户将被引导至 member_login.html 页面的 reset_popup <div>

5.1.6. reset_popup <div> 目录

reset_popup <div> 向访客展示

reset popup

重置密码通过调用 MemberLogin 中的 reset_password ( ) 入口点完成。该方法确保用户 ID 和会员 ID 都已被使用。

6. 数据库 目录

包含会员登录数据的数据库维护在托管服务的数据库服务器上,名称为Members_DB。该数据库包含两个表和六个过程。

6.1.  目录

6.1.1. member_id 目录

member id

member_id 表包含注册过程中将接受的所有会员识别号。此表中的值源自国家组织网站上维护的数据。

不幸的是,在我的情况下,国家组织无法(或不愿)创建一个 Web 服务 [^] 来提供会员识别号的验证。因此,有必要下载一个 CSV [^] 数据库,对其进行操作,然后将操作后的值(会员 ID)上传到 member_id 表中。

6.1.2. member_user_salt_password 目录

member user salt password

当访客成功注册时,会在 member_user_salt_password 表中创建一个记录(行)。用户凭据在此表中维护。尽管 member_user_salt_password 表通过 user_id 访问,但属性 member_id 仍保留在表中,用于重置密码。希望更改密码的用户必须知道 user_idmember_id

6.2. 过程 目录

数据库过程用 SQL 编码。每个数据库过程都通过 PHP 函数访问,PHP 函数本身由 MemberLogin 模块中的 JavaScript 方法调用。JavaScript、PHP、SQL 和数据库之间的交互可以在此处找到。

6.2.1. delete_member 目录

delete_memberMemberLogin.member_deleted 引用,用于从 Member_DB.member_user_salt_password 表中删除现有成员。如果从 Member_DB.member_user_salt_password 删除成功,MemberLogin.member_deleted 返回 true;否则返回 false。

PROCEDURE `delete_member` ( IN `mid` VARCHAR ( 16 ), 
                            IN `uid` VARCHAR ( 64 ))
    NO SQL
BEGIN
    DELETE 
    FROM member_user_salt_password 
    WHERE member_id = mid 
    AND user_id = uid;
END

6.2.2. insert_a_member 目录

insert_a_memberMemberLogin.insert_a_member 引用,用于向 Member_DB.member_user_salt_password 表中插入新成员。如果插入成功,MemberLogin.insert_a_member 返回 true;否则返回 false。

PROCEDURE `insert_a_member` ( IN `member_id` VARCHAR ( 16 ), 
                              IN `user_id` VARCHAR ( 64 ), 
                              IN `salt` VARCHAR ( 12 ), 
                              IN `hashed_password` VARCHAR ( 64 ) )
    NO SQL
BEGIN
    INSERT INTO member_user_salt_password ( member_id, 
                                            user_id, 
                                            salt, 
                                            hashed_password )
    VALUES ( member_id, 
             user_id, 
             salt, 
             hashed_password ); 
END

6.2.3. member_exists 目录

member_existsMemberLogin.member_id_verified 引用,用于验证访客提供的会员 ID 是否存在于 Member_DB.member_id 表中。如果提供的 ID 在 Member_DB.member_id 表的会员 ID 集合中找到,MemberLogin.member_id_verified 返回 true;否则返回 false。

PROCEDURE `member_exists` ( IN `id` VARCHAR ( 16 ) )
    NO SQL
BEGIN
    SELECT * FROM member_id WHERE member_id = id;
END

6.2.4. member_id_already_in_use 目录

member_id_already_in_useMemberLogin.member_id_already_used 引用,用于确定访客提供的会员 ID 是否已存在于 Member_DB.member_user_salt_password 表中。如果找到提供的 ID,MemberLogin.member_id_already_used 返回 true;否则返回 false。

PROCEDURE `member_id_already_in_use` ( IN `id` VARCHAR ( 16 ) )
    NO SQL
BEGIN
    SELECT member_id 
    FROM member_user_salt_password 
    WHERE member_id = id;
END

6.2.5. retrieve_salt_hash 目录

retrieve_salt_hashMemberLogin.salt_hash_retrieved 引用,用于从 Member_DB.member_user_salt_password 表中检索与指定用户 ID 关联的盐和哈希密码。如果检索成功,MemberLogin.salt_hash_retrieved 返回 true;否则返回 false。MemberLogin.salt_hash_retrieved 将盐和哈希密码分别返回到全局变量 stored_saltstored_hashed_password 中。

PROCEDURE `retrieve_salt_hash` ( IN `id` VARCHAR ( 64 ) )
    NO SQL
BEGIN
    SELECT salt,
           hashed_password 
    FROM member_user_salt_password 
    WHERE user_id = id;
END

6.2.6. user_already_exists 目录

user_already_existsMemberLogin.user_already_exists 引用,用于确定指定的会员 ID 是否存在于 Member_DB.member_user_salt_password 表中。如果提供的会员 ID 在 Member_DB.member_user_salt_password 中找到,MemberLogin.user_already_exists 返回 true;否则返回 false。

PROCEDURE `user_already_exists` ( IN `id` VARCHAR ( 64 ) )
    NO SQL
BEGIN
    SELECT user_id 
    FROM member_user_salt_password 
    WHERE user_id = id;
END

7. JavaScript 目录

JavaScript 确实是将登录项目所有组件连接在一起的“胶水”。

有两个重要的外部 JavaScript 文件。

  • SHA256.js
  • member_login.js

7.1. SHA256.js 目录

SHA256.js 源自 Web 工具包 [^] 代码片段库。该脚本包含一个生成给定文本几乎唯一 256 位(32 字节)签名的生成器。有关详细信息,读者可参考 SHA-2 [^]。SHA256.jsmember_login.js 中包含的方法引用。

应用 SHA256 算法的结果不是加密。该结果无法“解密”。它被称为“单向”或“陷门”操作。这使其非常适合生成哈希密码。MemberLogin 中生成哈希的函数是

    hashed_password ( password,
                      salt ) 

其中 password 是要哈希的明文密码,salt 是一个包含从调用函数 string_salt 获得的随机字符串的字符串。

    hash = SHA256 ( salt + password );

在生成哈希密码时,将 salt [^] 前缀(或后缀)到密码是一种标准做法。hashed_password 返回 hash,调用函数将 salthash 都保存在数据库中。

7.2. member_login.js 目录

member_login.js 的内容在登录过程中被广泛使用。member_login.js 被构建为支持成员登录的实用程序函数模块。该模块名为 MemberLoginMemberLogin 导出以下公共属性(入口点)

  • already_logged_in
  • clear_error_message
  • hashed_password
  • initialize_login
  • insert_a_member
  • is_eligible
  • login_popup
  • login
  • password_hide_show
  • random_string
  • register_popup
  • register
  • reset_password
  • reset_popup
  • return_to_referrer
  • session_variables
  • set_keyboard_focus_to_id
  • string_salt

登录项目中有三个主要的弹出窗口。

  • 登录
  • Register
  • 重置密码

登录项目中有两个辅助弹出窗口。

  • 会员 ID
  • 说明

这些弹出窗口都位于 member_login.html 页面中单独的弹出 <div> 中,每个弹出窗口(除了自包含的说明弹出窗口)都会调用 member_login.js 中的一个或多个函数。

此外,还有六个 PHP 函数

  • delete_a_member
  • insert_a_member
  • member_exists
  • member_id_already_in_use
  • retrieve_salt_hash
  • user_exists

7.2.1. 登录弹出窗口 目录

login flow

 

7.2.2. 注册弹出窗口 目录

registration flow

 

7.2.3. 重置密码弹出窗口 目录

reset flow

 

7.2.4. 会员 ID 弹出窗口 目录

member id flow

 

7.2.5. 说明弹出窗口 目录

instructionsflow

 

7.3. PHP 目录

PHP 函数提供了 JavaScript 和数据库 SQL 过程之间的接口。交互关系如下图所示。

javascript PHP SQL table

在以下各节讨论的所有 PHP 函数中,所需的连接字符串由数据库服务器名称、数据库用户名、数据库密码和数据库名称组成。出于显而易见的原因,这些数据项将不在此处泄露。相反,将替换为以下内容:

$servername = "server-name"
$username = "database-user"
$password = "database-password"
$database = "database-name"

$connection = mysqli_connect ( $servername, 
                               $username, 
                               $password, 
                               $database );

托管服务通常会禁止从互联网直接访问 PHP 文件。

7.3.1. delete_a_member ( ) 目录

登录项目中没有可用的更新。相反,当需要修改用户数据时,修订过程是先删除再插入。例如,在 JavaScript 的 reset_password ( ) 方法中出现:

      if ( !member_deleted ( ) )
        {
        set_error_message ( 
            reset_error_message,
            "Either the Member ID or User ID is not recognized" );
        return;
        }

      session_variables.user_id = user_id;
      session_variables.salt = string_salt ( 12, "aA#" );
      session_variables.hash = hashed_password ( 
                                          password, 
                                          session_variables.salt );
      session_variables.password = password;
      session_variables.already_logged_in = false;

      if ( insert_a_member ( ) )
        {
        login_popup.style.display='block';
        reset_popup.style.display='none';
        }
      else 
        {
        set_error_message ( reset_error_message,
                            "Failed to reset password" );
        return;
        }

在这里,正在修改密码的成员的记录首先被删除,然后插入一个新的成员记录。

之前的图中可以看出,JavaScript 的 member_deleted ( ) 调用 PHP 的 delete_a_member ( ),而 JavaScript 的 insert_a_member ( ) 调用 PHP 的 insert_a_member ( )

    <?php // delete_a_member.php

    ini_set ( "display_errors", 1 );
    error_reporting ( E_ALL );

    $q = strval(htmlspecialchars($_GET['member_id']));
    $r = strval(htmlspecialchars($_GET['user_id']));

    $servername = "server-name"
    $username = "database-user"
    $password = "database-password"
    $database = "database-name"

    $connection = mysqli_connect ( $servername, 
                                   $username, 
                                   $password, 
                                   $database );
    if ( !$connection ) 
      {
      die ( "Connection failed: " . mysqli_connect_error ( ) );
      }

    $sql = "CALL delete_member('".$q."','".$r."')"; 
    mysqli_query ( $connection, $sql );

    echo mysqli_affected_rows ( $connection );

    mysqli_close ( $connection );

    ?>

7.3.2. insert_a_member ( ) 目录

    <?php // insert_a_member.php

    ini_set ( "display_errors", 1 );
    error_reporting ( E_ALL );

    $q = strval(htmlspecialchars($_GET['member_id']));
    $r = strval(htmlspecialchars($_GET['user_id']));
    $s = strval(htmlspecialchars($_GET['salt']));
    $t = strval(htmlspecialchars($_GET['hashed_password']));

    $servername = "server-name"
    $username = "database-user"
    $password = "database-password"
    $database = "database-name"

    $connection = mysqli_connect ( $servername, 
                                   $username, 
                                   $password, 
                                   $database );
    if ( !$connection ) 
      {
      die ( "Connection failed: " . mysqli_connect_error ( ) );
      }

    $sql = "CALL insert_a_member('".$q."','".$r."','".$s."','".$t."')" 
    if ( mysqli_query ( $connection, $sql ) )
      {
      echo "OK"
      } 
    else
      {
      echo mysqli_error ( $connection );
      }

    mysqli_close ( $connection );

    ? >

插入数据(不包括 member_id)的示例为

user_id      salt          hashed_password
gggustafson  LtNkIOr2ooLR  bb89891c2484965cc638d5eed159bdb795e0c48064d9769698

7.3.3. member_exists ( ) 目录

    <?php // member_exists.php

    ini_set ( "display_errors", 1 );
    error_reporting ( E_ALL );

    $q = strval(htmlspecialchars($_GET['q'])); // member id 

    $servername = "server-name"
    $username = "database-user"
    $password = "database-password"
    $database = "database-name"

    $connection = mysqli_connect ( $servername, 
                                   $username, 
                                   $password, 
                                   $database );
    if ( !$connection ) 
      {
      die ( "Connection failed: " . mysqli_connect_error ( ) );
      }

    $sql = "CALL member_exists ( '" .$q. "' )" 
    $result = mysqli_query ( $connection, $sql );

    if ( mysqli_num_rows ( $result )  > 0 ) 
      {
      while ( $row = mysqli_fetch_assoc ( $result ) ) 
        {
        echo $row [ "member_id" ];
        }
      } 
    else 
      {
      echo "0"
      }

    mysqli_close ( $connection );

    ? >

7.3.4. member_id_already_in_use ( ) 目录

    <?php // member_id_already_in_use.php

    ini_set ( "display_errors", 1 );
    error_reporting ( E_ALL );

    $q = strval(htmlspecialchars($_GET['q'])); // member id 

    $servername = "server-name"
    $username = "database-user"
    $password = "database-password"
    $database = "database-name"

    $connection = mysqli_connect ( $servername, 
                                   $username, 
                                   $password, 
                                   $database );
    if ( !$connection ) 
      {
      die ( "Connection failed: " . mysqli_connect_error ( ) );
      }

    $sql = "CALL member_id_already_in_use ( " .$q. " )"; 
    $result = mysqli_query ( $connection, $sql );

    if ( mysqli_num_rows ( $result ) > 0 ) 
      {
      while ( $row = mysqli_fetch_assoc ( $result ) ) 
        {
        echo $row [ "member_id" ];
        }
      } 
    else 
      {
      echo "0";
      }

    mysqli_close ( $connection );

    ?>

7.3.5. retrieve_salt_hash ( ) 目录

    <?php // retrieve_salt_hash.php

    ini_set ( "display_errors", 1 );
    error_reporting ( E_ALL );

    $q = strval(htmlspecialchars($_GET['q'])); // user id

    $servername = "server-name"
    $username = "database-user"
    $password = "database-password"
    $database = "database-name"

    $connection = mysqli_connect ( $servername, 
                                   $username, 
                                   $password, 
                                   $database );

    if ( mysqli_connect_errno ( ) ) 
      {
      printf ( "Connect failed: %s\r\n", 
               mysqli_connect_error ( ) );
      exit();
      }

    $sql = "CALL retrieve_salt_hash ( '" .$q. "' )"; 
    if ( $result = mysqli_query ( $connection, $sql ) ) 
      {
      if ( $result- >num_rows  > 0 )
        {
        while ( $row = $result- >fetch_row ( ) ) 
          {
          echo ( $row [ 0 ] ." ". $row [ 1 ] );
          }
        }
      else
        {
        echo "0";
        }

      $result- >close();
      }
    else
      {
      echo "0";
      }

    mysqli_close ( $connection );

    ? >

7.3.6. user_exists ( ) 目录

    <?php // user_exists.php

    ini_set ( "display_errors", 1 );
    error_reporting ( E_ALL );

    $q = strval(htmlspecialchars($_GET['q'])); // user id

    $servername = "server-name"
    $username = "database-user"
    $password = "database-password"
    $database = "database-name"

    $connection = mysqli_connect ( $servername, 
                                   $username, 
                                   $password, 
                                   $database );

    if ( !$connection ) 
      {
      die ( "Connection failed: " . mysqli_connect_error ( ) );
      }

    $sql = "CALL user_already_exists ( '" .$q. "' )"; 
    $result = mysqli_query ( $connection, $sql );

    if ( mysqli_num_rows ( $result )  > 0 ) 
      {
      while ( $row = mysqli_fetch_assoc ( $result ) ) 
        {
        echo $row [ "user_id" ];
        }
      } 
    else 
      {
      echo "0";
      }

    mysqli_close ( $connection );

    ? >

8. 使用代码 目录

下载中包含的软件是模拟数据库的版本。需要解决一些问题才能将其修改为在生产环境中执行。

8.1. 修改 PHP 文件 目录

前述,提供的 PHP 文件包含连接字符串的虚拟值

    $servername = "server-name"
    $username = "database-user"
    $password = "database-password"
    $database = "database-name"

这些赋值语句右侧的每个值都必须替换为执行环境中的实际值。

8.2. 关闭模拟 目录

详情请参阅模拟数据库一节。

8.3. 确保需要登录 目录

至关重要的是,非会员访客绝不能访问网站会员专区中的页面。这可以通过在会员专区中的每个页面上使用以下模板来实现。

下载中最小的页面是 under_construction.html。该页面包含上面讨论的重要 <div> 和 <script>。

<!DOCTYPE html >

<html lang="en">

  <head>

    <title>Under Construction</title>

    <meta http-equiv="Content-type" 
          content="text/html; charset=UTF-8" />
    <meta name="viewport" 
          content="width=device-width, initial-scale=1.0" />

    <link rel="icon" 
          type="image/x-icon" 
          href="../Common/Images/favicon.ico"/>

    <link rel="stylesheet" 
          href="../Common/CSS/w3.css" /> 

    <link rel="stylesheet" 
          href="../Common/CSS/members_area.css" /> 

  </head>

  <body class="member-area">

    <div id="contents" 
         class="content output w3-panel centered" 
         style="display:none;">

      <img src="../Common/Images/under_construction.png"
           alt="Under Construction" 
           width="300px" 
           height="300px"
           style="margin-top:10px;"
           />

    </div>

    <script src="../Common/Scripts/cookies.js"></script>
    <script src="../Common/Scripts/member_login.js"></script>

    <script>
      window.onload = 
        function ( )
          {
          if ( MemberLogin.already_logged_in ( ) )
            {
            document.getElementById ( 'contents' ).style.display = 
                                                              "block";
            }
          else
            {
            document.location = "./MemberLogin/member_login.html";
            }
          };
    </script>

  </body>

</html>

关注点

  • 文件 w3.css [^] 已从网络复制到文件 w3.css 中。这样做是为了最大程度地减少互联网损失。请注意,w3.css 是从众多文件中选择的,可以替换。
  • 实际的网页内容放置在 id 等于“content”且其 display 属性设置为 none 的 <div> 中。在初始进入时,这是所期望的。如果需要,页面的 onload 事件处理程序将更新属性值。
  • 两个 <script> 语句
        <script src="../Common/Scripts/cookies.js"></script>
        <script src="../Common/Scripts/member_login.js"></script>
    
    onload 事件处理程序执行所必需的。
  • onload 事件处理程序确定访客是否已登录。这通过测试由早期登录设置的会话 cookie 是否存在来实现。如果访客已登录,则 content <div> 的 display 属性将设置为 block,否则访客将被重定向到 member_login.html 页面。

 

9. 参考 目录

10. 下载 目录

本文顶部的下载文件包含实现本文所述登录机制所需的所有文件(以及本文)。其目录结构为:

download directory

我建议下载 ZIP 文件,然后将其解压到一个名为“Login”的新创建目录中。

10.1. 模拟数据库 目录

文件 member_login.js 包含执行登录所需的所有函数。然而,由于许多开发人员的机器上没有安装 PHP 和 SQL,因此在文件 mock.js 中包含了一个数据库模拟。该文件通过以下方式包含在 member_login.html 中:

    <script src="../../Common/Scripts/mock.js"></script>

在每个访问 PHP 函数的函数中出现

    if ( MOCK )
      {
      return ( Mock.<mock-code-to-execute> );
      }
    else

无论此片段出现在何处,它都从第一列开始,后面跟着一个在非模拟模式下执行的代码块。如果需要完全移除模拟,可以移除 if ( MOCK ) 块,只留下 else 后面的代码块。对于纯粹主义者,围绕 else 块的花括号也可以移除。

常量 MOCKmember_login.js 顶部声明

    var MOCK = true;

如果只是禁用模拟,则此常量只需设置为 false

如果需要模拟,请确保在 member_login.js 文件中将常量 MOCK 设置为 true。当进入 members_area.html 时,cookie LOGGEDIN_COOKIE_NAME 将不存在,访客将被引导至 member_login.html

模拟软件接受从 1 到 19(含)的奇数作为 member_id。这些值可以通过修改 mock.js 文件中的 initialize_mock_login_project_variables 函数来修改。

10.2. members_area.html 目录

在测试本文所述的登录机制时,只需打开 Members 目录中的 members_area.html。如果 cookie LOGGEDIN_COOKIE_NAME 不存在,访客将被重定向到 member_login.html。如果 cookie 存在,将显示以下内容。

members area

为了允许多次测试登录机制,包含了一个删除 Cookie 按钮。要移除该按钮,members_area.html 中被以下代码包围的代码应被移除(包括 HTML 注释)。

    <!-- start for debugging -->
    :
    <!-- end for debugging -->

应被移除(包括 HTML 注释)。

11. 结论 目录

本文提供了实现网站登录机制所需的代码,无需使用第三方软件。

12. 开发环境 目录

登录项目是在以下环境中开发的

Microsoft Windows 7 专业版 SP 1
Microsoft Visual Studio 2008 专业版 SP1
Microsoft Visual C# 2008
Microsoft .Net Framework Version 3.5 SP1

13. 历史 目录

07/05/2021 原文
© . All rights reserved.