1. Trang chủ
  2. » Công Nghệ Thông Tin

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

10 236 0

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

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 10
Dung lượng 487,14 KB

Nội dung

PAGES THAT REMEMBER: SIMPLE LOGIN AND MULTIPAGE FORMS 261 If the password fails either test or both, the $_errors property contains at least one element, which PHP treats as intrinsicly true. The final line in the check() method uses the $_errors property as the condition with the ternary operator. If any errors are found, the check() method returns false indicating that the password failed validation. Otherwise, it returns true. The getErrors() public method simply returns the array of error messages. 4. Save CheckPassword.php, and switch to register.php. 5. In register.php, create a Ps2_CheckPassword object, passing $password as the argument. Then call the check() method and handle the result like this: require_once(' /classes/Ps2/CheckPassword.php'); $checkPwd = new Ps2_CheckPassword($password); $passwordOK = $checkPwd->check(); if ($passwordOK) { $result = array('Password OK'); } else { $result = $checkPwd->getErrors(); } 6. The second argument to the Ps2_CheckPassword constructor is optional, so leaving it out sets the minimum number of characters to the default 8. The result of the check() method is assigned to $passwordOK. If it returns true, a single-element array reporting that the password is OK is assigned to $result. Otherwise, the getErrors() method is used to retrieve the array of errors from the $checkPwd object. The single-element array will be used only to test the class. Once testing is complete, it will be replaced by the script that registers the user. 7. Add the following PHP code block just above the form in the body of the page: <h1>Register User</h1> <?php if (isset($result)) { echo '<ul>'; foreach ($result as $item) { echo "<li>$item</li>"; } echo '</ul>'; } ?> <form action="" method="post" id="form1"> This displays the results of the password test as an unordered list after the form has been submitted. CHAPTER 9 262 8. Save register.php, and load it in a browser. Test the Ps2_CheckPassword class by clicking the Register button without filling in any of the fields. You should see a message informing you that the password requires a minimum of eight characters. 9. Try it with a password that contains eight characters. You should see Password OK. 10. Try a password with at least eight characters but insert a space in the middle. Youll be warned that no spaces are permitted. 11. Try one with fewer than eight characters but with a space in the middle. Youll see the following warnings: 12. Change the code in register.php to pass the optional second argument to the Ps2_CheckPassword constructor, and set the minimum number of characters to 10: $checkPwd = new Ps2_CheckPassword($password, 10); 13. Save and test the page again. If you encounter any problems, compare your code with register_02.php and CheckPassword_01.php in the ch09 folder. 14. Assuming that your code is working, add to the class definition in CheckPassword.php the public methods to set the password strength. Where you put them inside the class makes no difference technically (as long as theyre inside the curly braces), but my preference is to put public methods in the same order as theyre used. You need to set the options before calling the check() method, so insert the following code between the constructor and check() method definitions: public function requireMixedCase() { $this->_mixedCase = true; } public function requireNumbers($num = 1) { if (is_numeric($num) && $num > 0) { $this->_minimumNumbers = (int) $num; } } public function requireSymbols($num = 1) { Download from Wow! eBook <www.wowebook.com> PAGES THAT REMEMBER: SIMPLE LOGIN AND MULTIPAGE FORMS 263 if (is_numeric($num) && $num > 0) { $this->_minimumSymbols = (int) $num; } } The code is pretty straightforward. The requireMixedCase() method takes no arguments and resets the $_mixedCase property to true. The other two methods take one argument, check that its a number greater that 0, and assign it to the relevant property. The (int) casting operator ensures that its an integer. You first met the casting operator in PHP Solution 6-4. The value of $num sets the minimum amount of numbers or nonalphanumeric symbols the password must contain. By default, the value is set to 1, making the argument optional. 15. The check() method needs to be updated to perform the necessary checks for these strength criteria. Amend the code like this: public function check() { if (preg_match('/\s/', $this->_password)) { $this->_errors[] = 'Password cannot contain spaces.'; } if (strlen($this->_password) < $this->_minimumChars) { $this->_errors[] = "Password must be at least $this->_minimumChars  characters."; } if ($this->_mixedCase) { $pattern = '/(?=.*[a-z])(?=.*[A-Z])/'; if (!preg_match($pattern, $this->_password)) { $this->_errors[] = 'Password should include uppercase and lowercase  characters.'; } } if ($this->_minimumNumbers) { $pattern = '/\d/'; $found = preg_match_all($pattern, $this->_password, $matches); if ($found < $this->_minimumNumbers) { $this->_errors[] = "Password should include at least  $this->_minimumNumbers number(s)."; } } if ($this->_minimumSymbols) { $pattern = "/[-!$%^&*(){}<>[\]'" . '"|#@:;.,?+=_\/\~]/'; $found = preg_match_all($pattern, $this->_password, $matches); if ($found < $this->_minimumSymbols) { $this->_errors[] = "Password should include at least  $this->_minimumSymbols nonalphanumeric character(s)."; } } return $this->_errors ? false : true; } CHAPTER 9 264 Each of the three new conditional statements is run only if the equivalent public method is called before the check() method. Each one stores a regular expression as $pattern and then uses preg_match() or preg_match_all() to test the password. If the $_mixedCase property is set to true, the regular expression and password are passed to preg_match() to look for at least one lowercase letter and one uppercase letter in any position in the password. The $_minimumNumbers and $_minimumSymbols properties are set to 0 by default. If theyre reset to a positive number, the regular expression and password are passed to the preg_match_all() function to find how many times the regex matches. The function requires three arguments: the regex, the string to be searched, and a variable to store the matches. And it returns the number of matches found. In this case, all youre interested in is the number of matches. The variable that stores the matches is discarded. The horrendous $pattern in the last conditional statement is actually a regex created by concatenating a single-quoted string to a double-quoted one. This is necessary to include single and double quotation marks in the permitted symbols. I have included most nonalphanumeric symbols on an English keyboard. If you want to add others, put them just before the final closing square bracket like this: $pattern = "/[-!$%^&*(){}<>[\]'" . '"|#@:;.,?+=_\/\~£¦]/'; 16. Save CheckPassword.php, and test the updated class by calling the new methods in register.php. For example, the following requires the password to have a minimum of 10 characters, at least one uppercase and one lowercase letter, two numbers, and one nonalphanumeric symbol: $checkPwd = new Ps2_CheckPassword($password, 10); $checkPwd->requireMixedCase(); $checkPwd->requireNumbers(2); $checkPwd->requireSymbols(); $passwordOK = $checkPwd->check(); It doesnt matter which order you call the new methods, as long as theyre after the constructor and before the call to the check() method. Use a variety of combinations to enforce different strengths of password. If necessary, check your code against register_03.php and CheckPassword_02.php in the ch09 folder. When developing the code for this chapter, I originally designed the password checker as a function. The basic code inside the function was the same, but I decided to convert it into a class to make it more flexible and easier to use. The problem with the function was that it needed a large number of arguments to set the different options, and it was difficult to remember which order they came in. There was also the difficulty of handling the result. If there were no errors, the function returned true; but if any errors were found, it returned the array of error messages. Since PHP treats an array with elements as implicitly true, this meant using the identical operator (three equal signs—see Table 3-5) to check whether the result was a Boolean true. PAGES THAT REMEMBER: SIMPLE LOGIN AND MULTIPAGE FORMS 265 Converting the code to a class eliminated these problems. The public methods to set the options have intuitive names and can be set in any order—or not at all. And the result is always a Boolean true or false, because a separate method retrieves the array of error messages. It involved writing more code, but the improvements made it worthwhile. PHP Solution 9-7: Creating a file-based user registration system This PHP solution creates a simple user registration system that encrypts passwords with SHA-1 and a salt. It uses the Ps2_CheckPassword class from PHP Solution 9-6 to enforce minimum strength requirements. Further checks ensure that the username contains a minimum number of characters and that the user has retyped the password correctly in a second field. The user credentials are stored in a plain text file, which must be outside the web servers document root. The instructions assume you have set up a private folder that PHP has write access to, as described in Chapter 7. Its also assumed youre familiar with “Appending content with fopen()” in the same chapter. Continue working with the files from the preceding PHP solution. Alternatively, use register_03.php in the ch09 folder and CheckPassword.php in the classes/completed folder. 1. Create a file called register_user_text.inc.php in the includes folder, and strip out any HTML inserted by your script editor. 2. Cut the following code from register.php (it doesnt matter if your settings for the Ps2_CheckPassword object are different): require_once(' /classes/Ps2/CheckPassword.php'); $checkPwd = new Ps2_CheckPassword($password, 10); $checkPwd->requireMixedCase(); $checkPwd->requireNumbers(2); $checkPwd->requireSymbols(); $passwordOK = $checkPwd->check(); if ($passwordOK) { $result = array('Password OK'); } else { $result = $checkPwd->getErrors(); } 3. At the end of the remaining script above the DOCTYPE declaration in register.php, create a variable for the location of the text file that will be used to store the user credentials, and include register_user_text.inc.php. The code in the PHP block at the top of register.php should now look like this: if (isset($_POST['register'])) { $username = trim($_POST['username']); $password = trim($_POST['pwd']); $retyped = trim($_POST['conf_pwd']); $userfile = 'C:/private/encrypted.txt'; require_once(' /includes/register_user_text.inc.php'); } CHAPTER 9 266 The text file for the user credentials doesnt exist yet. It will be created automatically when the first user is registered. Amend the path to the private folder to match your own setup if necessary. 4. In register_user_text.inc.php, paste the code you cut from register.php in step 2, and add the following code immediately after the command that includes the class definition: 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); The first two lines of new code specify the minimum number of characters in the username and initialize an empty array for error messages. The rest of the new code checks the length of the username and tests whether it contains any spaces. The conditional statements use the same code as in the Ps2_CheckPassword class. 5. Amend the code at the bottom of register_user_text.inc.php like this: $passwordOK = $checkPwd->check(); if (!$passwordOK) { $errors = array_merge($errors, $checkPwd->getErrors()); } if ($password != $retyped) { $errors[] = "Your passwords don't match."; } if ($errors) { $result = $errors; } else { $result = array('All OK'); } This adds the logical Not operator to the conditional statement that tests the value of $passwordOK. If the password fails to validate, array_merge() is used to merge the result of $checkPwd->getErrors() with the existing $errors array. The next conditional statement compares $password with $retyped and adds an error message to the $errors array if they dont match. If any errors are discovered, the final conditional statement assigns the $errors array to $result. Otherwise, a single-element array is assigned to $result, reporting that all is OK. Again, this is only for testing purposes. Once you have checked your code, the script that registers the user will replace the final conditional statement. PAGES THAT REMEMBER: SIMPLE LOGIN AND MULTIPAGE FORMS 267 6. Save register_user_text.inc.php and register.php, and test the form again. Leave all the fields blank and click Register. You should see the following error messages: 7. Try a variety of tests to make sure your validation code is working. If you have problems, compare your code with register_user_text.inc_01.php and register_04.php in the ch09 folder. Assuming that your code is working, youre ready to create the registration part of the script. Lets pause to consider what the main script needs to do. First, you need to encrypt the password by combining it with the username as a salt. Then, before writing the details to a text file, you must check whether the username is unique. This presents a problem of which mode to use with fopen(). The various fopen() modes are described in Chapter 7. Ideally, you want the internal pointer at the beginning of the file so that you can loop through existing records. The r+ mode does this, but the operation fails unless the file already exists. You cant use w+, because it deletes existing content. You cant use x+ either, because it fails if a file of the same name already exists. That leaves a+ as the only option with the flexibility you need: it creates the file if necessary and lets you read and write. The file is empty the first time you run the script (you can tell because the filesize() function returns 0), so you can go ahead and write the details. If filesize() doesnt return 0, you need to reset the internal pointer and loop through the records to see if the username is already registered. If theres a match, you break out of the loop and prepare an error message. If there isnt a match by the end of the loop, you not only know its a new username, you also know youre at the end of the file. So, you write a new line followed by the new record. Now that you understand the flow of the script, you can insert it into register_user_text.inc.php. 8. Delete the following code at the bottom of register_user_text.inc.php: if ($errors) { $result = $errors; } else { $result = array('All OK'); } 9. Replace it with the following code: if (!$errors) { // encrypt password, using username as salt CHAPTER 9 268 $password = sha1($username.$password); // open the file in append mode $file = fopen($userfile, 'a+'); // if filesize is zero, no names yet registered // so just write the username and password to file if (filesize($userfile) === 0) { fwrite($file, "$username, $password"); $result = "$username registered."; } else { // if filesize is greater than zero, check username first // move internal pointer to beginning of file rewind($file); // loop through file one line at a time while (!feof($file)) { $line = fgets($file); // split line at comma, and check first element against username $tmp = explode(', ', $line); if ($tmp[0] == $username) { $result = "$username taken - choose a different username."; break; } } // if $result not set, username is OK if (!isset($result)) { // insert line break followed by username, comma, and password fwrite($file, PHP_EOL . "$username, $password"); $result = "$username registered."; } // close the file fclose($file); } } The preceding explanation and inline comments should help you follow the script. 10. Windows, Mac OS X, and Linux use different characters to create a new line, so the script uses the PHP_EOL constant introduced in Chapter 7 to insert a line break in a platform-neutral way. The registration script stores the outcome as a string in $result. Amend the code in the body of register.php to display the result or the error messages like this: <?php if (isset($result) || isset($errors)) { echo '<ul>'; if (!empty($errors)) { foreach ($errors as $item) { echo "<li>$item</li>"; } } else { echo "<li>$result</li>"; PAGES THAT REMEMBER: SIMPLE LOGIN AND MULTIPAGE FORMS 269 } echo '</ul>'; } ?> This loops through the $errors array if its not empty. Otherwise, it displays the value of $result as a single bulleted item. 11. Save both register_user_text.inc.php and register.php, and test the registration system. Try registering the same username more than once. You should see a message informing you the username is taken and asking you to choose another. 12. Open encrypted.txt. You should see the usernames in plain text, but the passwords have been encrypted. Even if you choose the same password for two different users, the encrypted version is different because of the password being combined with the username as a salt. Figure 9-4 shows two users that were both registered with the password Ps2_Chapter9. Figure 9-4. Using a salt produces completely different encryptions of the same password. If necessary, check your code against register_user_text.inc_02.php and register_05.php in the ch09 folder. Most of the code in register_user_text.php is generic. All you need to do to use it with any registration form is define $username, $password, $retyped, and $userfile before including it, and capture the results using $errors and $result. The only changes you might need to make to the external file are in setting the minimum number of characters in the username and the password strength. Those settings are defined at the top of the file, so theyre easy to access and adjust. PHP Solution 9-8: Using an encrypted login Now that you have encrypted passwords, you need to change the login form to handle the new setup. All thats necessary is to select the text file that contains the encrypted passwords and to encrypt the password before comparing it with the one stored in the file. 1. Open login.php from PHP Solution 9-3, or use login_01.php from the ch09 folder. Amend the code like this: $username = trim($_POST['username']); $password = sha1($username . $_POST['pwd']); // location of usernames and passwords $userlist = 'C:/private/encrypted.txt'; This trims whitespace from the username. The next line adds the username to the front of the password before passing it to sha1() for encryption. Finally, the file that stores the user credentials is changed to the encrypted version. CHAPTER 9 270 2. Save login.php, and test it. It should work the same as before but be more secure. Check your code if necessary with login_02.php in the ch09 folder. PHP Solutions 9-3 to 9-8 build a simple, yet effective, user authentication system that doesnt require a database back end. However, it does have its limitations. Above all, its essential that the text file containing the usernames and passwords be outside the server root. Even though the passwords are encrypted, knowing the usernames reduces the effort that an attacker needs to try to break through your security. Another weakness is that the salt is the username. Ideally, you should create a random salt for each password, but you need to store it somewhere. If its in the same file as the usernames, they would both be exposed at the same time. Using a database for user authentication gets around many of these problems. It involves more work, but is likely to be more secure. Also, once you get more than a few records, querying a database is usually much faster than looping through a text file line by line. Chapter 17 covers user authentication with a database. Setting a time limit on sessions By default, PHP sets the lifetime of the session cookie on the users computer to 0, which keeps the session active until the user logs out or the browser is closed. You can make the session timeout earlier through a call to ini_set(), the function that allows you to change some PHP configuration directives on the fly. As soon as the session starts, pass the directive session.cookie_lifetime as the first argument and a string containing the number of seconds you want the cookie to remain active as the second argument. For example, you could limit the session cookies lifetime to ten minutes like this: session_start(); ini_set('session.cookie_lifetime', '600'); Although this is effective, it has two drawbacks. First, the expiration is set relative to the time on the server, not the users computer. If the users computer clock is wrong, the cookie might be out of date immediately, or it might persist much longer than you anticipate. The other problem is that the user might be automatically logged out without explanation. The next PHP solution offers a user-friendlier approach. PHP Solution 9-9: Ending a session after a period of inactivity This PHP solution shows how to end a session if a user doesnt do anything that triggers a page to load after a specified period. When the session first starts, typically when the user logs in, the current time is stored in a session variable. Each time the user loads a page, the session variable is compared with the current time. If the difference is greater than a predetermined limit, the session and its variables are destroyed. Otherwise, the variable is updated to the current time. These instructions assume you have set up the login system in PHP Solutions 9-3 to 9-8. 1. You need to store the current time after the users credentials have been authenticated but before the script redirects the user to the restricted part of the site. Locate the following section of code in authenticate.inc.php (around lines 12–16), and insert the new code highlighted in bold as follows: if ($tmp[0] == $username && rtrim($tmp[1]) == $password) { $_SESSION['authenticated'] = 'Jethro Tull'; $_SESSION['start'] = time(); . require_once(' /classes/Ps2/CheckPassword .php& apos;); $checkPwd = new Ps2_CheckPassword($password); $passwordOK = $checkPwd->check(); if ($passwordOK) { $result = array('Password OK');. require_once(' /classes/Ps2/CheckPassword .php& apos;); $checkPwd = new Ps2_CheckPassword($password, 10); $checkPwd->requireMixedCase(); $checkPwd->requireNumbers(2); $checkPwd->requireSymbols();. trim($_POST['pwd']); $retyped = trim($_POST['conf_pwd']); $userfile = 'C:/private/encrypted.txt'; require_once(' /includes/register_user_text.inc .php& apos;); } CHAPTER

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

TỪ KHÓA LIÊN QUAN