LINUX.ORG.RU

Реализация регистрации/входа/выхода пользователей на php

 , , ,


0

3

Всем привет.

Нужна безопасная реализация регистрации/входа/выхода пользователей на php.

Где лучше и проще выдрать без всякого лишнего мусора? Нужна только реализация в виде функций/классов? Остальное напишу сам. Либо нужен подробный алгоритм всего этого, как правильнее и безопаснее сделать.

Желательно алгоритм (да, я люблю свои велосипеды, мне так комфортнее и удобнее).

Конкретно интересует:

  1. регистрация (шифрование пароля, активация профиля и т.п.)
  2. вход/выход (проверка логина/пароля, где хранить вошедшего пользователя, logout при бездействии, защита от перехвата авторизации, правильный выход и т.п.)
  3. восстановление/смена пароля

Заранее спасибо за ответы. Жирных троллей прошу проходить мимо.

P.S. пока для этого использовал интегрированные в свои самописы движки. Думал это будет проще и быстрее, но геморроя со временем все больше и больше.

★★★

Ответ на: комментарий от Zhbert

Про геморрой и время - столкнулся с той же ситуацией, когда врпмпнр на творчество нет, а делать надо. Забил ра свой движок, отложив его в сторону, и взял за основу друпал...

Zhbert ★★★★★ ()

Система регистрации и авторизации пользователей в моём блоге.

