答案 0 :(得分:-2)
你走了......完成了!
github.com/panique/php-login-one-file
<?php
/**
* Class OneFileLoginApplication
*
* An entire php application with user registration, login and logout in one file.
* Uses very modern password hashing via the PHP 5.5 password hashing functions.
* This project includes a compatibility file to make these functions available in PHP 5.3.7+ and PHP 5.4+.
*
* @author Panique
* @link https://github.com/panique/php-login-one-file/
* @license http://opensource.org/licenses/MIT MIT License
*/
class OneFileLoginApplication
{
/**
* @var string Type of used database (currently only SQLite, but feel free to expand this with mysql etc)
*/
private $db_type = "sqlite"; //
/**
* @var string Path of the database file (create this with _install.php)
*/
private $db_sqlite_path = "./users.db";
/**
* @var object Database connection
*/
private $db_connection = null;
/**
* @var bool Login status of user
*/
private $user_is_logged_in = false;
/**
* @var string System messages, likes errors, notices, etc.
*/
public $feedback = "";
/**
* Does necessary checks for PHP version and PHP password compatibility library and runs the application
*/
public function __construct()
{
if ($this->performMinimumRequirementsCheck()) {
$this->runApplication();
}
}
/**
* Performs a check for minimum requirements to run this application.
* Does not run the further application when PHP version is lower than 5.3.7
* Does include the PHP password compatibility library when PHP version lower than 5.5.0
* (this library adds the PHP 5.5 password hashing functions to older versions of PHP)
* @return bool Success status of minimum requirements check, default is false
*/
private function performMinimumRequirementsCheck()
{
if (version_compare(PHP_VERSION, '5.3.7', '<')) {
echo "Sorry, Simple PHP Login does not run on a PHP version older than 5.3.7 !";
} elseif (version_compare(PHP_VERSION, '5.5.0', '<')) {
require_once("libraries/password_compatibility_library.php");
return true;
} elseif (version_compare(PHP_VERSION, '5.5.0', '>=')) {
return true;
}
// default return
return false;
}
/**
* This is basically the controller that handles the entire flow of the application.
*/
public function runApplication()
{
// check is user wants to see register page (etc.)
if (isset($_GET["action"]) && $_GET["action"] == "register") {
$this->doRegistration();
$this->showPageRegistration();
} else {
// start the session, always needed!
$this->doStartSession();
// check for possible user interactions (login with session/post data or logout)
$this->performUserLoginAction();
// show "page", according to user's login status
if ($this->getUserLoginStatus()) {
$this->showPageLoggedIn();
} else {
$this->showPageLoginForm();
}
}
}
/**
* Creates a PDO database connection (in this case to a SQLite flat-file database)
* @return bool Database creation success status, false by default
*/
private function createDatabaseConnection()
{
try {
$this->db_connection = new PDO($this->db_type . ':' . $this->db_sqlite_path);
return true;
} catch (PDOException $e) {
$this->feedback = "PDO database connection problem: " . $e->getMessage();
} catch (Exception $e) {
$this->feedback = "General problem: " . $e->getMessage();
}
return false;
}
/**
* Handles the flow of the login/logout process. According to the circumstances, a logout, a login with session
* data or a login with post data will be performed
*/
private function performUserLoginAction()
{
if (isset($_GET["action"]) && $_GET["action"] == "logout") {
$this->doLogout();
} elseif (!empty($_SESSION['user_name']) && ($_SESSION['user_is_logged_in'])) {
$this->doLoginWithSessionData();
} elseif (isset($_POST["login"])) {
$this->doLoginWithPostData();
}
}
/**
* Simply starts the session.
* It's cleaner to put this into a method than writing it directly into runApplication()
*/
private function doStartSession()
{
if(session_status() == PHP_SESSION_NONE) session_start();
}
/**
* Set a marker (NOTE: is this method necessary ?)
*/
private function doLoginWithSessionData()
{
$this->user_is_logged_in = true; // ?
}
/**
* Process flow of login with POST data
*/
private function doLoginWithPostData()
{
if ($this->checkLoginFormDataNotEmpty()) {
if ($this->createDatabaseConnection()) {
$this->checkPasswordCorrectnessAndLogin();
}
}
}
/**
* Logs the user out
*/
private function doLogout()
{
$_SESSION = array();
session_destroy();
$this->user_is_logged_in = false;
$this->feedback = "You were just logged out.";
}
/**
* The registration flow
* @return bool
*/
private function doRegistration()
{
if ($this->checkRegistrationData()) {
if ($this->createDatabaseConnection()) {
$this->createNewUser();
}
}
// default return
return false;
}
/**
* Validates the login form data, checks if username and password are provided
* @return bool Login form data check success state
*/
private function checkLoginFormDataNotEmpty()
{
if (!empty($_POST['user_name']) && !empty($_POST['user_password'])) {
return true;
} elseif (empty($_POST['user_name'])) {
$this->feedback = "Username field was empty.";
} elseif (empty($_POST['user_password'])) {
$this->feedback = "Password field was empty.";
}
// default return
return false;
}
/**
* Checks if user exits, if so: check if provided password matches the one in the database
* @return bool User login success status
*/
private function checkPasswordCorrectnessAndLogin()
{
// remember: the user can log in with username or email address
$sql = 'SELECT user_name, user_email, user_password_hash
FROM users
WHERE user_name = :user_name OR user_email = :user_name
LIMIT 1';
$query = $this->db_connection->prepare($sql);
$query->bindValue(':user_name', $_POST['user_name']);
$query->execute();
// Btw that's the weird way to get num_rows in PDO with SQLite:
// if (count($query->fetchAll(PDO::FETCH_NUM)) == 1) {
// Holy! But that's how it is. $result->numRows() works with SQLite pure, but not with SQLite PDO.
// This is so crappy, but that's how PDO works.
// As there is no numRows() in SQLite/PDO (!!) we have to do it this way:
// If you meet the inventor of PDO, punch him. Seriously.
$result_row = $query->fetchObject();
if ($result_row) {
// using PHP 5.5's password_verify() function to check password
if (password_verify($_POST['user_password'], $result_row->user_password_hash)) {
// write user data into PHP SESSION [a file on your server]
$_SESSION['user_name'] = $result_row->user_name;
$_SESSION['user_email'] = $result_row->user_email;
$_SESSION['user_is_logged_in'] = true;
$this->user_is_logged_in = true;
return true;
} else {
$this->feedback = "Wrong password.";
}
} else {
$this->feedback = "This user does not exist.";
}
// default return
return false;
}
/**
* Validates the user's registration input
* @return bool Success status of user's registration data validation
*/
private function checkRegistrationData()
{
// if no registration form submitted: exit the method
if (!isset($_POST["register"])) {
return false;
}
// validating the input
if (!empty($_POST['user_name'])
&& strlen($_POST['user_name']) <= 64
&& strlen($_POST['user_name']) >= 2
&& preg_match('/^[a-z\d]{2,64}$/i', $_POST['user_name'])
&& !empty($_POST['user_email'])
&& strlen($_POST['user_email']) <= 64
&& filter_var($_POST['user_email'], FILTER_VALIDATE_EMAIL)
&& !empty($_POST['user_password_new'])
&& strlen($_POST['user_password_new']) >= 6
&& !empty($_POST['user_password_repeat'])
&& ($_POST['user_password_new'] === $_POST['user_password_repeat'])
) {
// only this case return true, only this case is valid
return true;
} elseif (empty($_POST['user_name'])) {
$this->feedback = "Empty Username";
} elseif (empty($_POST['user_password_new']) || empty($_POST['user_password_repeat'])) {
$this->feedback = "Empty Password";
} elseif ($_POST['user_password_new'] !== $_POST['user_password_repeat']) {
$this->feedback = "Password and password repeat are not the same";
} elseif (strlen($_POST['user_password_new']) < 6) {
$this->feedback = "Password has a minimum length of 6 characters";
} elseif (strlen($_POST['user_name']) > 64 || strlen($_POST['user_name']) < 2) {
$this->feedback = "Username cannot be shorter than 2 or longer than 64 characters";
} elseif (!preg_match('/^[a-z\d]{2,64}$/i', $_POST['user_name'])) {
$this->feedback = "Username does not fit the name scheme: only a-Z and numbers are allowed, 2 to 64 characters";
} elseif (empty($_POST['user_email'])) {
$this->feedback = "Email cannot be empty";
} elseif (strlen($_POST['user_email']) > 64) {
$this->feedback = "Email cannot be longer than 64 characters";
} elseif (!filter_var($_POST['user_email'], FILTER_VALIDATE_EMAIL)) {
$this->feedback = "Your email address is not in a valid email format";
} else {
$this->feedback = "An unknown error occurred.";
}
// default return
return false;
}
/**
* Creates a new user.
* @return bool Success status of user registration
*/
private function createNewUser()
{
// remove html code etc. from username and email
$user_name = htmlentities($_POST['user_name'], ENT_QUOTES);
$user_email = htmlentities($_POST['user_email'], ENT_QUOTES);
$user_password = $_POST['user_password_new'];
// crypt the user's password with the PHP 5.5's password_hash() function, results in a 60 char hash string.
// the constant PASSWORD_DEFAULT comes from PHP 5.5 or the password_compatibility_library
$user_password_hash = password_hash($user_password, PASSWORD_DEFAULT);
$sql = 'SELECT * FROM users WHERE user_name = :user_name OR user_email = :user_email';
$query = $this->db_connection->prepare($sql);
$query->bindValue(':user_name', $user_name);
$query->bindValue(':user_email', $user_email);
$query->execute();
// As there is no numRows() in SQLite/PDO (!!) we have to do it this way:
// If you meet the inventor of PDO, punch him. Seriously.
$result_row = $query->fetchObject();
if ($result_row) {
$this->feedback = "Sorry, that username / email is already taken. Please choose another one.";
} else {
$sql = 'INSERT INTO users (user_name, user_password_hash, user_email)
VALUES(:user_name, :user_password_hash, :user_email)';
$query = $this->db_connection->prepare($sql);
$query->bindValue(':user_name', $user_name);
$query->bindValue(':user_password_hash', $user_password_hash);
$query->bindValue(':user_email', $user_email);
// PDO's execute() gives back TRUE when successful, FALSE when not
// @link http://stackoverflow.com/q/1661863/1114320
$registration_success_state = $query->execute();
if ($registration_success_state) {
$this->feedback = "Your account has been created successfully. You can now log in.";
return true;
} else {
$this->feedback = "Sorry, your registration failed. Please go back and try again.";
}
}
// default return
return false;
}
/**
* Simply returns the current status of the user's login
* @return bool User's login status
*/
public function getUserLoginStatus()
{
return $this->user_is_logged_in;
}
/**
* Simple demo-"page" that will be shown when the user is logged in.
* In a real application you would probably include an html-template here, but for this extremely simple
* demo the "echo" statements are totally okay.
*/
private function showPageLoggedIn()
{
if ($this->feedback) {
echo $this->feedback . "<br/><br/>";
}
echo 'Hello ' . $_SESSION['user_name'] . ', you are logged in.<br/><br/>';
echo '<a href="' . $_SERVER['SCRIPT_NAME'] . '?action=logout">Log out</a>';
}
/**
* Simple demo-"page" with the login form.
* In a real application you would probably include an html-template here, but for this extremely simple
* demo the "echo" statements are totally okay.
*/
private function showPageLoginForm()
{
if ($this->feedback) {
echo $this->feedback . "<br/><br/>";
}
echo '<h2>Login</h2>';
echo '<form method="post" action="' . $_SERVER['SCRIPT_NAME'] . '" name="loginform">';
echo '<label for="login_input_username">Username (or email)</label> ';
echo '<input id="login_input_username" type="text" name="user_name" required /> ';
echo '<label for="login_input_password">Password</label> ';
echo '<input id="login_input_password" type="password" name="user_password" required /> ';
echo '<input type="submit" name="login" value="Log in" />';
echo '</form>';
echo '<a href="' . $_SERVER['SCRIPT_NAME'] . '?action=register">Register new account</a>';
}
/**
* Simple demo-"page" with the registration form.
* In a real application you would probably include an html-template here, but for this extremely simple
* demo the "echo" statements are totally okay.
*/
private function showPageRegistration()
{
if ($this->feedback) {
echo $this->feedback . "<br/><br/>";
}
echo '<h2>Registration</h2>';
echo '<form method="post" action="' . $_SERVER['SCRIPT_NAME'] . '?action=register" name="registerform">';
echo '<label for="login_input_username">Username (only letters and numbers, 2 to 64 characters)</label>';
echo '<input id="login_input_username" type="text" pattern="[a-zA-Z0-9]{2,64}" name="user_name" required />';
echo '<label for="login_input_email">User\'s email</label>';
echo '<input id="login_input_email" type="email" name="user_email" required />';
echo '<label for="login_input_password_new">Password (min. 6 characters)</label>';
echo '<input id="login_input_password_new" class="login_input" type="password" name="user_password_new" pattern=".{6,}" required autocomplete="off" />';
echo '<label for="login_input_password_repeat">Repeat password</label>';
echo '<input id="login_input_password_repeat" class="login_input" type="password" name="user_password_repeat" pattern=".{6,}" required autocomplete="off" />';
echo '<input type="submit" name="register" value="Register" />';
echo '</form>';
echo '<a href="' . $_SERVER['SCRIPT_NAME'] . '">Homepage</a>';
}
}
// run the application
$application = new OneFileLoginApplication();