cutelyst教程_05_认证

本文介绍了如何在Cutelyst应用中添加身份验证功能,包括添加用户和角色到数据库、配置Authentication和Session插件,以及创建登录和注销控制器。通过实例演示了如何设置用户验证流程和防止未授权访问的机制。
摘要由CSDN通过智能技术生成

Tutorial_05_Authentication

教程_05_认证

Buschmann edited this page on Feb 24, 2018 · 13 revisions

Buschmann于2018年2月24日编辑了本页

OVERVIEW

总览

  1. Introduction
  2. Cutelyst Basics
  3. More Cutelyst Basics
  4. Basic CRUD
  5. Authentication

DESCRIPTION

说明

Now that we finally have a simple yet functional application, we can focus on providing authentication (with authorization coming next in Chapter 6).

现在我们终于有了一个简单但功能强大的应用程序,我们可以专注于提供身份验证(授权将在第6章中介绍)。

BASIC AUTHENTICATION

基本身份验证

This section explores how to add authentication logic to a Cutelyst application.

本节探讨如何向Cutelyst应用程序添加身份验证逻辑。

Add Users and Roles to the Database

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

First, we add both user and role information to the database (we will add the role information here although it will not be used until the authorization section, Chapter 6). Create a new SQL script file by opening myapp02.sql in your editor and insert:

首先,我们将用户和角色信息都添加到数据库中(我们将在这里添加角色信息,尽管直到第6章授权部分才会使用它)。通过打开myapp02.sql脚本文件。在编辑器中输入sql并插入:

--
-- 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);

Then load this into the myapp.db database with the following command:

然后将其加载到myapp中。使用以下命令创建db数据库:

$ sqlite3 myapp.db < myapp02.sql

Include Authentication and Session Plugins

包括身份验证和会话插件

Edit src/CMakeLists.txt and update it as follows:

编辑src/CMakeLists.txt并按如下方式更新:

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

Create a Authentication Store

创建身份验证存储

Cutelyst comes with a base AuthenticationStore class that can be subclassed to allow for integration with Authentication class, since we are not using an ORM this class will be used to fetch an user from the database we just changed.

Cutelyst附带了一个基本AuthenticationStore类,该类可以被子类化,以允许与Authentication类集成,因为我们没有使用ORM,该类将用于从我们刚刚更改的数据库中获取用户。

Create a src/authstoresql.h with the following content:

创建一个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_H

And src/authstore.cpp:

和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)
{
    QString id = userinfo[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();
}

Configure Authentication

配置身份验证

Edit src/myapp.cpp and update it as follows:

编辑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);
    ...
}

The Authentication plugin supports Authentication while the Session plugins are required to maintain state across multiple HTTP requests.

Authentication插件支持身份验证,而会话插件需要跨多个HTTP请求维护状态。

There are different password types. Here we make use of the CredentialPassword::Clear since our SQL data is in clear text. On production you want Hashed and in order to obtain a password hash e.g. to store in a database you can use something like

有不同的密码类型。这里我们使用CredentialPassword::Clear,因为我们的SQL数据是明文的。在生产过程中,您需要进行哈希运算,为了获得密码哈希,例如存储在数据库中,您可以使用

QByteArray hashedPassword = CredentialPassword::createPassword(         
    password.toUtf8(),
    QCryptographicHash::Sha256,                                         
    3, // iterations
    16, // bytes salt
    16 // bytes hash
);

Add Login and Logout Controllers

添加登录和注销控制器

Use the Cutelyst command to create two stub controller files:

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

$ cutelyst --controller Login
$ cutelyst --controller Logout

You could easily use a single controller here. For example, you could have a User controller with both login and logout actions. Remember, Cutelyst is designed to be very flexible, and leaves such matters up to you, the designer and programmer.

你可以很容易地在这里使用一个控制器。例如,您可以有一个同时具有登录和注销操作的用户控制器。记住,Cutelyst的设计是非常灵活的,这要取决于你,设计师和程序员。

Then open src/login.cpp, and update the definition of the method index to match:

然后打开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");
}

This controller fetches the username and password values from the login form and attempts to authenticate the user. If successful, it redirects the user to the book list page. If the login fails, the user will stay at the login page and receive an error message. If the username and password values are not present in the form, the user will be taken to the empty login form.

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

Note that we could have used something like C_ATTR(index, :Path), however, it is generally recommended (partly for historical reasons, and partly for code clarity) only to use an action that matches everything in Root controller of MyApp, and then mainly to generate the 404 not found page for the application.

请注意,我们可以使用C_ATTR(index, :Path)之类的东西,但是,通常建议(部分出于历史原因,部分出于代码清晰性)只使用与MyApp根控制器中的所有内容匹配的操作,然后主要为应用程序生成404 not found页面。