# sqlite3 spfng.sqlite3 <<EOF
CREATE TABLE 'user'
(
  'user_id' INTEGER PRIMARY KEY AUTOINCREMENT,
  'user_login' VARCHAR(32),
  'user_password' VARCHAR(128),
  'user_salt' VARCHAR(16),
  'user_name' VARCHAR(80),
  'user_date_registered' DATETIME DEFAULT CURRENT_TIMESTAMP,
  'user_date_authorized' DATETIME
);
EOF
<?php
$dbh = new SQLite3('spfng.sqlite3');
$dbh->busyTimeout(31337);
$dbh->query('
PRAGMA synchronous=NORMAL;
PRAGMA journal_mode=WAL;
PRAGMA page_size=16384;
PRAGMA cache_size=65536;
PRAGMA temp_store=MEMORY;
');
?>
<?php
if (isset($_COOKIE[session_name()])) {
session_start();
if (empty($_SESSION['user_id'])) {
setcookie(session_name(), '', 1);
session_destroy();
}
}
?>
<?php if ($_SERVER['REQUEST_URI'] === '/'): ?>
<?php if (session_id() === ''): ?>
Welcome, <b>anonymous</b> (IP: <?php echo $_SERVER['REMOTE_ADDR']?>). <a href="/signin/">Sign in</a> | <a href="/signup/">Sign up</a>
<?php else: ?>
Welcome, <b><?php echo $_SESSION['user_name']; ?></b> (IP: <?php echo $_SERVER['REMOTE_ADDR']?>). <a href="/signout/">Sign out</a>
<?php endif; ?>
<?php elseif ($_SERVER['REQUEST_URI'] === '/signup/'): ?>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ((strlen($_POST['name']) < 2) || (strlen($_POST['name']) > 80)) {
die('?');
}
if ((strlen($_POST['login']) < 2) || (strlen($_POST['login']) > 32) || (str_replace(array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'), '', substr($_POST['login'], 0, 1)) === '') || (str_replace(array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'), '', $_POST['login']) !== '')) {
die('?');
}
if ((strlen($_POST['password']) < 2) || (strlen($_POST['password']) > 512)) {
die('?');
}
if ($dbh->query('SELECT * FROM user WHERE user_login = "'.$_POST['login'].'"')->fetchArray()['user_id']) {
die('?');
}
$dbh->query('BEGIN TRANSACTION');
$try = $dbh->prepare('INSERT INTO user (user_login, user_password, user_salt, user_name) VALUES (:user_login, :user_password, :user_salt, :user_name)');
$try->bindValue(':user_login', $_POST['login']);
$try->bindValue(':user_salt', 'Spoofing + Faumi = <3');
$try->bindValue(':user_password', crypt($_POST['password'], '$6$rounds=31337$'.'Spoofing + Faumi = <3'.''));
$try->bindValue(':user_name', $_POST['name']);
$try->execute();
$dbh->query('UPDATE user SET user_date_authorized = CURRENT_TIMESTAMP WHERE user_login = "'.$_POST['login'].'"');
$dbh->query('COMMIT');
session_start();
$try = $dbh->query('SELECT * FROM user WHERE user_login = "'.$_POST['login'].'"');
if ($row = $try->fetchArray()) {
extract($row); unset($row);
}
$_SESSION['user_id'] = $user_id;
$_SESSION['user_login'] = $user_login;
$_SESSION['user_name'] = htmlspecialchars($user_name);
header($_SERVER['SERVER_PROTOCOL'].' 303 See Other');
header('Location: /');
die();
}
?>
<?php if ($_SERVER['REQUEST_METHOD'] === 'GET'): ?>
<form action="/signup/" method="POST">
<input class="edits" type="text" name="login">
<input class="edits" type="text" name="password" value="<?php echo substr(str_shuffle('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'), <input class="edits" type="text" name="name">
<input class="edits" type="submit" value="sign up">
</form>
<?php endif; ?>
<?php elseif ($_SERVER['REQUEST_URI'] === '/signin/'): ?>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$try = $dbh->prepare('SELECT * FROM user WHERE user_login = :user_login AND user_password = :user_password');
$try->bindValue(':user_login', $_POST['login']);
$try->bindValue(':user_password', crypt($_POST['password'], '$6$rounds=31337$'.'Spoofing + Faumi = <3'.''));
if ($row = $try->execute()->fetchArray()) {
extract($row); unset($row);
}
if (isset($user_id)) {
$dbh->query('UPDATE user SET user_date_authorized = CURRENT_TIMESTAMP WHERE user_id = "'.$user_id.'"');
session_start();
$_SESSION['user_id'] = $user_id;
$_SESSION['user_login'] = $user_login;
$_SESSION['user_name'] = htmlspecialchars($user_name);
header($_SERVER['SERVER_PROTOCOL'].' 303 See Other');
header('Location: /');
die();
}
else {
die('?');
}
}
?>
<?php if ($_SERVER['REQUEST_METHOD'] === 'GET'): ?>
<form action="/signin/" method="POST">
<input class="edits" type="text" name="login">
<input class="edits" type="text" name="password">
<input class="edits" type="submit" value="sign in">
</form>
<?php endif; ?>
<?php elseif ($_SERVER['REQUEST_URI'] === '/signout/'): ?>
<?php if ($_SERVER['REQUEST_METHOD'] === 'GET'): ?>
<?php
session_start();
session_destroy();
setcookie(session_name(), '', 1);
header($_SERVER['SERVER_PROTOCOL'].' 303 See Other');
header('Location: /');
die();
?>
<?php endif; ?>
<?php endif; ?>
Spoofing ★★★★★ ()
Последнее исправление: Spoofing (всего исправлений: 1)

регистрация (шифрование пароля,

Сейчас модно bcrypt использовать. Только это не шифрование, а хеширование. Ещё соль желательно использовать.

активация профиля

При регистрации ставишь в аккаунте статус «не активирован». Высылаешь на емейл MAC-защищённую ссылку с userId и timestamp-ом. При переходе по ней проверяешь подпись, timestamp и меняет статус на «активирован».

Альтернативно можешь в БД завести таблицу, туда пихать UUID и прочую информацию, а пользователю слать только UUID в ссылке. Но это ненужные усложнения.

и т.п.)

?

вход/выход (проверка логина/пароля

Непонятно, в чём вопрос.

где хранить вошедшего пользователя, logout при бездействии

В серверной сессии хранишь userID, а пользователю высылаешь session id. Также в серверной сессии хранишь timestamp и по нужной тебе логике обновляешь его, делаешь logout если время истекло.

защита от перехвата авторизации

HTTPS?

правильный выход и т.п.)

