Добрый день всем. В этой статье я опишу свой практический опыт проектирования системы авторизации, в частности на C++ Qt 4. Для многих опытных программистов покажется все нижеизложенное банальностью, но для начинающих свой путь программистов, данный опыт думаю, будет полезен. Слов минимум, в основном вырезки кода.
Зачем же нужна авторизация? Что за вопрос, все же знают. Если коротко, то авторизация служит как для входа пользователя в систему, так и для ограничения прав на доступ к функциям системы. У меня он выделен, так сказать, в отдельный модуль, для переносимости, что позволяет использовать один и тот же код в разных проектах.
С чего же начинается авторизация? Ну, конечно же, с окна ввода пользователя и пароля! Вот как то так будет оно выглядеть:
Здесь все стандартно и понятно. Поле логина и пароля, и, конечно же, галочка «запомнить». Когда то в молодости я ее не использовал. Но ввод этой галочки явно облегчил жизнь моим пользователям. За кнопкой «Настройки» таится окошко настройки подключения к базе данных.
И что ж там внутри?
Функция 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. Служит он для дальнейшего ограничения функций текущего пользователя.
В итоге мы получаем систему авторизации, подходящую практически для любой сложности приложений. На основе классов оберток таблиц, строятся все запросы к базе данных, что при переносе на другой проект, заменяются имена полей таблицы на свои. Что очень удобно.
Спасибо что дочитали до конца.
Автор: wulff007