Instead, we are using C_ATTR(somename, :Path :Args(0)) here to specifically match the URL /login. Path actions (aka, "literal actions") create URI matches relative to the namespace of the controller where they are defined. Although Path supports arguments that allow relative and absolute paths to be defined, here we use an empty Path definition to match on just the name of the controller itself. The method name, index, is arbitrary. We make the match even more specific with the :Args(0) action modifier -- this forces the match on only /login, not /login/somethingelse.

相反,我们在这里使用C_ATTR(somename, :Path :Args(0))来专门匹配URL/login。路径行为(也称为“文字行为”)创建与定义它们的控制器名称空间相关的URI匹配。尽管Path支持允许定义相对路径和绝对路径的参数,但这里我们使用空路径定义来匹配控制器本身的名称。方法名index是任意的。我们使用:Args(0)行为修饰符使匹配更加具体——这将强制只在/login,而不是/login/somethingelse上进行匹配。

Next, update the corresponding method in src/logout.cpp to match:

接下来,在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("/"));
}

Include Login and Logout Controllers

包括登录和注销控制器

Edit src/myapp.cpp and update it as follows:

编辑src/myapp.cpp,并更新如下:

#include "login.h"
#include "logout.h"

bool MyApp::init()
{
    ...
    new Login(this);

    new Logout(this);
    ...
}

Add a Login Form Template Page

添加登录表单模板页面

Create a login form by opening root/src/login.html and inserting:

创建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>

Add Valid User Check

添加有效的用户检查

We need something that provides enforcement for the authentication mechanism -- a global mechanism that prevents users who have not passed authentication from reaching any pages except the login page. This is generally done via an Auto action/method in src/root.cpp.

我们需要一种为身份验证机制提供强制的机制——一种防止未通过身份验证的用户访问除登录页面以外的任何页面的全局机制。这通常是通过src/root.cpp中的Auto 行为/方法完成的。

Edit the existing src/root.h and src/root.cpp class file and insert the following method:

编辑现有的src/root.h和src/root.cpp类文件,并插入以下方法:

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);
};
#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;
}

As discussed in "CREATE A CUTELYST CONTROLLER" in Cutelyst::Tutorial::03_MoreCutelystBasics, every Auto method from the application/root controller down to the most specific controller will be called. By placing the authentication enforcement code inside the auto method of src/root.cpp, it will be called for every request that is received by the entire application.

正如Cutelyst::Tutorial::03_MoreCutelystBasics中的“创建CUTELYST控制器”中所述,从应用程序/根控制器到最特定的控制器的每个自动方法都将被调用。通过将身份验证强制代码放在src/root的auto.cpp方法中。整个应用程序收到的每个请求都会调用它。

Try Out Authentication

测试验证

The development server should have reloaded each time we recompiled the application in the previous section. Now try going to http://localhost:3000/books/list and you should be redirected to the login page, hitting Shift+Reload or Ctrl+Reload if necessary. Note the ***Root::Auto User not found... debug message in the application server output. Enter username test01 and password mypass, and you should be taken to the Book List page.

​在上一节中,每次我们重新编译应用程序时,开发服务器都应该重新加载。现在试着去http://localhost:3000/books/list你应该被重定向到登录页面,必要时点击Shift+Reload或Ctrl+Reload。注意:***Root::Auto User not found...,应用程序服务器输出中的调试消息。输入用户名test01和密码mypass,您将进入图书列表页面。

IMPORTANT NOTE: If you are having issues with authentication on Internet Explorer (or potentially other browsers), be sure to check the system clocks on both your server and client machines. Internet Explorer is very picky about timestamps for cookies. You can use the ntpq -p command to check time sync and/or use the following command to force a sync:

重要提示:如果您在Internet Explorer(或其他浏览器)上遇到身份验证问题,请务必检查服务器和客户端计算机上的系统时钟。Internet Explorer对Cookie的时间戳非常挑剔。您可以使用ntpq-p命令检查时间同步和/或使用以下命令强制同步:

$ sudo ntpdate-debian

Or, depending on your firewall configuration, try it with -u:

或者,根据您的防火墙配置,使用-u:

sudo ntpdate-debian -u

Note: NTP can be a little more finicky about firewalls because it uses UDP vs. the more common TCP that you see with most Internet protocols. Worse case, you might have to manually set the time on your development box instead of using NTP.

注意:NTP对防火墙可能会更挑剔一些,因为它使用UDP,而不是大多数互联网协议中更常见的TCP。更糟糕的情况是,您可能需要在开发框中手动设置时间,而不是使用NTP。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值