Удалил серверную сессию, опционально удалил у клиента куку.

восстановление/смена пароля

Аналогично активации. Только желательно добавить счётчик в аккаунт смен пароля, чтобы по ссылке можно было сменить пароль только один раз.

Legioner ★★★★★ ()
Ответ на: комментарий от Legioner

Чё только не делают люди, лишь бы не юзать Laravel / Django / Ruby on Rails.

Вся эта ботва что тут ТС хочет в ruby on rails пишется за 10 минут на коленке в каком-нибудь RubyMine. Плюс ещё два дня на изучение азов руби и рэилс.

menangen ★★★★★ ()
Ответ на: комментарий от menangen

в ruby on rails пишется за 10 минут

Может ему сразу Java со спрингом изучать для такой адски сложной фичи. Она и на голом PHP за несколько минут пишется. Потом появляются уникумы, которые лэндинги одностраничные на рельсах пишут.

nguseff ()
Ответ на: комментарий от Legioner

Короче, быстренько накидал код. Получилась примерно такая логика:

  • после ввода логина и пароля проверяется наличие юзера в базе
  • если такой юзер есть, то запускаю сессию, вместе с этим добаляется кука с айдишником сессии
  • загоняю в $_SESSION айдишник юзера
  • редирект на главную
  • в начале всего говнокода проверяется наличие айдишника сессии в кукисах
  • если есть, то стартую сессию
  • если сессия запущена, проверяется, есть ли в $_SESSION айдишник юзера
    • если нет, то убиваю сессию
    • если есть, то достаю нужные данные юзера и пихаю в массив

Все верно?

И еще вопрос: как делать автовход? Генерить случайный ключ, добавлять его юзеру в базу и кукисы, а потом проверять наличие автовхода у юзера и сравнивать ключи?

zevilz ★★★ ()
Последнее исправление: zevilz (всего исправлений: 1)
Ответ на: комментарий от zevilz

в начале всего говнокода проверяется наличие айдишника сессии в кукисах
если есть, то стартую сессию

Пусть меня поправят знатоки PHP, но вроде это должен делать рантайм самостоятельно.

И еще вопрос: как делать автовход? Генерить случайный ключ, добавлять его юзеру в базу и кукисы, а потом проверять наличие автовхода у юзера и сравнивать ключи?

Автовход это просто очень долгая сессия. Надо или держать сессии в базе, или сохранять их при перезапуске сервера. Можно и как ты написал, хотя выглядит усложнением.

Legioner ★★★★★ ()
Последнее исправление: Legioner (всего исправлений: 1)

Конкретно интересует

шифрование пароля

проверка логина/пароля

class crypt
{
	public static function password($password, $salt = null, $salt_length = 32)
	{
		if ($salt === null)
		{
			$salt = static::salt($salt_length);
		}

		$hash = md5($password . $salt);

		return array($hash, $salt);
	}

