Добрый день всем. В этой статье я опишу свой практический опыт проектирования системы авторизации, в частности на C++ Qt 4. Для многих опытных программистов покажется все нижеизложенное банальностью, но для начинающих свой путь программистов, данный опыт думаю, будет полезен. Слов минимум, в основном вырезки кода.
Зачем же нужна авторизация? Что за вопрос, все же знают. Если коротко, то авторизация служит как для входа пользователя в систему, так и для ограничения прав на доступ к функциям системы. У меня он выделен, так сказать, в отдельный модуль, для переносимости, что позволяет использовать один и тот же код в разных проектах.
С чего же начинается авторизация? Ну, конечно же, с окна ввода пользователя и пароля! Вот как то так будет оно выглядеть:

Здесь все стандартно и понятно. Поле логина и пароля, и, конечно же, галочка «запомнить». Когда то в молодости я ее не использовал. Но ввод этой галочки явно облегчил жизнь моим пользователям. За кнопкой «Настройки» таится окошко настройки подключения к базе данных.
И что ж там внутри?
Функция main(), довольно все просто и понятно.
Функция CanConnectToDB() проверяет корректность наших параметров для подключения к БД(если программа запущена в первые, то функция возвратит false, т.к. настроек еще нет);
Класс ProgSettings служит для работы с настройками программы, в нем нет ничего особенно, соот. и останавливаться не будем.
Отдельно стоит остановиться на функции Auth(), наверное самая главная функция.
Без комментариев, за исключение функции SetActiveUser(), которая создает статический экземпляр от SystemUser класса, требуемый для дальнейшей работы по ограничению доступа к функциям всей программы.
Класс UserTable предстовляет собой класс обертку таблицы пользователей.
Стоит отметить о сохранении паролей, они пишутся в реестр и таблицу базы данных зашифрованными. Пароль в таблице пользователей и пароль для подключения к серверу БД шифруются одинаковым алгоритмом и ключом. Кстати, не редко вижу на практике, что многие серьезные программы, пишут в системный реестр незашифрованные пароли, это как, нормально?
Шифрация паролей происходит на уровне приложения, следовательно, первый пользователь системы (без которого мы вообще не войдем в программу) создается при первой попытке входа в систему, вот такой функцией:
Тут же потребуется класс обертка для таблицы ограничений пользователей:
И на последок класс SystemUser:
Ограничения пользователя хранятся в обычном списке QList, который заполняется из таблицы лимитов пользователей в конструкторе SystemUser. Служит он для дальнейшего ограничения функций текущего пользователя.
В итоге мы получаем систему авторизации, подходящую практически для любой сложности приложений. На основе классов оберток таблиц, строятся все запросы к базе данных, что при переносе на другой проект, заменяются имена полей таблицы на свои. Что очень удобно.
Спасибо что дочитали до конца.
Зачем же нужна авторизация? Что за вопрос, все же знают. Если коротко, то авторизация служит как для входа пользователя в систему, так и для ограничения прав на доступ к функциям системы. У меня он выделен, так сказать, в отдельный модуль, для переносимости, что позволяет использовать один и тот же код в разных проектах.
С чего же начинается авторизация? Ну, конечно же, с окна ввода пользователя и пароля! Вот как то так будет оно выглядеть:

