Login
Hallo PHP Hulp Dit is een simpel PHP login systeem, dit wil dus ook zeggen dat een registratie, activatie en wachtwoord vergeten onderdeel weggelaten is. Wel kan dit script je al een heel stuk op weg helpen om die modules erbij te voegen. Eventueel zou je de database klasse ook nog wat kunnen veranderen want deze is onderdeel van een ouder systeem. De mappen structuur ziet er als volgt uit: index.php, login.php en phphulp.sql (dit is de users tabel). Daarna is er nog een map genaamd classes met alle klassen erin! Opmerking! Als je dit script gebruikt zou ik wel in het bestand class.hash.php de pepper bovenaan veranderen. Zorg ook dat je deze goed opslaat. Ook moet je opletten bij de user klasse want die gebruikt de gebruikers naam als onderdeel van de hash. Als je dit niet wilt moet je zoeken naar de functie $this->getHashedPassword() en dit manueel aanpassen. Als er vragen of opmerkingen zijn gelieve het formulier hieronder te gebruiken. Update 26 december 2012 1. User mapper > instance User 2. User class > hashen wachtwoord gebruikt nu de gebruikersnaam in kleine letters zodat de user ook met demo kan inloggen i.p.v. Demo. Opmerking 26 december 2012 Eventueel zou je als je dat zelf wilt en voor extra veiligheid en efficiƫntie de database een object kunnen later bezorgen. Pas daarvoor de database class and bij fetchAll() en fetch(). Zet daarin een param $object = '' en gebruikt dat dan daaronder. Ook moet je dan als je gebruikers info opvraag $user->getUsername() gebruiken. $user->username zal dan niet meer werken. Veel succes!
[code]<?php session_start();
/**
*
* Account information:
*
* $user = new User();
* $user->setUsername('Demo');
* $user->setPassword('Demo123');
*
*/
/**
* On the submit press button, validate the input when the user isn't logged
* in:
*/
if($_SERVER['REQUEST_METHOD'] == 'POST' && !isset($_SESSION['user'])) {
/**
* Include all the classes that are required:
*/
foreach(array('class.database', 'class.dataMapper', 'class.hash', 'class.user', 'class.userMapper', 'class.bruteForce', 'class.bruteForceMapper') as $file) {
include 'classes/'.$file.'.php';
}
/**
* Debug for the database class on or off:
*/
define('DEBUG', true);
/**
* Set the database information, normally you should do that in a config
* file:
*/
$database = new Database();
$database->setHost('localhost');
$database->setUsername('root');
$database->setPassword('root');
$database->setDatabase('phphulp');
/**
* Get the connection. When you use a config file, you do not need to call this
* if you do not change from database information:
*/
$database->getConnection();
/**
* Array to store errors, you could also use an own error class:
*/
$errors = array();
/**
* Set the user information:
*/
$user = new User();
$user->setUsername(isset($_POST['username']) ? $_POST['username'] : '');
$user->setPassword(isset($_POST['password']) ? $_POST['password'] : '');
/**
* When the username hasn't been submitted or isn't valid. A username can only
* contain a-z, A-Z and 0-9 tokens:
*/
if((isset($_POST['username']) && !preg_match('/^[0-9\p{L}]{2,12}$/u', $_POST['username'])) || !isset($_POST['username'])) {
$errors[] = 'The login credentials are not correct.';
}
else {
/**
* Create bruteforce object:
*/
$bruteforce = new BruteForce();
$bruteforce->setUsername($user->getUsername());
$bruteforce->setIp($_SERVER['REMOTE_ADDR']);
$bruteforce->setMaxAttempts(5); /** Number of attempts, normally a value between 3 and 5 **/
$bruteforce->setTimeout(5); /** Timeout, normally a value between 5 and 15 **/
/**
* The new user information:
*/
$user_db = UserMapper::create()->getByLoginCredentials($user, 'user_id, username');
/**
* Check if there is a brute force ban for the user:
*/
$bruteforce_mapper = new BruteForceMapper();
if($bruteforce_mapper->getByUsername($bruteforce, 'brute_force_id') != false) {
$errors[] = 'You have a login ban. Please wait for max. 15 minutes.';
}
elseif($bruteforce_mapper->getByIp($bruteforce, 'brute_force_id') != false) {
$errors[] = 'You have a login ban. Please wait for max. 15 minutes.';
}
/**
* Check if the user exists:
*/
elseif($user_db == false) {
$bruteforce_mapper->deleteByTime($bruteforce); /** Delete the old bans, you could use this one in a cronjob **/
$bruteforce_mapper->insert($bruteforce); /** Insert the new one **/
$errors[] = 'The login credentials are not correct.';
}
/**
* Validation of the user has been completed:
*/
else {
$_SESSION['user']['user_id'] = $user_db->user_id;
$_SESSION['user']['username'] = $user_db->username;
/** Add sessions and stuff **/
}
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>PHP Login With Brute Force Protection</title>
</head>
<body>
<?php if(!isset($_SESSION['user'])): ?>
<?php if(isset($errors) && is_array($errors) && count($errors) > 0): ?>
<ul>
<?php foreach($errors as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
<?php endif; ?>
<form method="POST">
<label for="username">Username (Demo):</label>
<input type="text" name="username" value="" />
<label for="password">Password (Demo123):</label>
<input type="password" name="password" value="" />
<input type="submit" value="Login" />
</form>
<?php endif; ?>
<?php if(isset($_SESSION['user'])): ?>
<?php echo 'Logged in. Welcome '.$_SESSION['user']['username'].'.'; ?>
<?php endif; ?>
</body>
</html>[/code]
[code]<!DOCTYPE html>
<html>
<head>
<title>Anti Brute Force Login</title>
</head>
<body>
<a href="login.php">Go to the login.</a>
</body>
</html>[/code]
-- phpMyAdmin SQL Dump
-- version 3.5.2.2
-- http://www.phpmyadmin.net
--
-- Machine: 127.0.0.1
-- Genereertijd: 25 dec 2012 om 20:15
-- Serverversie: 5.5.27
-- PHP-versie: 5.4.7
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Databank: `phphulp`
--
-- --------------------------------------------------------
--
-- Tabelstructuur voor tabel `brute_force`
--
CREATE TABLE IF NOT EXISTS `brute_force` (
`brute_force_id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(12) NOT NULL,
`ip` varchar(50) NOT NULL,
`insert_datetime` datetime NOT NULL,
PRIMARY KEY (`brute_force_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
--
-- Tabelstructuur voor tabel `users`
--
CREATE TABLE IF NOT EXISTS `users` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(12) NOT NULL,
`password` varchar(128) NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
--
-- Gegevens worden uitgevoerd voor tabel `users`
--
INSERT INTO `users` (`user_id`, `username`, `password`) VALUES
(1, 'Demo', '994ee866826dfa33c196613f5fc938d007c2c0e89b2b1f967148c03ef288caa2376a19b5d767063c43e0a34bca4115c28b548a39803908c007199689938ff095');
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
<?php
/**
*
* @date 15 Aug 2012,
* @package Login System
*
*/
Class User {
/**
* @param int $user_id,
* @param string $username,
* @param string $password
*/
private
$user_id,
$username,
$password;
/**
* @param int $user_id,
* @return void
*/
public function setUserId($user_id) {
$this->user_id = (int) $user_id;
}
/**
* @param string $username,
* @return void
*/
public function setUsername($username) {
$this->username = (string) $username;
}
/**
* @param string $password,
* @return void
*/
public function setPassword($password) {
$this->password = (string) $password;
}
/**
* @return int $user_id
*/
public function getUserId() {
return (int) $this->user_id;
}
/**
* @return string $username
*/
public function getUsername() {
return (string) preg_replace('/[^0-9\p{L}]$/u', '', $this->username);
}
/**
* @return string $password
*/
public function getHashedPassword() {
$hash = new Hash($this->password, strtolower($this->username));
return $hash->getHashedPassword();
}
}
?>
<?php
/**
*
* @date 15 Aug 2012,
* @package Login System
*
*/
Class UserMapper extends DataMapper {
/**
* @param object $user,
* @param string $fields,
* @return mixed
*/
public function getByLoginCredentials(User $user, $fields = '*') {
$this->pdo->query("SELECT ".$fields." FROM users WHERE username = :username AND password = :password LIMIT 1", array(':username' => $user->getUsername(), ':password' => $user->getHashedPassword()));
return $this->pdo->fetch();
}
}
?>
<?php
/**
*
* @date 15 Aug 2012,
* @package Login System
*
*/
Class Bruteforce {
/**
* @param int $brute_force_id,
* @param string $username,
* @param string $ip,
* @param int $attempts -> the number of attempts,
* @param int $timeout -> timeout in minutes,
* @param string $insert_datetime
*/
private
$brute_force_id,
$username,
$ip,
$attempts,
$timeout,
$insert_datetime = '0000-00-00 00:00:00';
/**
* @param int $id,
* @return void
*/
public function setBruteForceId($brute_force_id) {
$this->brute_force_id = (int) $brute_force_id;
}
/**
* @param int $username,
* @return void
*/
public function setUsername($username) {
$this->username = (string) $username;
}
/**
* @param string $ip,
* @return void
*/
public function setIp($ip) {
$this->ip = (string) $ip;
}
/**
* @param int $attempts,
* @return void
*/
public function setMaxAttempts($attempts) {
$this->attempts = (int) $attempts;
}
/**
* @param int $timeout,
* @return void
*/
public function setTimeout($timeout) {
$this->timeout = (int) $timeout;
}
/**
* @return int $id
*/
public function getBruteForceId() {
return (int) $this->brute_force_id;
}
/**
* @return int $username
*/
public function getUsername() {
$user = new User();
$user->setUsername($this->username);
return $user->getUsername();
}
/**
* @return string $ip
*/
public function getIp() {
return (string) $this->ip;
}
/**
* @return int $attempts
*/
public function getMaxAttempts() {
return (int) $this->attempts;
}
/**
* @return int $timeout
*/
public function getTimeout() {
return (int) $this->timeout;
}
/**
* @return $insert_datetime
*/
public function getInsertDatetime() {
return (string) preg_match($this->insert_datetime, '(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})') ? $this->insert_datetime : null;
}
}
?>
<?php
/**
*
* @date 15 Aug 2012,
* @package Login System
*
*/
Class BruteForceMapper extends DataMapper {
/**
* @param object $bruteforce,
* @return mixed -> bool when failing, otherwishe array
*/
public function getByUsername(BruteForce $bruteforce, $fields = '*') {
$this->pdo->query("SELECT ".$fields." FROM brute_force WHERE insert_datetime >= NOW() - INTERVAL :attempts_time MINUTE AND username = :username GROUP BY username, ip HAVING (COUNT(username) >= :max_attempts)", array(':username' => $bruteforce->getUsername(), ':attempts_time' => $bruteforce->getTimeout(), ':max_attempts' => $bruteforce->getMaxAttempts()));
return $this->pdo->fetchAll();
}
/**
* @param object $bruteforce,
* @return mixed
*/
public function getByIp(Bruteforce $bruteforce, $fields = '*') {
$this->pdo->query("SELECT ".$fields." FROM brute_force WHERE insert_datetime >= NOW() - INTERVAL :attempts_time MINUTE AND ip = :ip GROUP BY username, ip HAVING (COUNT(username) >= :max_attempts)", array(':ip' => $bruteforce->getIp(), ':attempts_time' => $bruteforce->getTimeout(), ':max_attempts' => $bruteforce->getMaxAttempts()));
return $this->pdo->fetchAll();
}
/**
* @param object $bruteforce,
* @return bool true/false
*/
public function insert(BruteForce $bruteforce) {
return $this->pdo->query("INSERT INTO brute_force SET username = :username, ip = :ip, insert_datetime = NOW()", array(':username' => $bruteforce->getUsername(), ':ip' => $bruteforce->getIp()));
}
/**
* @param object $bruteforce,
* @return bool true/false
*/
public function deleteByTime(BruteForce $bruteforce) {
return $this->pdo->query("DELETE FROM brute_force WHERE insert_datetime < :insert_datetime", array(':insert_datetime' => date('Y-m-d H:i:s', time() - $bruteforce->getTimeout() * 60)));
}
}
?>
<?php
/**
*
* @date 15 Aug 2012,
* @package Login System
*
*/
Class Database {
/**
* @param string $host,
* @param string $username,
* @param string $password,
* @param string $database,
* @param mixed $result
*/
private
$host,
$username,
$password,
$database,
$result;
/**
* @param object $pdo -> static because otherwhise with each page load
* there will be multiple connections
*/
private
static $pdo = false;
/**
* @param string $host,
* @return void
*/
public function setHost($host) {
$this->host = $host;
}
/**
* @param string $username,
* @return void
*/
public function setUsername($username) {
$this->username = $username;
}
/**
* @param string $password,
* @return void
*/
public function setPassword($password) {
$this->password = $password;
}
/**
* @param string $database,
* @return void
*/
public function setDatabase($database) {
$this->database = $database;
}
/**
* @return void
*/
public function getConnection() {
if(!self::$pdo) {
try {
self::$pdo = new PDO('mysql:host='.$this->host.';dbname='.$this->database, $this->username, $this->password, array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e) {
defined('DEBUG') && DEBUG == false ? exit('Database connection failed.') : exit('Database connection error: '.$e->getMessage().'.');
}
}
}
/**
* @param string $qry,
* @param array $args -> can be empty when not using WHERE,
* @return mixed $result/exception
*/
public function query($qry, $args = array()) {
try {
$this->result = $stmt = self::$pdo->prepare($qry);
return $stmt->execute($args);
}
catch(PDOException $e) {
defined('DEBUG') && DEBUG == true ? exit('Query failed: '.$e->getMessage().'.') : exit('Query failed.');
}
}
/**
* @return mixed $rows
*/
public function fetchAll() {
return $this->result->fetchAll(PDO::FETCH_OBJ);
}
/**
* @return mixed $rows
*/
public function fetch() {
return $this->result->fetch(PDO::FETCH_OBJ);
}
/**
* @return int $rows
*/
public function rowCount() {
return count($this->result);
}
}
?>
<?php
/**
*
* @date 15 Aug 2012,
* @package Login System
*
*/
Class DataMapper {
/**
* @param object $pdo
*/
protected
$pdo;
/**
* @return void
*/
public function __construct() {
$this->pdo = new Database();
}
/**
* @return object new Called Class
*/
public static function create() {
$class = get_called_class();
return new $class();
}
}
?>
<?php
/**
* Define the pepper that will be used below in the hash class
* script:
*/
define('PEPPER', '2:@P%%oQ[|b&p#m+:|1$ {n#+1zyk6B>OO:HTBs6dx~Ws._|~-rb/WJ9^&pAo@XA');
/**
*
* @date 15 Aug 2012,
* @package Security
*
*/
Class Hash {
/**
* @param string $password,
* @param string $salt,
* @param string $key -> a key for the hashing algo (almost different each time),
* @param string $hash -> the hash that will be used to encrypt passwords,
* @param bool $raw -> raw output (for most safety set on false)
*/
private
$password,
$salt,
$key = '',
$hash = 'sha512',
$raw = false;
/*
* @param string $hashed -> the hashed password
*/
private
$hashed;
/**
* @param string $password,
* @param string $salt,
* @return void
*/
public function __construct($password, $salt) {
$this->password = (string) $password;
$this->salt = (string) $salt;
}
/**
* @param string $hash;
* @return void
*/
public function setHash($hash) {
if(!in_array(strtolower($hash), hash_algos())) {
throw new Exception('Hash: '.$hash.' is NOT available on this system.');
}
$this->hash = strtolower($hash);
}
/**
* @param string $key,
* @return void
*/
public function setKey($key) {
$this->key = (string) $key;
}
/**
* @param bool $raw,
* @return void
*/
public function setRaw($raw) {
$this->raw = (bool) $raw;
}
/**
* Generate the hashed password. No params are needed.
* @return string $hashed
*/
private function generate() {
/**
* Generate a special key that is different for each client and is based
* on the length of the password:
*/
if(strlen($this->key) == 0) {
$i = round(strlen(PEPPER) + strlen($this->salt) + strlen($this->password));
$this->setKey(hash_hmac($this->hash, substr(PEPPER.$this->salt.$this->password.$this->salt.PEPPER, ($i / 2)), PEPPER, true));
}
/**
* Generate the hashed value of the setted pepper, salt and
* password together:
*/
$this->hashed = hash_hmac($this->hash, PEPPER.$this->salt.$this->password.$this->salt.PEPPER, $this->key, (bool) $this->raw);
return $this->hashed;
}
/**
* @return string $hashed
*/
public function getHashedPassword() {
return $this->generate();
}
}
?>
Reacties
0