	public static function salt($length = 32, $alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
	{
		return strings::rand($length, $alphabet);
	}

	public static function is_valid($password, $hash, $salt)
	{
		list($tmp_hash, $tmp_salt) = static::password($password, $salt);

		if ($tmp_hash === $hash and $tmp_salt === $salt)
		{
			return true;
		}

		return false;
	}
}
define('RUDE_STRING_ENCODING', 'UTF-8');

class strings
{
	public static function rand($length = 32, $alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
	{
		$result = '';

		$alphabet_size = static::length($alphabet);

		for ($i = 0; $i < $length; $i++)
		{
			$number = mt_rand(1, $alphabet_size);

			$result .= static::char($alphabet, $number);
		}

		return $result;
	}

	public static function length($string)
	{
		if (static::is_utf8($string))
		{
			return mb_strlen($string, RUDE_STRING_ENCODING);
		}

		return strlen($string);
	}

	public static function char($string, $number)
	{
		return static::read($string, 1, $number - 1);
	}

	public static function read($string, $length = null, $offset = 0)
	{
		if (static::is_utf8($string))
		{
			return mb_substr($string, $offset, $length, RUDE_STRING_ENCODING);
		}

		return substr($string, $offset, $length);
	}

	public static function length($string)
	{
		if (static::is_utf8($string))
		{
			return mb_strlen($string, RUDE_STRING_ENCODING);
		}

		return strlen($string);
	}

	public static function is_utf8($string)
	{
		########################################################################################
		# 1. Any UTF8 string is a valid 8-bit encoding string (even if it produces gibberish); #
		# 2. On the other hand, most 8-bit encoded strings with extended (128+) characters are #
		#    not valid UTF8, but, as any other random byte sequence, they might happen to be;  #
		# 3. Of course, any ASCII text is valid UTF8;                                          #
		# 4. Native mb_detect_encoding() is slow.                                              #
		########################################################################################

		if (preg_match('//u', $string))
		{
			return true; # it's UTF-8
		}

		return false; # it's something else
	}
}

Пример использования:

$password = '123456'; # пароль пользователя

list($hash, $salt) = crypt::password($password); # шифрование пароля

$result = crypt::is_valid($password, $hash, $salt); # проверка пароля $password с указанными хешем и солью

ThisNameWasFree ()
Ответ на: комментарий от Spoofing

заворачивай эту дрянь в пастебин, не мозоль глаза

reprimand ★★★★★ ()
Ответ на: комментарий от Spoofing

Уау :) Это как натирать глаза об наждак. Крутяк.

iu0v1 ()
Ответ на: комментарий от Nucleus-

да ладно
и каким же образом?
перебором пароля?

q11q11 ★★★★★ ()
Ответ на: комментарий от Nucleus-

На голом пхп вообще пишут идиоты или школьники. Вы ещё во времена CGI вернитесь... Уже давно несколько нормальных фреймверков написано на пыхе, в которых грамотная защита от CSRF и прочих болезней новичков. Всё самому реализовывать то, что уже всеми переписано 100 раз подряд - признак маргиналов.

Вообще, при наличии ruby / python / JavaScript v6 / Cofeescript, писать на этом говноязычке может только толстый слон, который ограничен в выборе информации.

menangen ★★★★★ ()
Ответ на: комментарий от menangen

Интересно вы пишите, тогда вы должны посещать сайты, написанные чисто на ruby / python / JavaScript v6 / Cofeescript. Иначе, вас раздавит толстый слон. Что ж вы так, фреймворки, то да сё, а если человек хочет понять сам, изнутри как все работает, а не полагатся на готовые решения. Удачи вам :)

nixbrain ()
Ответ на: комментарий от menangen

На голом пхп вообще пишут идиоты или школьники

Тяжело ли вам быть дураком?

ThisNameWasFree ()
Ответ на: комментарий от menangen

Зачем ты мне это написал? Я за то, чтобы использовать нормальные решения, вместо клепания своего говна. Но пхпшники часто ленивые или без мозгов, какие там фреймворки? Не все еще про PDO знают.

Nucleus- ()
Ответ на: комментарий от menangen

На голом пхп вообще пишут идиоты или школьники. Вы ещё во времена CGI вернитесь... Уже давно несколько нормальных фреймверков написано на пыхе, в которых грамотная защита от CSRF и прочих болезней новичков. Всё самому реализовывать то, что уже всеми переписано 100 раз подряд - признак маргиналов.

Использование фреймворков ведет к деградации. Единственный плюс - скорость разработки. Для себя я лучше напишу на голом пхп, вникну в суть и реализую только то, что мне нужно, а не буду тащить кучу ненужного мне говна.

zevilz ★★★ ()
Ответ на: комментарий от anonymous

А сколько принесет радости кулхацкерам твоя поделка!

