前言:
本篇继续在前一篇的基础上实现
之前的功能实现,都没有考虑认证的问题,本次测试,会增加基本的认证功能,可供读者参考
一、向数据库中添加用户和角色
执行如下语句,向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', 't01@na.com', 'Joe', 'Blow', 1);
INSERT INTO users VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe', 1);
INSERT INTO users VALUES (3, 'test03', 'mypass', 't03@na.com', '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 <Cutelyst/Plugins/Authentication/authenticationstore.h>
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 <Cutelyst/Plugins/Utils/Sql>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QSqlError>
#include <QDebug>
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 <Cutelyst/Plugins/Session/Session>
#include <Cutelyst/Plugins/Authentication/authentication.h>
#include <Cutelyst/Plugins/Authentication/credentialpassword.h>
#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 <Cutelyst/Plugins/Authentication/authentication.h>
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 <Cutelyst/Plugins/Authentication/authentication.h>
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登录表单模板,并修改如下
<form method="post" action="/login">
<table>
<tr>
<td>Username:</td>
<td><input type="text" name="username" size="40" /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" size="40" /></td>
</tr>
<tr>
<td colspan="2"><input type="submit" name="submit" value="Submit" /></td>
</tr>
</table>
</form>
七、强制身份验证
为防止未通过身份验证的用户访问除登录页面以外的任何页面,需要在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 <Cutelyst/Plugins/Authentication/authentication.h>
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方法中。整个应用程序收到的每个请求都会调用它。
八、编译测试
直接编译,生成动态库,并运行服务,输出如下
可以看到登录相关的控制器已添加
在浏览器中访问http://localhost:3000/books/list
运行如下所示
若未输入正确的用户名和密码,则一直停留在这个页面上
输入正确的用户test01 密码mypass,会跳转到正确的页面上,如下所示
本测试源码下载
后记:
到目录为止,一个简单的小demo已经完成了,但笔者仍然有很多疑问,计划接下来,看下RESTful服务的实现