Ostatnio zainteresowałem się prostymi systemami logowania na stronie. Każdy który znalazłem w sieci miał swoje wady i zalety. Postanowiłem więc podjąć się napisania własnego i z waszą pomocą jego usprawnienia. Bazuje on oczywiście na tych najprostszych lecz starałem się aby był w miarę możliwości bezpieczny. Póki co jest dopiero skrypt logowania bez formularza lecz myślę, że jest już w takim stadium w którym można by zacząć dyskusję na jego temat. Jako prosty skrypt może już być wykorzystany lecz jakie są wasze uwagi co do niego?
Co już jest:
-możliwość zablokowania logowania na różne konta z tego samego IP (zmienna 1 albo 0)
-nie można zalogować się na to samo konto z dwóch różnych przeglądarek/komputerów
-można tymczasowo zablokować logowanie się na stronie
-blokowanie możliwości logowania jeśli istnieje już ważna sesja o danym IP albo SID
-proste ustawienie ważności sesji
-przy wylogowaniu aktualizuje SID
Poziomy użytkownika:
- -1 niezalogowany
- 0 konto nieaktywne
- cała reszta do waszej dyspozycji
Co jest w planie:
-blokowanie logowania z danego IP
-niewidoczny element wykorzystujący AJAX do odświeżania sesji co zadaną ilość sekund oraz sprawdzenia poprawności sesji
-odczytanie listy zalogowanych z ważną sesją
-rejestracja z wysłaniem maila aktywacyjnego
-funkcja dla admina usuwająca wszystkie sesje z bazy danych (wylogowanie wszystkich)
System wykorzystuje bazę MySQL przy użyciu PDO
Mam jakiś problem z dodawaniem załączników więc plik baza.sql ze strukturą oraz przykładowymi użytkownikami znajduje się na samym dole.
index.php
<?php
include('lconfig.php');
echo('strona glowna');
echo('<hr>');
Log_msg();
echo('<hr>');
if ($_SESSION['log_lvl'] > -1) {
echo('<h3>');
echo('Login: ' . $_SESSION['log_login'] . '<br>');
echo('Id: ' . $_SESSION['log_uid'] . '<br>');
echo('Poziom: ' . $_SESSION['log_lvl'] . '<br>');
echo('Sesja: ' . $_SESSION['log_sid'] . '<br>');
echo('Ip: ' . $_SESSION['log_ip'] . '<br>');
echo('</h3>');
echo('<a href="flogin.php?f=logout">Wyloguj</a>');
} else {
echo('<a href="flogin.php?l=admin&p=admin">Admin</a><br>');
echo('<a href="flogin.php?l=user&p=user">User</a><br>');
echo('<a href="flogin.php?l=ktos&p=haslo">Nieaktywny</a><br>');
echo('<a href="flogin.php?l=aaa&p=bbb">ZleDane</a><br>');
}
?>
lconfig.php
<?php
session_start();
$page_index = 'index.php'; //strona glowna na ktora bedzie przkierowanie jesli wszystko ok
$page_login = 'index.php'; //strona logowania w razie niepoprawnych danych logowania
$db_host = 'localhost'; //serwer bazy danych
$db_port = '3306'; //port
$db_name = 'test'; //nazwa bazy
$db_pass = 'haslo'; //haslo
$db_user = 'root'; //login do bazy
$sesion_exp = 3600; //waznosc sesji w sekundach
$allow_login = 1; //1=mozna sie logowac 0=nie mozna sie logowac
$ip_multilog = 0; //1=dozwolone logowanie z jednego ip 0=niedozwolone
$str_blad_polaczeniadb = 'Błąd połączenia z bazą';
$str_blokada_logowania = 'Logowanie na stronie jest tymczasowo niemożliwe';
$str_juz_zalogowany = 'Jesteś już zalogowany';
$str_ip_zalogowany = 'Ktoś inny z tego adresu IP jest już zalogowany na stronie';
$str_podaj_lh = 'Nie podano loginu lub hasła';
$str_zle_lh = 'Niepoprawny login lub hasło';
$str_konto_nieaktywne = 'To konto nie zostało jeszcze aktywowane';
$str_konto_zalogowane = 'Ktoś inny jest już zalogowany na tym koncie';
$str_logowanie_pomyslne = 'Zalogowano';
$str_wylogowany = 'Wylogowano';
//ustawianie zmiennych sesji na wartosci standardowe niezalogowanego uzytkownika
function reset_session_data() {
if (!isset($_SESSION['log_uid'])) {$_SESSION['log_uid'] = -1;}
if (!isset($_SESSION['log_login'])) {$_SESSION['log_login'] = '';}
if (!isset($_SESSION['log_lvl'])) {$_SESSION['log_lvl'] = -1;}
if (!isset($_SESSION['log_sid'])) {$_SESSION['log_sid'] = session_id();}
if (!isset($_SESSION['log_ip'])) {$_SESSION['log_ip'] = $_SERVER['REMOTE_ADDR'];}
}
//laczenie z baza danych
function db_connect() {
global $db_log_sys;
global $str_blad_polaczeniadb;
global $db_host;
global $db_port;
global $db_name;
global $db_pass;
global $db_user;
try {
$db_log_sys = new PDO('mysql:host=' . $db_host . ';port=' . $db_port . ';dbname=' . $db_name, $db_user, $db_pass);
} catch(PDOException $e) {
die($str_blad_polaczeniadb);
}
}
//funkcja wylogowania
function logout() {
global $db_log_sys;
try {
//sprawdzenie czy sesja w bazie zgadza sie z sesja przegladarki
$s_dane = $db_log_sys -> query('SELECT sid FROM session where uid = ' . $_SESSION['log_uid']);
//jesli tak to usun ta sesje z bazy danych przy wylogowaniu
if($s_dane_row = $s_dane -> fetch()) {
if (($s_dane_row['sid']) == session_id()) {
$dane = $db_log_sys -> prepare('DELETE FROM session WHERE uid = :uid');
$dane -> bindValue(':uid', $_SESSION['log_uid'], PDO::PARAM_INT);
$dane -> execute();
$dane -> closeCursor();
}
}
$s_dane -> closeCursor();
} catch(PDOException $e) {
}
$_SESSION = array();
reset_session_data();
session_regenerate_id(); //utworzenie nowego id sesji
}
//funkcja czyszczaca tekst aby nie bawic sie w sql injection itp.
function clear_string($s) {
if(get_magic_quotes_gpc()) {
$s = stripslashes($s);
}
$s = trim($s);
$s = mysql_real_escape_string($s);
$s = htmlspecialchars($s);
return $s;
}
//predzej slonce zgasnie niz brute force odkryje co znajduje sie pod tym hashem
//zakladajac ze tylko Ty znasz te znaczki
function solenie($pass) {
return md5(sha1('*&^D%%F'.$pass).'#!%Rgd64');
}
//funkcja wyswietlajaca informacje jednorazowe typu: 'bledny login' czy 'zostales wylogowany'
//jesli wartosc bedzie pusta to nie bedzie zadnej reakcji
function Log_msg() {
if (isset($_SESSION['log_msg'])) {
if ($_SESSION['log_msg'] != '') {
echo($_SESSION['log_msg']);
unset($_SESSION['log_msg']);
}
}
}
//generuje aktualny czas w sekundach od 1 stycznia 1970 + sekundy podane w parametrze
function Generuj_Czas($ile) {
$czas_akt = time();
return($czas_akt + $ile);
}
reset_session_data();
db_connect();
//sprawdzanie zgodnosci danych zesji przegladarki z tymi w bazie
if ($_SESSION['log_lvl'] > -1) {
$s_dane = $db_log_sys -> query('SELECT exp, ip, sid FROM session where uid = ' . $_SESSION['log_uid']);
//sprawdzenie czy wpis o id sesji(uzytkownika) znajduje sie w bazie
if($s_dane_row = $s_dane -> fetch()) {
//jesli sesja w bazie jest nieaktualna to uruchom funkcje wyloguj
if ($s_dane_row['exp'] <= Generuj_Czas(0)) {
$s_dane -> closeCursor();
logout();
}
//jesli id sesji nie zgadza sie z tym w bazie to wyloguj
if ($s_dane_row['sid'] != session_id()) {
$s_dane -> closeCursor();
logout();
}
//jesli ip sesji nie zgadza sie z tym w bazie to wyloguj
if ($s_dane_row['ip'] != $_SERVER['REMOTE_ADDR']) {
$s_dane -> closeCursor();
logout();
}
} else {
//jesli wpis sesji nie znajduje sie w bazie to wyloguj
$s_dane -> closeCursor();
logout();
}
$s_dane -> closeCursor();
}
?>
flogin.php
<?php
include('lconfig.php');
//wylogowanie przy wykorzystaniu $_GET
if (isset($_GET['f'])) {
if ($_GET['f'] == 'logout') {
logout();
$_SESSION['log_msg'] = $str_wylogowany;
header('Location: ' . $page_index);
exit;
}
}
//jesli logowanie wylaczone to przypisz komunikat i do strony glownej
if ($allow_login != 1) {
$_SESSION['log_msg'] = $str_blokada_logowania;
header('Location: ' . $page_index);
exit;
}
//komunikat w przypadku gdy uzytkownik jest juz zalogowany
if ($_SESSION['log_lvl'] > -1) {
$_SESSION['log_msg'] = $str_juz_zalogowany;
header('Location: ' . $page_index);
exit;
}
//jesli logowanie kilku uzytkownikow z jednego ip jest zablokowane
if ($ip_multilog == 0) {
$s_dane = $db_log_sys -> query('SELECT ip, exp FROM session');
foreach($s_dane as $s_dane_row) { //petla czytajaca wszystkie ip z bazy sesji
if ($s_dane_row['ip'] == $_SERVER['REMOTE_ADDR']) { //jesli dany ip sie znajdzie
if ($s_dane_row['exp'] >= Generuj_Czas(0)) { //a sesja jest w ciaz wazna
$_SESSION['log_msg'] = $str_ip_zalogowany;
$s_dane -> closeCursor();
header('Location: ' . $page_index);
exit;
}
}
}
$s_dane -> closeCursor();
}
/// usunac w dzialajacym skrypcie
///
$_POST['login'] = $_GET['l']; /// to pozwala na logowanie za pomocą $_GET
$_POST['pass'] = $_GET['p']; /// uzywane w czasie testow bez formularza z metodą POST
///
//komunikat jesli login albo haslo nie jest podane
if (!isset($_POST['login']) || !isset($_POST['pass'])) {
$_SESSION['log_msg'] = $str_podaj_lh;
header('Location: ' . $page_login);
exit;
}
$_POST['login'] = clear_string($_POST['login']); //czyszczenie loginu
$_POST['pass'] = clear_string($_POST['pass']); //czyszczenie hasla
$_POST['pass'] = solenie($_POST['pass']); //solenie hasla
//pobranie danych uzytkownika o podanym loginie i hasle
$u_dane = $db_log_sys -> prepare('SELECT id, login, lvl, akt FROM users WHERE login = :login AND haslo = :haslo LIMIT 1');
$u_dane -> bindValue(':login', $_POST['login'], PDO::PARAM_STR);
$u_dane -> bindValue(':haslo', $_POST['pass'], PDO::PARAM_STR);
$u_dane -> execute();
//jesli dany uzytkownik i haslo jest zgodne z tymi z bazy
if($u_dane_row = $u_dane -> fetch()) {
if ($u_dane_row['akt']!='') { //jesli pole z kluczem aktywacyjnym nie jest puste to znaczy ze jeszcze nie aktywowano konta
$_SESSION['log_msg'] = $str_konto_nieaktywne;
$u_dane -> closeCursor();
header('Location: ' . $page_index);
exit;
} else { //a jesli konto aktywne
//wyszukaj ewentualnie istniejacego wpisu sesji uzytkownika o danym ID
$s_dane = $db_log_sys -> prepare('SELECT sid, ip, exp FROM session WHERE uid = :uid LIMIT 1');
$s_dane -> bindValue(':uid', $u_dane_row['id'], PDO::PARAM_INT);
$s_dane -> execute();
//jesli taki wpis istnieje
if($s_dane_row = $s_dane -> fetch()) {
//jesli ID sesji albo IP sesji się nie zgadza z danymi przeglądarki to informuj o tym
//chyba ze waznosc tej sesji z bazy juz wygasla
if ((($s_dane_row['sid'] != session_id()) || ($s_dane_row['ip'] != $_SERVER['REMOTE_ADDR'])) && ($s_dane_row['exp'] >= Generuj_Czas(0))) {
$_SESSION['log_msg'] = $str_konto_zalogowane;
$u_dane -> closeCursor();
$s_dane -> closeCursor();
header('Location: ' . $page_login);
exit;
} else {
//jesli jednak dane IP i ID sesji się zgadza to aktualizuj waznosc sesji
//to w przypadku gdyby ktos jakims cudem logowal sie na to samo konto gdy jest juz zalogowany
$czas = Generuj_Czas($sesion_exp);
$wpis = $db_log_sys -> prepare('UPDATE session SET exp = :exp WHERE uid = :uid');
$wpis -> bindValue(':exp', $czas, PDO::PARAM_INT);
$wpis -> bindValue(':uid', $u_dane_row['id'], PDO::PARAM_STR);
$wpis -> execute();
$wpis -> closeCursor();
}
} else {
//jesli wpis sesji nie istnieje w bazie to go utworz
$czas = Generuj_Czas($sesion_exp);
$wpis = $db_log_sys -> prepare('INSERT INTO session (uid, sid, ip, exp) VALUES (:uid, :sid, :ip, :exp)');
$wpis -> bindValue(':uid', $u_dane_row['id'], PDO::PARAM_INT);
$wpis -> bindValue(':sid', session_id(), PDO::PARAM_STR);
$wpis -> bindValue(':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR);
$wpis -> bindValue(':exp', $czas, PDO::PARAM_INT);
$wpis -> execute();
$wpis -> closeCursor();
}
//przypisanie zmiennej sesyjnej wartosci z tabeli uzytkownika
$_SESSION['log_uid'] = $u_dane_row['id'];
$_SESSION['log_login'] = $u_dane_row['login'];
$_SESSION['log_lvl'] = $u_dane_row['lvl'];
$_SESSION['log_sid'] = session_id();
$_SESSION['log_ip'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['log_msg'] = $str_logowanie_pomyslne;
$u_dane -> closeCursor();
$s_dane -> closeCursor();
header('Location: ' . $page_index);
exit;
}
} else {
//jesli w bazie nie ma uzytkownika o takim loginie i hasle to znaczy ze login albo haslo niepoprawne
$_SESSION['log_msg'] = $str_zle_lh;
$_SESSION['log'] = array();
$u_dane -> closeCursor();
header('Location: ' . $page_login);
exit;
}
?>
dane.sql
-- phpMyAdmin SQL Dump
-- version 3.4.10.1
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Czas wygenerowania: 07 Lis 2012, 00:55
-- Wersja serwera: 5.5.20
-- Wersja PHP: 5.3.10
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 */;
--
-- Baza danych: `test`
--
-- --------------------------------------------------------
--
-- Struktura tabeli dla `session`
--
CREATE TABLE IF NOT EXISTS `session` (
`uid` int(11) NOT NULL,
`sid` varchar(32) NOT NULL,
`ip` varchar(20) NOT NULL,
`exp` int(11) NOT NULL,
KEY `uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Zrzut danych tabeli `session`
--
INSERT INTO `session` (`uid`, `sid`, `ip`, `exp`) VALUES
(0, '0', '0', 0);
-- --------------------------------------------------------
--
-- Struktura tabeli dla `users`
--
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`login` varchar(32) NOT NULL,
`haslo` varchar(32) NOT NULL,
`lvl` tinyint(4) NOT NULL DEFAULT '0',
`email` varchar(50) NOT NULL,
`akt` varchar(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
--
-- Zrzut danych tabeli `users`
--
INSERT INTO `users` (`id`, `login`, `haslo`, `lvl`, `email`, `akt`) VALUES
(1, 'Admin', 'ee2d6c60dae92745ebbf8c232c4564a2', 3, '[email protected]', ''),
(2, 'User', '8f93d514815e78a8181c01ec1a455395', 1, '[email protected]', ''),
(3, 'Ktos', 'bb41ab2bd977382ea329c49591ca207c', 0, '[email protected]', '8d7f5gsdf7');
/*!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 */;