Почему ты так уверен? К их приходу надеюсь подготовлю хорошую защиту. Сырое и не оттестеное даже вылаживать не буду. + исходный код только у меня на локалхосте и на сервере. А для кулхацкеров можно завести фейковые страницы входа в админки и фейковую структуру популярных CMS, пускай сношаются)

zevilz ★★★ ()
Ответ на: комментарий от nguseff

PHP — уникальный инструмент для отстреливания ног при чистке ушей.

anonymous ()
Ответ на: комментарий от zevilz

LOL

Использование фреймворков ведет к деградации. Единственный плюс - скорость разработки. Для себя я лучше напишу на голом пхп, вникну в суть и реализую только то, что мне нужно, а не буду тащить кучу ненужного мне говна.

Ну знаете, если так рассуждать, то может следует писать сайты на Asm? :D Вас послушать, дак получается что прогресс вообще в топку можно спустить. Может быть проблема все-таки в том, что кому-то лень разбираться в этих самых «фреймворках, ведущих к деградации»? Конечно, понять ту же схему MVC сложнее, чем, допустим изучить пару «голых» PHP функций. Но я более чем уверен, что после разбора таких понятий как «ООП», или «паттер проектирования», у людей в голове прибавится явно больше, чем после того, как они выучат пару функций, типа explode/implode/etc и наговнокодят свой код для авторизации/пагинации/etc. Но это, конечно, ИМХО.

znenyegvkby ()
Ответ на: комментарий от znenyegvkby

ООП тут непричем, это просто стиль программирования, а я про готовые велосипеды имею ввиду.

Насчет ООП, прочитал пару книжек по нему и паттернам, ковырялся в Codeigniter и Yii, сам начал писать на ООП, но потом плюнул. Дело не в том, что не разобрался, а в том, что мне это ненужно для своих небольших проектов, над которыми работаю только я.

Сейчас начнется срач ООП vs ФП :)

zevilz ★★★ ()
Ответ на: комментарий от zevilz

Чтобы вникнуть в суть процессов на сервере, кук и сессий - пхп не нужен. Есть питон для таких целей, плюс куча классных питон отладчиков, всё это прекрасно работает в community open source Pycharm от Jetbrains. Возьми тупо поставь питон и Flask web фреймверк. Это лучшее, что ты можешь сделать для себя. Так ты будешь кодить в удовольствие и для себя (саморазвития). Тем более, если проекты чисто для себя. Тот же Flask ничего не скрывает от программиста, но позволяет смотреть / наследоваться / видоизменять всё, что душе позволяет.

menangen ★★★★★ ()
Ответ на: комментарий от ritsufag

А пхп геи вас не волнуют? Очень странно, стОит обеспокоиться.

Руби, кстати, мне меньше нравится, ччем питон и JavaScript. Просто для начинающего разработчика он наиболее производительный в написании кода бэкенда, за это его и любят всякие хипстеры. Ну а node.js и ecma script 6 с кучей «батареек» в npm стали популярны по той же причине. Взять тот же express.js - там вообще, куча низкоуровщины, всё нужно связывать самому. И сессии с куками, и компилятор jade/less/sass. Но есть и более высокоуровневые фреймворки типа метеора, которые в любом случае стоит глянуть, т.к. любой пыха программер должен знать хорошо JavaScript/ HTML5 - таковы реалии. Чё б сразу не писать на каком-нибудь jade/less, ускорение в разы.

menangen ★★★★★ ()
Последнее исправление: menangen (всего исправлений: 1)
Ответ на: комментарий от zevilz

Не думаю, что начнется срач :) Вы, возможно, имели ввиду процедурное программирование (ПП), а не функциональное, т.к. функциональное программирование это немного не то. Вообще, тут дело на самом деле не в холиварах. Печально видеть, что в XXI веке еще используют ПП для решения именно такого типа задач. В любом случае, конечно, в итоге это личный выбор каждого. Вот вы сказали про Yii и Codeigniter. Дак почему бы вам, если уж так не нравятся фреймворки, не взять оттуда просто код для авторизации. Согласитесь, лучше уж довериться Qiang Xue в этом плане, тем паче что можно не тупо копировать, а изучить код (не всего проекта конечно, а именно авторизации). Я не слишком силен в этом, но вот, за пару минут нашел в доке ответ на ваш вопрос, относительно:

