Авторизация в приложениях

в 12:14, , рубрики: c++, qt, Qt Software, авторизация, Песочница, метки: ,

Добрый день всем. В этой статье я опишу свой практический опыт проектирования системы авторизации, в частности на 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js