Qt——登录对话框的改进

1.没有实现验证码功能,容易被恶意程序攻击,盗取用户名和密码

2.改进思路------验证码机制,需求:验证码必须能够有效避开恶意程序的识别!

  • 随机产生验证码
  • 用户识别后填写
  • 判断用户识别的正确性

3.关于验证码和恶意程序

  • 自动测试原理
    • 利用一些特殊的系统函数能够通过代码控制程序,从而模拟用户操作
  • 恶意程序
    • 使用自动测试原理对目标程序进行控制,从而盗取信息或进行攻击
  • 验证码
    • 随机产生,用户容易识别,程序难以识别,从而有效避免恶意攻击

4.需要注意的问题

  • 验证码必须动态随机产生
  • 验证码的显示避开使用标准组件(标签、文件框等)
  • 验证码应该附带足够多的障碍增加程序识别难度

5.解决方案

  • 随机产生目标验证码
  • 将验证码直接绘制于登录对话框
  • 验证码中的字符颜色随机改变
  • 在验证码区域随机绘制噪点

Widget.h

复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QPushButton>
#include "QLoginDialog.h"

class Widget : public QWidget
{
    Q_OBJECT
    QPushButton m_Login;
    QLoginDialog m_logindialog;

private slots:
    void on_Login();

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget() override;
};
#endif // WIDGET_H

Widget.cpp

复制代码
#include "Widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent), m_Login(this)
{
    m_Login.setText("Test Login Dialog");
    connect(&m_Login, SIGNAL(clicked()), this, SLOT(on_Login()));

}
void Widget::on_Login()
{
    m_logindialog.show();
}
Widget::~Widget() = default;

QLoginDialog.h

复制代码
#ifndef QLOGINDIALOG_H
#define QLOGINDIALOG_H

#include <QDialog>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QTimer>

class QLoginDialog : public QDialog
{
    Q_OBJECT

private:
    QLabel UserLabel;
    QLabel PwdLabel;
    QLabel CaptLabel;
    QLineEdit UserEdit;
    QLineEdit PwdEdit;
    QLineEdit CaptEdit;
    QPushButton LoginBtn;
    QPushButton CancelBtn;
    QString m_user;
    QString m_pwd;
    QString m_captcha;
    Qt::GlobalColor* m_colors;
    QTimer m_timer;
private slots:
    void LoginBtn_Clicked();
    void CancelBtn_Clicked();
    void Timer_timeout();
protected:
    void paintEvent(QPaintEvent* );
    QString getCaptcha(); //获取验证码字符串
    Qt::GlobalColor* getColors();
public:
    explicit QLoginDialog(QWidget *parent = nullptr);
    QString getUser();
    QString getPwd();
    ~QLoginDialog();
};

#endif // QLOGINDIALOG_H

QLoginDialog.cpp

复制代码
#include "QLoginDialog.h"
#include <QPainter>
#include <QTime>
#include <QDebug>
#include <QMessageBox>

QLoginDialog::QLoginDialog(QWidget *parent) : QDialog(parent, Qt::WindowCloseButtonHint),
    UserLabel(this), PwdLabel(this), CaptLabel(this),
    UserEdit(this), PwdEdit(this), CaptEdit(this),
    LoginBtn(this), CancelBtn(this)
{
    UserLabel.setText("User ID:");
    UserLabel.move(20, 30);
    UserLabel.resize(60, 25);

    UserEdit.move(90, 30);
    UserEdit.resize(85, 25);

    PwdLabel.setText("Password:");
    PwdLabel.move(20, 65);
    PwdLabel.resize(70, 25);

    PwdEdit.move(90, 65);
    PwdEdit.resize(180, 25);
    PwdEdit.setEchoMode(QLineEdit::Password);

    CaptLabel.setText("Captcha:");
    CaptLabel.move(20, 100);
    CaptLabel.resize(60, 25);

    CaptEdit.move(90, 100);
    CaptEdit.resize(85, 25);

    CancelBtn.setText("Cancel");
    CancelBtn.move(85, 145);
    CancelBtn.resize(85, 30);

    LoginBtn.setText("Login");
    LoginBtn.move(180, 145);
    LoginBtn.resize(85, 30);

    m_timer.setParent(this);

    setWindowTitle("Login");
    setFixedSize(285, 205);

    connect(&m_timer, SIGNAL(timeout()), this, SLOT(Timer_timeout()));

    connect(&LoginBtn, SIGNAL(clicked()), this, SLOT(LoginBtn_Clicked()));
    connect(&CancelBtn, SIGNAL(clicked()), this, SLOT(CancelBtn_Clicked()));

    qsrand(QTime::currentTime().second() * 1000 + QTime::currentTime().msec()); //种下随机种子,把当前时间换算成从这一分钟开始算起的总毫秒数
    m_captcha = getCaptcha();
    m_colors = getColors();

    m_timer.start(100);
}

void QLoginDialog::LoginBtn_Clicked()
{
    qDebug() << "LoginBtn_Clicked() Begin";

    QString captcha = CaptEdit.text().replace(" ", "");

    if( m_captcha.toLower() == captcha.toLower() )
    {
        m_user = UserEdit.text().trimmed();
        m_pwd = PwdEdit.text();
        if( m_user == "" )
        {
            QMessageBox::information(this, "Info", "User ID can NOT be empty!");
        }
        else if( m_pwd == "" )
        {
            QMessageBox::information(this, "Info", "Password can NOT be empty!");
        }
        else
        {
            done(Accepted);
        }
    }
    else
    {
        QMessageBox::critical(this, "Error", "The captcha is Not matched!");
        m_captcha = getCaptcha();
        CaptEdit.selectAll();
    }
    qDebug() << "LoginBtn_Clicked() End";
}
void QLoginDialog::CancelBtn_Clicked()
{
    qDebug() << "CancelBtn_Clicked() Begin";

    done(Rejected);

    qDebug() << "CancelBtn_Clicked() End";
}
void QLoginDialog::paintEvent(QPaintEvent* )
{
    QPainter painter(this);
    painter.fillRect(180, 100, 84, 24, Qt::white);
    painter.setFont(QFont("Comic Sans MS", 12));
    for(int i=0; i<150; i++)
    {
        painter.setPen(m_colors[i%4]);
        painter.drawPoint(180 + qrand() % 84, 100 + qrand() % 24);
    }
    for(int i=0; i<4; i++)
    {
        painter.setPen(m_colors[i]);
        painter.drawText(180 + 20 * i, 100, 20, 24, Qt::AlignCenter, QString(m_captcha[i]));
    }
}
QString QLoginDialog::getCaptcha()
{
    QString ret = "";

    for(int i=0; i<4; i++)
    {
        int c = (qrand() % 2) ? 'a' : 'A';
        ret += static_cast<QChar>(c + qrand() % 24);
    }
    return ret;
}
Qt::GlobalColor* QLoginDialog::getColors()
{
    static Qt::GlobalColor colors[4];
    for(int i=0; i<4; i++)
    {
        colors[i] = static_cast<Qt::GlobalColor>(2 + qrand() % 16);
    }
}
QString QLoginDialog::getUser()
{
    return m_user;
}
QString QLoginDialog::getPwd()
{
    return m_pwd;
}
void QLoginDialog::Timer_timeout()
{
    m_colors = getColors();
    update();
}
QLoginDialog::~QLoginDialog()
{

}

运行结果: