Qt+Cutelyst学习笔记(二十三)win10+Qt5.15.2 添加认证

前言:

本篇继续在前一篇的基础上实现

之前的功能实现,都没有考虑认证的问题,本次测试,会增加基本的认证功能,可供读者参考

一、向数据库中添加用户和角色

执行如下语句,向sqlite数据库添加

--
-- Add users and role tables, along with a many-to-many join table
--
PRAGMA foreign_keys = ON;
CREATE TABLE users (
        id            INTEGER PRIMARY KEY,
        username      TEXT,
        password      TEXT,
        email_address TEXT,
        first_name    TEXT,
        last_name     TEXT,
        active        INTEGER
);
CREATE TABLE role (
        id   INTEGER PRIMARY KEY,
        role TEXT
);
CREATE TABLE user_role (
        user_id INTEGER REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
        role_id INTEGER REFERENCES role(id) ON DELETE CASCADE ON UPDATE CASCADE,
        PRIMARY KEY (user_id, role_id)
);
--
-- Load up some initial test data
--
INSERT INTO users VALUES (1, 'test01', 'mypass', '[email protected]', 'Joe',  'Blow', 1);
INSERT INTO users VALUES (2, 'test02', 'mypass', '[email protected]', 'Jane', 'Doe',  1);
INSERT INTO users VALUES (3, 'test03', 'mypass', '[email protected]', 'No',   'Go',   0);
INSERT INTO role VALUES (1, 'user');
INSERT INTO role VALUES (2, 'admin');
INSERT INTO user_role VALUES (1, 1);
INSERT INTO user_role VALUES (1, 2);
INSERT INTO user_role VALUES (2, 1);
INSERT INTO user_role VALUES (3, 1);

二、添加相关调用库

编辑src/CMakeLists.txt并添加如下

target_link_libraries(MyApp
    ...
    Cutelyst::Session        # Add these lines
    Cutelyst::Authentication # Add these lines
    ...
}

添加会话库和认证库

三、创建身份验证的辅助类

创建类AuthStoreSql,用于存储从数据库查询出的相关信息给认证使用

src/authstoresql.h内容如下

#ifndef AUTHSTORESQL_H
#define AUTHSTORESQL_H
 
#include 
 
using namespace Cutelyst;
 
class AuthStoreSql : public AuthenticationStore
{
public:
    explicit AuthStoreSql(QObject *parent = 0);
 
    virtual AuthenticationUser findUser(Context *c, const ParamsMultiMap &userinfo) override;
 
private:
    QString m_idField;
};
 
#endif // AUTHSTORESQL_

继承自AuthenticationStore,并实现一个重载函数findUser()

src/authstore.cpp内容如下:

#include "authstoresql.h"

#include 

#include 
#include 
#include 
#include 

AuthStoreSql::AuthStoreSql(QObject *parent) : AuthenticationStore(parent)
{
    m_idField = "username";
}

AuthenticationUser AuthStoreSql::findUser(Context *c, const ParamsMultiMap &userinfo)
{
    qDebug()<<"AuthStoreSql::findUser" << userinfo;
    QString id = userinfo.value(m_idField);

    QSqlQuery query = CPreparedSqlQueryThreadForDB("SELECT * FROM users WHERE username = :username", "MyDB");
    query.bindValue(":username", id);
    if (query.exec() && query.next()) {
        QVariant userId = query.value("id");
        qDebug() << "FOUND USER -> " << userId.toInt();
        AuthenticationUser user(userId.toString());

        int columns = query.record().count();
        // send column headers
        QStringList cols;
        for (int j = 0; j < columns; ++j) {
            cols << query.record().fieldName(j);
        }

        for (int j = 0; j < columns; ++j) {
            user.insert(cols.at(j),
                        query.value(j).toString());
        }

        return user;
    }
    qDebug() << query.lastError().text();

    return AuthenticationUser();
}

代码不难理解,就是根据信息从数据库查询出来,并生成包含信息的对象,并反馈

四、注册并启用身份验证

编辑src/myapp.cpp,并添加如下

#include 
#include 
#include 
 
#include "authstoresql.h"
 
bool MyApp::init()
{
    ...
    new Session(this);
 
    auto auth = new Authentication(this);
    auto credential = new CredentialPassword;
    credential->setPasswordType(CredentialPassword::Clear);
    
    auth->addRealm(new AuthStoreSql, credential);
    ...
}

