Giải pháp thiết kế web động với PHP - p 49 pptx

10 290 0
Giải pháp thiết kế web động với PHP - p 49 pptx

Đang tải... (xem toàn văn)

Thông tin tài liệu

AUTHENTICATING USERS WITH A DATABASE 461 Registering new users in the database To register users in the database, you need to create a registration form that asks for a username and password. The processing script needs to validate the user input before inserting it in the database. MySQL returns an error if an attempt is made to insert a username thats already in use because the username column has been defined with a UNIQUE index. The script needs to detect the error and advise the user to choose a different username. PHP Solution 17-1: Creating a user registration form This PHP solution shows how to adapt the registration script from Chapter 9 to work with MySQL. It uses the Ps2_CheckPassword class from PHP Solution 9-6 and register_user_text.php from PHP Solution 9-7. If necessary, copy CheckPassword.php from the classes/completed folder to the classes/Ps2 folder, and use a copy of register_user_text.inc_02.php from the ch09 folder in place of register_user_text.php. You should also read the instructions in PHP Solutions 9-6 and 9-7 to understand how the original scripts work. 1. Copy register_db.php from the ch17 folder to a new folder called authenticate in the phpsols site root. The page contains the same basic user registration form as in Chapter 9 with a text input field for the username, a password field, another password field for confirmation, and a button to submit the data, as shown in the following screenshot. 2. Add the following code in a PHP block above the DOCTYPE declaration: if (isset($_POST['register'])) { $username = trim($_POST['username']); $password = trim($_POST['pwd']); $retyped = trim($_POST['conf_pwd']); require_once(' /includes/register_user_mysqli.inc.php'); } This is very similar to the code in PHP Solution 9-7. If the form has been submitted, the user input is stripped of leading and trailing whitespace and assigned to simple variables. Then, an CHAPTER 17 462 external file called register_user_mysqli.inc.php is included. If you plan to use PDO, name the include file register_user_pdo.inc.php instead. 3. The file that processes the user input is based on register_user_text.inc.php, which you created in Chapter 9. Make a copy of your original file, and save it in the includes folder as register_user_mysqli.inc.php or register_user_pdo.inc.php. Alternatively, copy register_user_text_02.php from the ch09 folder to the includes folder, and save it as register_user_mysqli.inc.php or register_user_pdo.inc.php. 4. In the file you have just copied and renamed, locate the conditional statement that begins like this (around line 22): if (!$errors) { // encrypt password, using username as salt $password = sha1($username.$password); Delete all the code inside the conditional statement (from line 23 to the line before the end). The contents of the file should now look like this: require_once(' /classes/Ps2/CheckPassword.php'); $usernameMinChars = 6; $errors = array(); if (strlen($username) < $usernameMinChars) { $errors[] = "Username must be at least $usernameMinChars characters."; } if (preg_match('/\s/', $username)) { $errors[] = 'Username should not contain spaces.'; } $checkPwd = new Ps2_CheckPassword($password, 10); $checkPwd->requireMixedCase(); $checkPwd->requireNumbers(2); $checkPwd->requireSymbols(); $passwordOK = $checkPwd->check(); if (!$passwordOK) { $errors = array_merge($errors, $checkPwd->getErrors()); } if ($password != $retyped) { $errors[] = "Your passwords don't match."; } if (!$errors) { } It doesnt matter if your script uses different values for $usernameMinChars and for the password strength settings. 5. The code that inserts the users details in the database goes inside the empty conditional statement at the bottom of the script. Begin by including the database connection file and creating a connection with read and write privileges. Download from Wow! eBook <www.wowebook.com> AUTHENTICATING USERS WITH A DATABASE 463 if (!$errors) { // include the connection file require_once('connection.inc.php'); $conn = dbConnect('write'); } The connection file is also in the includes folder, so you need only the filename. For PDO, add 'pdo' as the second argument to dbConnect(). 6. Next, use the time() function to get the current timestamp and assign it to $salt. Then concatenate the salt to the user-submitted password and encrypt them with the sha1() function. Amend the code like this: if (!$errors) { // include the connection file require_once('connection.inc.php'); $conn = dbConnect('write'); // create a salt using the current timestamp $salt = time(); // encrypt the password and salt $pwd = sha1($password . $salt); } 7. The final section of the code prepares and executes the prepared statement to insert the users details in the database. Because the username column has a UNIQUE index, the query fails if the username already exists. If that happens, the code needs to generate an error message. The code is different for MySQLi and PDO. For MySQLi, add the code highlighted in bold: if (!$errors) { // include the connection file require_once('connection.inc.php'); $conn = dbConnect('write'); // create a salt using the current timestamp $salt = time(); // encrypt the password and salt $pwd = sha1($password . $salt); // prepare SQL statement $sql = 'INSERT INTO users (username, salt, pwd) VALUES (?, ?, ?)'; $stmt = $conn->stmt_init(); $stmt = $conn->prepare($sql); // bind parameters and insert the details into the database $stmt->bind_param('sis', $username, $salt, $pwd); $stmt->execute(); if ($stmt->affected_rows == 1) { $success = "$username has been registered. You may now log in."; } elseif ($stmt->errno == 1062) { $errors[] = "$username is already in use. Please choose another  CHAPTER 17 464 username."; } else { $errors[] = 'Sorry, there was a problem with the database.'; } } The new code begins by binding the parameters to the prepared statement. The username and password are strings, but the salt is an integer, so the first argument to bind_param() is 'sis' (see “Embedding variables in MySQLi prepared statements” in Chapter 11) After the statement has been executed, the conditional statement checks the value of the affected_rows property. If its 1, the details have been inserted successfully. You need to check the value of affected_rows explicitly because its –1 if theres an error. Unlike some programming languages, PHP treats –1 as true . The alternative condition checks the value of the prepared statements errno property, which contains the MySQL error code. The code for a duplicate value in a column with a UNIQUE index is 1062. If that error code is detected, an error message is added to the $errors array asking the user to choose a different username. If a different error code is generated, a generic error message is added to the $errors array instead. The PDO version looks like this: if (!$errors) { // include the connection file require_once('connection.inc.php'); $conn = dbConnect('write', 'pdo'); // create a salt using the current timestamp $salt = time(); // encrypt the password and salt $pwd = sha1($password . $salt); // prepare SQL statement $sql = 'INSERT INTO users (username, salt, pwd) VALUES (:username, :salt, :pwd)'; $stmt = $conn->prepare($sql); // bind parameters and insert the details into the database $stmt->bindParam(':username', $username, PDO::PARAM_STR); $stmt->bindParam(':salt', $salt, PDO::PARAM_INT); $stmt->bindParam(':pwd', $pwd, PDO::PARAM_STR); $stmt->execute(); if ($stmt->rowCount() == 1) { $success = "$username has been registered. You may now log in."; } elseif ($stmt->errorCode() == 23000) { $errors[] = "$username is already in use. Please choose another  username."; } else { $errors[] = 'Sorry, there was a problem with the database.'; AUTHENTICATING USERS WITH A DATABASE 465 } } The prepared statement uses named parameters, which are bound to it by the bindParam() method, specifying the data type as string for the username and pwd columns, and as integer for salt. After the statement has been executed, the conditional statement uses the rowCount() method to check if the record has been created. If the prepared statement fails because the username already exists, the value generated by the errorCode() method is 23000. As noted in the previous chapter, PDO uses error codes defined by the ANSI SQL standard instead of those generated by MySQL. If the error code matches, a message is added to the $errors array asking the user to choose a different username. Otherwise, a generic error message is used. 8. All that remains is to add the code that displays the outcome in the registration page. Add the following code just before the opening <form> tag in register_db.php: <h1>Register user</h1> <?php if (isset($success)) { echo "<p>$success</p>"; } elseif (isset($errors) && !empty($errors)) { echo '<ul>'; foreach ($errors as $error) { echo "<li>$error</li>"; } echo '</ul>'; } ?> <form id="form1" method="post" action=""> 9. Save register_db.php, and load it in a browser. Test it by entering input that you know breaks the rules. If you make multiple mistakes in the same attempt, a bulleted list of error messages should appear at the top of the form, as shown in the next screenshot. CHAPTER 17 466 10. Now fill in the registration form correctly. You should see a message telling you that an account has been created for the username you chose. 11. Try registering the same username again. This time you should get a message similar to the one shown in the following screenshot: 12. Check your code, if necessary, against register_db_mysqli.php and register_user_mysqli.inc.php, or register_db_pdo.php and register_user_pdo.inc.php in the ch17 folder. Now that you have a username and password registered in the database, you need to create a login script. The ch17 folder contains a set of files that replicates the setup in PHP Solution 9-9: a login page and two password protected pages. PHP Solution 17-2: Authenticating a users credentials with a database This PHP solution shows how to authenticate a users credentials stored in a database. It involves querying the database to find the usernames salt and stored password and then encrypting the submitted password with the salt. If the result matches the stored password, the user is redirected to a restricted page. 1. Copy login_db.php, menu_db.php, and secretpage_db.php from the ch17 folder to the authenticate folder. Also copy logout_db.inc.php and session_timeout_db.inc.php from the ch17 folder to the includes folder. This sets up the same basic test platform as in Chapter 9. The only difference is that the links have been changed to redirect to the authenticate folder. 2. In login_db.php add the following code in a PHP block above the DOCTYPE declaration: $error = ''; if (isset($_POST['login'])) { session_start(); $username = trim($_POST['username']); $password = trim($_POST['pwd']); // location to redirect on success $redirect = 'http://localhost/phpsols/authenticate/menu_db.php'; require_once(' /includes/authenticate_mysqli.inc.php'); } AUTHENTICATING USERS WITH A DATABASE 467 This follows a similar pattern to the code in the login form in Chapter 9. It begins by initializing $error as an empty string. The conditional statement initiates a session if the form has been submitted. Whitespace is trimmed from the user input fields, and the location of the page the user will be redirected to on success is stored in a variable. Finally, the authentication script, which youll build next, is included. If youre using PDO, use authenticate_pdo.inc.php as the processing script. 3. Create a new file called authenticate_mysqli.inc.php or authenticate_pdo.inc.php, and save it in the includes folder. The file will contain only PHP script, so strip out any HTML markup. 4. Include the database connection file, create a connection to the database with the read-only account, and use a prepared statement to fetch the users details. For MySQLi use the following code: <?php require_once('connection.inc.php'); $conn = dbConnect('read'); // get the username's details from the database $sql = 'SELECT salt, pwd FROM users WHERE username = ?'; // initialize and prepare statement $stmt = $conn->stmt_init(); $stmt->prepare($sql); // bind the input parameter $stmt->bind_param('s', $username); // bind the result, using a new variable for the password $stmt->bind_result($salt, $storedPwd); $stmt->execute(); $stmt->fetch(); This is a fairly straightforward SELECT query using a MySQLi prepared statement. The username is a string, so the first argument to bind_param() is 's'. The results of the query are bound to $salt and $storedPwd. You need to use a new variable for the stored password to avoid overwriting the password submitted by the user. After the statement has been executed, the fetch() method gets the result. For PDO, use the following code instead: <?php require_once('connection.inc.php'); $conn = dbConnect('read', 'pdo'); // get the username's details from the database $sql = 'SELECT salt, pwd FROM users WHERE username = :username'; // prepare statement $stmt = $conn->prepare($sql); // bind the input parameter $stmt->bindParam(':username', $username, PDO::PARAM_STR); // bind the result, using a new variable for the password CHAPTER 17 468 $stmt->bindColumn(1, $salt); $stmt->bindColumn(2, $storedPwd); $stmt->execute(); $stmt->fetch(); This code does the same as the MySQLi version, but using PDO syntax. 5. Once you have retrieved the usernames details, you need to encrypt the password entered by the user by combining it with the salt and passing them both to sha1(). You can then compare the result to the stored version of the password, which was similarly encrypted at the time of registration. If they match, create the session variables to indicate a successful login and the time the session began, regenerate the session ID, and redirect to the restricted page. Otherwise, store an error message in $error. Insert the following code after the code you entered in the preceding step. Its the same for both MySQLi and PDO. // encrypt the submitted password with the salt // and compare with stored password if (sha1($password . $salt) == $storedPwd) { $_SESSION['authenticated'] = 'Jethro Tull'; // get the time the session started $_SESSION['start'] = time(); session_regenerate_id(); header("Location: $redirect"); exit; } else { // if no match, prepare error message $error = 'Invalid username or password'; } As in Chapter 9, the value of $_SESSION['authenticated'] is of no real importance. 6. Save authenticate_mysqli.inc.php or authenticate_pdo.inc.php, and test login_db.php by logging in with the username and password that you registered at the end of PHP Solution 17-1. The login process should work in exactly the same way as Chapter 9. The difference is that all the details are stored more securely in a database, and each user has a unique and probably unguessable salt. You can check your code, if necessary, against login_mysqli.php and authenticate_mysqli.inc.php, or login_pdo.php and authenticate_pdo.inc.php in the ch17 folder. If you encounter problems, use echo to display the values of the freshly encrypted password and the stored version. The most common mistake is creating too narrow a column for the encrypted password in the database. It must be 40 characters wide. AUTHENTICATING USERS WITH A DATABASE 469 Although storing an encrypted password in a database is more secure than using a text file, the password is sent from the users browser to the server in plain, unencrypted text. This is adequate for most websites, but if you need a high level of security, the login and access to subsequent pages should be made through a Secure Sockets Layer (SSL) connection. Using two-way encryption The main differences in setting up user registration and authentication for two-way encryption are that the password needs to be stored in the database as a binary object using the BLOB data type (see “Storing binary data” in Chapter 10 for more information), and that the comparison between the encrypted passwords takes place in the SQL query, rather than in the PHP script. Although you can use a salt with the password, doing so involves querying the database twice when logging in: first to retrieve the salt and then to verify the password with the salt. To keep things simple, Ill show you how to implement two-way encryption without a salt. Creating the table to store users details In phpMyAdmin, create a new table called users_2way in the phpsols database. It needs three columns (fields) with the settings listed in Table 17-2. Table 17-2. Settings for the users_2way table Field Type Length/Values Attributes Null Index A_I user_id INT UNSIGNED Deselected PRIMARY Selected username VARCHAR 15 Deselected UNIQUE pwd BLOB Deselected Registering new users The MySQL AES_ENCRYPT() function takes two arguments: the value to be encrypted and an encryption key. The encryption key can be any string of characters you choose. For the purposes of this example, I have chosen takeThisWith@PinchOfSalt, but a random series of alphanumeric characters and symbols would be more secure. The basic registration scripts for one-way and two-way encryption are the same. The only difference lies in the section that inserts the users data into the database. The following scripts embed the encryption key directly in the page. If you have a private folder outside the server root, its a good idea to define the key in an include file and store it in your private folder. The code for MySQLi looks like this (its in register_2way_mysqli.inc.php in the ch17 folder): CHAPTER 17 470 if (!$errors) { // include the connection file require_once('connection.inc.php'); $conn = dbConnect('write'); // create a key $key = 'takeThisWith@PinchOfSalt'; // prepare SQL statement $sql = 'INSERT INTO users_2way (username, pwd) VALUES (?, AES_ENCRYPT(?, ?))'; $stmt = $conn->stmt_init(); $stmt = $conn->prepare($sql); // bind parameters and insert the details into the database $stmt->bind_param('sss', $username, $password, $key); $stmt->execute(); if ($stmt->affected_rows == 1) { $success = "$username has been registered. You may now log in."; } elseif ($stmt->errno == 1062) { $errors[] = "$username is already in use. Please choose another username."; } else { $errors[] = 'Sorry, there was a problem with the database.'; } } For PDO, it looks like this (see register_2way_pdo.inc.php in the ch16 folder): if (!$errors) { // include the connection file require_once('connection.inc.php'); $conn = dbConnect('write', 'pdo'); // create a key $key = 'takeThisWith@PinchOfSalt'; // prepare SQL statement $sql = 'INSERT INTO users_2way (username, pwd) VALUES (:username, AES_ENCRYPT(:pwd, :key))'; $stmt = $conn->prepare($sql); // bind parameters and insert the details into the database $stmt->bindParam(':username', $username, PDO::PARAM_STR); $stmt->bindParam(':pwd', $password, PDO::PARAM_STR); $stmt->bindParam(':key', $key, PDO::PARAM_STR); $stmt->execute(); if ($stmt->rowCount() == 1) { $success = "$username has been registered. You may now log in."; } elseif ($stmt->errorCode() == 23000) { $errors[] = "$username is already in use. Please choose another username."; } else { $errors[] = 'Sorry, there was a problem with the database.'; } } . script from Chapter 9 to work with MySQL. It uses the Ps2_CheckPassword class from PHP Solution 9-6 and register_user_text .php from PHP Solution 9-7 . If necessary, copy CheckPassword .php from. a PHP block above the DOCTYPE declaration: if (isset($_POST['register'])) { $username = trim($_POST['username']); $password = trim($_POST['pwd']); $retyped. trim($_POST['username']); $password = trim($_POST['pwd']); // location to redirect on success $redirect = 'http://localhost/phpsols/authenticate/menu_db .php& apos;; require_once('

Ngày đăng: 06/07/2014, 19:20

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan