1. 程式人生 > >一個簡單的MariaDB認證插件demo

一個簡單的MariaDB認證插件demo

ring 成員 read 很多 新建用戶 eas 解決方法 上進 文件復制

代碼地址如下:
http://www.demodashi.com/demo/13076.html

一、前言

眾所周知(其實可能很多人不知道)MariaDB支持插件認證。在MariaDB中新建用戶,常見的語句是:

CREATE USER ‘username‘@‘host‘ IDENTIFIED BY ‘password‘;

這樣創建的用戶,登錄時的認證方式是密碼。其實創建用戶的語句還可以是:

CREATE USER ‘username‘@‘host‘ IDENTIFIED VIA ‘pluginname‘ USING ‘authstring‘;

這樣創建的用戶,登錄時的認證方式由插件決定。

本文展示了編寫一個簡單的MariaDB認證插件的全過程。實現的認證機制是用戶輸入正確的姓名學號即可登錄。顯然這一認證機制毫無安全性可言,本文重點在於展示插件編寫過程。

本文內容基於MariaDB-10.1.8,操作系統是Ubuntu12.04。假設已經安裝好了數據庫。

二、基本原理

一個認證插件分為兩部分,服務器側和客戶端側,兩者配合,才能完成整個認證過程。最常見的認證情景是服務器側提問,客戶端側回答。

MariaDB提供了一個通用的客戶端側“dialog”,該客戶端側的功能是接收服務器側的問題,將問題顯示在終端上,並在終端上讀取待登錄用戶的回答,之後將回答發送給服務器側。它支持不限個數的問答,還支持普通問題和密碼問題兩種問題,普通問題在待登錄用戶輸入回答時是有回顯的,密碼問題在待登錄用戶輸入回答時是沒有回顯的。由於最後一個問題需要特殊處理,所以實際上有四種類型的問題。問題字符串的第一個字節是問題類型,宏定義如下:

/* mysql/auth_dialog_client.h */
#define ORDINARY_QUESTION       "\2"
#define LAST_QUESTION           "\3"
#define PASSWORD_QUESTION       "\4"
#define LAST_PASSWORD           "\5"

由於我們想要編寫一個簡單的認證插件,所以簡單起見,客戶端側就使用“dialog”,完全滿足要求。這樣,我們便只用編寫服務器側部分。

服務器側部分要做的事情便是與客戶端側的“dialog”通訊,讀取輸入的姓名學號進行驗證。具體實現見下節。

三、編寫代碼

1、套路部分

認證插件的套路如下:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <mysql/plugin_auth.h>
#include <mysql/auth_dialog_client.h>

static int school_number_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
{
    /* 該函數是實際上進行認證的地方,
    認證通過返回CR_OK,
    認證失敗返回CR_ERROR; */
}

static struct st_mysql_auth my_auth_plugin=
{
    MYSQL_AUTHENTICATION_INTERFACE_VERSION, // 插件的接口版本號
    "dialog", // 客戶端側處理函數,我們直接使用了“dialog”,也可以自定義
    school_number_auth // 服務器側處理函數
};

mysql_declare_plugin(dialog)
{
    MYSQL_AUTHENTICATION_PLUGIN, // 插件類型
    &my_auth_plugin, // 插件結構體指針
    "school_number", // 插件名
    "Werner", // 作者
    "A simple MariaDB auth plugin", // 描述
    PLUGIN_LICENSE_GPL, // 許可證書
    NULL,
    NULL,
    0x0100,
    NULL,
    NULL,
    NULL,
    0,
}
mysql_declare_plugin_end;

mysql_declare_plugin聲明了一個插件,其中寫明了插件名、插件類型、作者、描述和許可證書等信息,
最重要的是插件結構體指針“&my_auth_plugin”。

插件結構體指針“&my_auth_plugin”指向插件結構體“my_auth_plugin”,該結構體中寫明了客戶端側處理函數和服務器側處理函數。在我們編寫的插件中,客戶端側處理函數直接寫字符串"dialog",表示使用MariaDB提供的通用客戶端側“dialog”,服務器側處理函數school_number_auth是實際上進行認證的地方,認證通過返回CR_OK,認證失敗返回CR_ERROR。CR_OK和CR_ERROR宏定義如下:

/* mysql/plugin_auth_common.h */
#define CR_ERROR 0
#define CR_OK -1

我們只需要完善函數school_number_auth即可。

2、認證部分

在這一小節中,我們將完善函數school_number_auth。

首先看該函數的兩個參數“MYSQL_PLUGIN_VIO *vio”和“MYSQL_SERVER_AUTH_INFO *info”。

“MYSQL_PLUGIN_VIO”中的“VIO”的含義是虛擬輸入輸出,它的定義如下所示:

/* mysql/plugin_auth.h.pp */
typedef struct st_plugin_vio
{
    int (*read_packet)(struct st_plugin_vio *vio,
    unsigned char **buf);
    int (*write_packet)(struct st_plugin_vio *vio,
    const unsigned char *packet,
    int packet_len);
    void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info);
} MYSQL_PLUGIN_VIO;

可以看到它是一個結構體,成員都是函數指針。

顧名思義,函數*read_packet是虛擬的讀,從vio中讀取以“\0”結尾的字符串,返回讀取到的字符串長度。這個讀操作是阻塞讀。

*write_packet是虛擬的寫,向vio中寫入一個字符串,需要指定寫入長度。同樣,寫操作是阻塞寫。

“MYSQL_SERVER_AUTH_INFO”的定義如下:

/* mysql/plugin_auth.h.pp */
typedef struct st_mysql_server_auth_info
{
    char *user_name; // 客戶端發送的用戶名
    unsigned int user_name_length; // 客戶端發送的用戶名長度
    const char *auth_string; // 在mysql.user表中記錄的相應賬戶的authentication_string
    unsigned long auth_string_length; // authentication_string長度
    char authenticated_as[512 +1]; // 代理用戶名,傳入時為user_name,可設置
    char external_user[512 +1]; // 系統變量external_user顯示的值,待設置
    int password_used; // 是否使用密碼,待設置
    const char *host_or_ip; // 主機或IP
    unsigned int host_or_ip_length; // 主機或IP的長度
} MYSQL_SERVER_AUTH_INFO;

由上述定義可知在“MYSQL_SERVER_AUTH_INFO”中可以取到“user_name”和“auth_string”這樣的關鍵字符串。

“password_used”的含義是“是否使用密碼”,當認證出錯時,報錯信息的後面有“Password used: Yes/No”,顯示“Yes”還是“No”就由“password_used”決定。默認為“No”,若想保存信息中顯示“Yes”,可在school_number_auth函數中設置“password_used”,代碼片段如下:

info->password_used= PASSWORD_USED_YES;

明白傳入參數的含義後很容易就可以寫出school_number_auth函數,其內容如下:

static int school_number_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
{
    int pkt_len;
    unsigned char *pkt;

    if (vio->write_packet(vio, (const unsigned char *) ORDINARY_QUESTION "Please enter your name: ", 26))
        return CR_ERROR;

    if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
        return CR_ERROR;

    if (strcmp((const char *) pkt, info->user_name))
        return CR_ERROR;

    if (vio->write_packet(vio, (const unsigned char *) LAST_QUESTION "Please enter your school number: ", 35))
        return CR_ERROR;

    if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
        return CR_ERROR;

    if (strcmp((const char *) pkt, info->auth_string))
        return CR_ERROR;

    return CR_OK;
}

至此,我們就完成了認證插件的代碼編寫,將其保存到文件my_auth_plugin.c中,然後進入到下一節。

四、編譯安裝

1、編譯

插件的代碼寫好後按如下命令編譯:

gcc $(mysql_config --cflags) -shared -fPIC -DMYSQL_DYNAMIC_PLUGIN -o my_auth_plugin.so my_auth_plugin.c

參數“-DMYSQL_DYNAMIC_PLUGIN”是必不可少的,否則編譯的時候不會報錯,但在MariaDB中執行“INSTALL PLUGIN”時會報如下錯誤:

ERROR 1127 (HY000): Can‘t find symbol ‘_mysql_plugin_interface_version_‘ in library

另外一種常見的錯誤是找不到頭文件:

#include <mysql/plugin_auth.h>
#include <mysql/auth_dialog_client.h>

解決方法是安裝相關開發包引入需要的頭文件,命令是:

sudo rpm -ivh MariaDB-devel-5.2.9-102.el5.x86_64.rpm

sudo apt-get install libmariadbclient-dev

其實不執行上述命令,將MariaDB安裝路徑下的inculde目錄加入到gcc的頭文件搜索路徑中也可以解決頭文件缺失問題。

編譯成功後得到my_auth_plugin.so。

2、復制

編譯得到.so文件後需要將.so文件復制到MariaDB的插件目錄中。進入MariaDB,用如下語句查詢插件目錄:

MariaDB [(none)]> SHOW VARIABLES LIKE ‘plugin_dir‘;
+---------------+------------------------------+
| Variable_name | Value                        |
+---------------+------------------------------+
| plugin_dir    | /usr/local/mysql/lib/plugin/ |
+---------------+------------------------------+
1 row in set (0.00 sec)

將my_auth_plugin.so復制到MariaDB的插件目錄中:

sudo cp my_auth_plugin.so /usr/local/mysql/lib/plugin/

復制完成後最好修改my_auth_plugin.so的所有者為運行MariaDB的用戶,該用戶名一般是mysql,命令如下:

sudo chown mysql /usr/local/mysql/lib/plugin/my_auth_plugin.so

3、安裝

只是將.so文件復制到MariaDB的插件目錄中還不夠,還需要在MariaDB中安裝插件,語句如下:

MariaDB [(none)]> INSTALL PLUGIN school_number SONAME ‘my_auth_plugin.so‘;
Query OK, 0 rows affected (0.00 sec)

“school_number”是插件名,定義在mysql_declare_plugin中,my_auth_plugin.so是.so文件名,不要混淆。

有安裝就有卸載,如何卸載呢?語句如下:

MariaDB [(none)]> UNINSTALL PLUGIN school_number;
Query OK, 0 rows affected (0.00 sec)

先不要執行卸載語句,或是卸載後重新安裝,後面還要用到這個插件。

五、使用插件

先創建一個使用該插件認證登錄的用戶,語句如下:

MariaDB [(none)]> CREATE USER ‘werner‘@‘localhost‘ IDENTIFIED VIA ‘school_number‘ USING ‘M201434212‘;
Query OK, 0 rows affected (0.00 sec)

退出MariaDB後以werner用戶登錄,可以看到確實使用了插件認證方式,具體過程如下圖所示。

技術分享圖片

六、項目文件目錄截圖

技術分享圖片一個簡單的MariaDB認證插件demo

代碼地址如下:
http://www.demodashi.com/demo/13076.html

註:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權

一個簡單的MariaDB認證插件demo