Здесь все стандартно и понятно. Поле логина и пароля, и, конечно же, галочка «запомнить». Когда то в молодости я ее не использовал. Но ввод этой галочки явно облегчил жизнь моим пользователям. За кнопкой «Настройки» таится окошко настройки подключения к базе данных.
И что ж там внутри?
Функция main(), довольно все просто и понятно.
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Устанавливаем кодеки
QTextCodec::setCodecForLocale(QTextCodec::codecForName("cp1251"));
QTextCodec::setCodecForTr(QTextCodec::codecForName("cp1251"));
frmMain w;
ProgSettings sett;
DlgAuth dlg;
dlg.setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | Qt::Dialog);
// Устанавливаем имя последнего сохраненного пользователя в поле логин
// и галочку "Запомнить"
dlg.setUserName(sett.LastUser());
dlg.setRemember(sett.IsRemember());
// Если стоит галочка "запомнить"
// то подключаемся к БД(при условии валидности настроек),
// и устанавливаем последний сохраненный пароль
if (dlg.IsRemember() && DBConnector::CanConnectToDB()) {
DBConnector::ConnectToDB();
dlg.setPassword(sett.SavePassword());
}
// Или входим сразу с сохраненным паролем, либо показываем диалог авторизации
if ((DBConnector::CanConnectToDB() && sett.IsRemember() &&
coreUsers::Auth(sett.LastUser(),sett.SavePassword()))
|| dlg.exec() == QDialog::Accepted)
{
w.show();
// Сохраняем последние п-ры входа
sett.setLastUser(dlg.UserName());
sett.setSavePassword(dlg.Password());
sett.setIsReminder(dlg.IsRemember());
}
else return 0;
return a.exec();
}
Функция CanConnectToDB() проверяет корректность наших параметров для подключения к БД(если программа запущена в первые, то функция возвратит false, т.к. настроек еще нет);
Класс ProgSettings служит для работы с настройками программы, в нем нет ничего особенно, соот. и останавливаться не будем.
Отдельно стоит остановиться на функции Auth(), наверное самая главная функция.
bool coreUsers::Auth(QString login, QString pwd)
{
QSqlQuery sql;
bool Ok = false;
sql.exec(QString("SELECT * FROM %1 WHERE %2 = '%3'")
.arg(UserTable::tableName())
.arg(UserTable::loginField())
.arg(login));
if (sql.lastError().isValid()) {
QMessageBox::information(QApplication::activeWindow(),tr("Ошибка"),
sql.lastError().text(),QMessageBox::Ok);
return false;
}
if (sql.size() > 0) {
sql.next();
if (QString::compare(Cryptor::Decode(sql.record().field(UserTable::pwdField()).value().toString())
,pwd,Qt::CaseSensitive)!=0) {
QMessageBox::information(QApplication::activeWindow(),tr("Ошибка"),tr("Неверный пароль! ")
,QMessageBox::Ok);
} else {
if (sql.record().field(UserTable::lockField()).value().toBool())
QMessageBox::information(QApplication::activeWindow(),tr("Ошибка"),
tr("Пользователь '%1' заблокирован.").arg(login),QMessageBox::Ok);
else {
Ok = true;
SetActiveUser(new SystemUser(sql.record().field(UserTable::idField()).value().toInt(),
login,"",
sql.record().field(UserTable::nameField()).value().toString()));
}
}
}
else
QMessageBox::information(QApplication::activeWindow(),tr("Ошибка"),
tr("Нет такого пользователя или неверный пароль!"),QMessageBox::Ok);
return Ok;
}
Без комментариев, за исключение функции SetActiveUser(), которая создает статический экземпляр от SystemUser класса, требуемый для дальнейшей работы по ограничению доступа к функциям всей программы.
Класс UserTable предстовляет собой класс обертку таблицы пользователей.
class UserTable
{
public:
static QString tableName() { return "sy_user"; }
static QString loginField() { return "us_login"; }
static QString pwdField() { return "us_pwd"; }
static QString idField() { return "us_id"; }
static QString nameField() { return "us_name"; }
static QString lockField() { return "us_lock"; }
static QString onlineField() { return "us_online"; }
static QString onlineTimeField(){ return "us_online_time"; }
static bool IsEmpty();
// Создать первого пользователя системы для первого входа в систему
static void CreateFirstUser();
};
Стоит отметить о сохранении паролей, они пишутся в реестр и таблицу базы данных зашифрованными. Пароль в таблице пользователей и пароль для подключения к серверу БД шифруются одинаковым алгоритмом и ключом. Кстати, не редко вижу на практике, что многие серьезные программы, пишут в системный реестр незашифрованные пароли, это как, нормально?
Шифрация паролей происходит на уровне приложения, следовательно, первый пользователь системы (без которого мы вообще не войдем в программу) создается при первой попытке входа в систему, вот такой функцией:
void UserTable::CreateFirstUser()
{
if (!QSqlDatabase::database().isOpen() && !UserTable::IsEmpty())
return;
QSqlQuery sql;
sql.exec(QString("INSERT INTO %1 (%2, %3) VALUES('admin', '%4');")
.arg(tableName())
.arg(loginField())
.arg(pwdField())
.arg(Cryptor::Encode("admin")));
if (sql.lastError().isValid()) {
qDebug() << sql.lastError().text();
} else {
UserLimitTable::AddLimit(sql.lastInsertId().toInt(),100);
}
}
Тут же потребуется класс обертка для таблицы ограничений пользователей:
class UserLimitTable
{
public:
static QString TableName() { return "sy_user_limit"; }
static QString limitIdField() { return "ul_limit"; }
static QString userIdField() { return "ul_us_id"; }
static void AddLimit(int userID, int limitID);
};
И на последок класс SystemUser:
class SystemUser
{
public:
SystemUser(int id, QString login, QString pwd, QString name);
// Возвращает список ограничений пользователя
QList<int> Limits() {return limits;}
int id() {return Id;}
QString login() {return Login;}
QString password() {return Password;}
QString userName() {return UserName;}
QDateTime startSessionTime() {return StartSessionTime;}
void setId(int value) {Id = value;}
void setLogin(QString value) { Login = value;}
void setPassword(QString value) {Password = value;}
void setUserName (QString value) {UserName = value;}
void setStartSessionTime (QDateTime value) { StartSessionTime = value;}
private:
QList<int> limits;
int Id;
QString Login;
QString Password;
QString UserName;
QDateTime StartSessionTime;
};
Ограничения пользователя хранятся в обычном списке QList, который заполняется из таблицы лимитов пользователей в конструкторе SystemUser. Служит он для дальнейшего ограничения функций текущего пользователя.
В итоге мы получаем систему авторизации, подходящую практически для любой сложности приложений. На основе классов оберток таблиц, строятся все запросы к базе данных, что при переносе на другой проект, заменяются имена полей таблицы на свои. Что очень удобно.
Спасибо что дочитали до конца.