五、添加登录和注销控制器

使用Cutelyst命令创建两个控制器文件


$ cutelyst3-qt5 --controller Login
$ cutelyst3-qt5 --controller Logout

为方便理解,两控制器分别处理登录和注销,当然写在一起可是可以的

打开src/login.cpp,并修改如下

#include 
 
void Login::index(Context *c)
{
    // Get the username and password from form
    QString username = c->request()->bodyParam("username");
    QString password = c->request()->bodyParam("password");
 
    // If the username and password values were found in form
    if (!username.isNull() && !password.isNull()) {
        // Attempt to log the user in
        if (Authentication::authenticate(c, { {"username", username}, {"password", password} })) {
            // If successful, then let them use the application
            c->response()->redirect(c->uriFor(c->controller("Books")->actionFor("list")));
            return;
        } else {
            // Set an error message
            c->setStash("error_msg", "Bad username or password.");
        }
    } else if (!Authentication::userExists(c)) {
        // Set an error message
        c->setStash("error_msg", "Empty username or password.");
    }
 
    // If either of above don't work out, send to the login page
    c->setStash("template", "login.html");
}

控制器从登录表单获取用户名和密码,并尝试验证用户身份。如果成功,它会将用户重定向到图书列表页面。如果登录失败,用户将停留在登录页面并收到错误消息。如果表单中没有用户名和密码值,用户将被带到空的登录表单。

在src/logout.cpp中,处理注销相关的,修改如下

#include 
 
void Logout::index(Context *c)
{
    // Clear the user's state
    Authentication::logout(c);
    
    // Send the user to the starting point
    c->response()->redirect(c->uriFor("/"));
}

添加完成控制器后,要记得注册下

六、添加登录表单模板页面

创建root/src/login.html登录表单模板,并修改如下

Username:
Password:

七、强制身份验证

为防止未通过身份验证的用户访问除登录页面以外的任何页面,需要在src/root.cpp中添加Auto方法

src/root.h文件添加如下

class Root : public Controller
{
    ...
private:
    /**
     * Check if there is a user and, if not, forward to login page
     */
    C_ATTR(Auto, :Private)
    bool Auto(Context *c);
};

src/root.cpp文件添加如下

#include 
 
bool Root::Auto(Context *c)
{
    // Allow unauthenticated users to reach the login page.  This
    // allows unauthenticated users to reach any action in the Login
    // controller.  To lock it down to a single action, we could use:
    //   if (c->action() eq c->controller("Login")->actionFor("index"))
    // to only allow unauthenticated access to the 'index' action we
    // added above
    if (c->controller() == c->controller("Login")) {
        return true;
    }
 
    // If a user doesn't exist, force login
    if (!Authentication::userExists(c)) {
        // Dump a log message to the development server debug output
        qDebug("***Root::Auto User not found, forwarding to /login");
 
        // Redirect the user to the login page
        c->response()->redirect(c->uriFor("/login"));
 
        // Return false to cancel 'post-auto' processing and prevent use of application
        return false;
    }
 
    // User found, so return true to continue with processing after this 'auto'
    return true;
}

从应用程序/根控制器到最特定的控制器的每个自动方法都将被调用。通过将身份验证强制代码放在src/root的auto.cpp方法中。整个应用程序收到的每个请求都会调用它。

八、编译测试

 直接编译,生成动态库,并运行服务,输出如下

Qt+Cutelyst学习笔记(二十三)win10+Qt5.15.2 添加认证_第1张图片

 可以看到登录相关的控制器已添加

在浏览器中访问http://localhost:3000/books/list

运行如下所示

Qt+Cutelyst学习笔记(二十三)win10+Qt5.15.2 添加认证_第2张图片

若未输入正确的用户名和密码,则一直停留在这个页面上

输入正确的用户test01 密码mypass,会跳转到正确的页面上,如下所示

Qt+Cutelyst学习笔记(二十三)win10+Qt5.15.2 添加认证_第3张图片

 本测试源码下载

后记:

到目录为止,一个简单的小demo已经完成了,但笔者仍然有很多疑问,计划接下来,看下RESTful服务的实现

你可能感兴趣的:(Qt+web后台开发,qt,cutelyst3)