где хранить вошедшего пользователя

Нажимаем SHOW напротив Source Code:, и наслаждаемся кодом. Как видите, ничего сложного в нем нет, все хранится в обычных куках, здесь даже организована проверка, если вас интересует как работает следующий метод, просто скопируйте его и так же пройдите глубже. Я думаю так, вы намного проще разберетесь в реализации нужного вам функционала. Лично я, конечно, отрубал бы руки за такое

$data=@unserialize($data);
if(is_array($data) && isset($data[0],$data[1],$data[2],$data[3]))
Но, в любом случае, я думаю что сод Qiang Xue в этом плане безопаснее, нежели будет ваш самописный :) Опять же, решать вам. Удачи в начинаниях.

znenyegvkby ()
Ответ на: комментарий от zevilz

Использование фреймворков ведет к деградации

Kek. Либами-то можно пользоваться.

anonymous ()
Ответ на: комментарий от menangen

Нет, не волнуют. Они же не ходят на парады с плакатами.
А вот рубисты чуть ли не в каждую щель пытаются пропихнуть свой язык одного фреймворка.

node.js и ecma script 6

Тащемта без тайпскрипта это всё те же помои, только обёртка поприятнее.

ritsufag ★★★★★ ()
Ответ на: комментарий от Nucleus-

Ну да, а rails никто не ломает, потому что на нем сайтов и цмс нет ха ха. Ломают не ЯП, а сайты, через дыры в коде. Ну как можно «сломать» echo «ломай меня сильно»

gobot ★★★★ ()
Ответ на: комментарий от nguseff

А потом он захочет формы, орм, csrf-защиту, админку, кэширование, миграции бд, тесты, древовидные структуры в бд и прочее и прочее. А это всё уже изобретено. Так лучше изучить готовые и нормально написанные инструменты.

pawnhearts ★★★★★ ()
Ответ на: комментарий от Legioner

держать сессии в базе

Это создает огромную излишнюю нагрузку на БД. Лучше в файлах или каком-нить redis`е или подобном.

pawnhearts ★★★★★ ()
Ответ на: комментарий от gobot

Ну да, а rails никто не ломает, потому что на нем сайтов и цмс нет ха ха.

Хорошая шутка)

Ломают не ЯП, а сайты, через дыры в коде. Ну как можно «сломать» echo «ломай меня сильно»

А я разве не то же самое сказал? Спор вообще не об этом.

Nucleus- ()
Ответ на: комментарий от gobot

Ну как можно «сломать» echo «ломай меня сильно»

Вот выплюнешь ты форму через echo, а как данные принимать и обрабатывать начнёшь, так и опростоволосишься. Что касается «ломают не ЯП», предлагаю почитать последние секьюрити апдейты жумлы, лол.

anonymous ()
Ответ на: комментарий от anonymous

Ну, а причем тут пхп? Не припомню я что то, чтобы сайты ломали через built-in конструкции языка. А жумлу можно и на С написать, она тут ни при чем, да и никто тебя не заставляет ее использовать или ты еж лезущий на кактус?

gobot ★★★★ ()
Ответ на: комментарий от zevilz

загоняю в $_SESSION айдишник юзера

Если ты перезапустишь сервер, то все залогиненные юзеры, подозреваю, разлогинятся? Позаботьтся о том, чтобы так не вышло: либо храни соответствие «кука -> юзер» в БД, либо настрой сам PHP, чтобы сессии так и работали.

greatperson ()
Ответ на: комментарий от ThisNameWasFree

Потому, что если сопрут соль из исходников, то пробрутфорсят содержимое базы на каких-нибудь 5-ти плейстейшенах и за сутки подберут больше половины паролей пользователей.

menangen ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.