一个简单的MariaDB认证插件demo
一、前言
众所周知(其实可能很多人不知道)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
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权
一个简单的MariaDB认证插件demo的更多相关文章
- 如何使用AEditor制作一个简单的H5交互页demo
转载自:http://www.alloyteam.com/2015/06/h5-jiao-hu-ye-bian-ji-qi-aeditor-jie-shao/ 本教程演示如何使用AEditor制作一个 ...
- 从0开始写一个简单的vite hmr 插件
从0开始写一个简单的vite hmr 插件 0. 写在前面 在构建前端项目的时候,除开基本的资源格式(图片,json)以外,还常常会需要导入一些其他格式的资源,这些资源如果没有第三方vite插件的支持 ...
- 一个简单的IM系统(Demo附源码)-- ESFramework 4.0 快速上手(08)
前面的文章已经介绍完了基于ESFramework/ESPlus进行二次开发的所有要点,现在,我们可以开始小试牛刀了. 本文将介绍使用ESFramework的Rapid引擎开发的两个最简单的Demo,E ...
- 【Spring Boot】创建一个简单的Spring Boot的 Demo
走进Spring Boot 文章目录 走进Spring Boot 环境搭建 新建Spring Boot项目 开始创建项目 配置JDK版本 和 Initializr Service URL 配置Proj ...
- 也谈SSO,一个简单实用的单点登录Demo
关于SSO(单点登录),百度百科解释如下 : “SSO英文全称Single Sign On,单点登录.SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.它包括可以将这次主要 ...
- 搭建CAS服务器,并实现一个简单的单点登录的demo
官网:http://jasig.github.io/cas/Cas Server下载:http://developer.jasig.org/cas/Cas Client下载:http://develo ...
- 一个简单的 IDA f5插件问题分析
有人提出问题,以下汇编f5结果缺失代码: .text:00000C18 Java_com_a_b_c .text:00000C18 PUSH {R3,LR} .text:00000C1A CMP R2 ...
- 聊聊UDP、TCP和实现一个简单的JAVA UDP小Demo
最近真的比较忙,很久就想写了,可是一直苦于写点什么,今天脑袋灵光一闪,觉得自己再UDP方面还有些不了解的地方,所以要给自己扫盲. 好了,咱们进入今天的主题,先列一下提纲: 1. UDP是什么,UDP适 ...
- 一个简单的物料防错DEMO
前言 快2个月没写过博客了,就算是记流水账似的文章都没时间写,主要是太忙了:太多的bug要修复.太多由于bug引起的异常问题要解决.还有新的项目要开发,不忙怎么行呢?最近利用业余时间在鼓捣一个PDA的 ...
随机推荐
- HDU 6318.Swaps and Inversions-求逆序对-线段树 or 归并排序 or 离散化+树状数组 (2018 Multi-University Training Contest 2 1010)
6318.Swaps and Inversions 这个题就是找逆序对,然后逆序对数*min(x,y)就可以了. 官方题解:注意到逆序对=交换相邻需要交换的次数,那么输出 逆序对个数 即可. 求逆序对 ...
- poj3233(等比矩阵求和)
poj3233 题意 给出一个 \(n \times n\) 的矩阵 \(A\) ,求 \(A + A^2 + A^3 + ... + A^k\) . 分析 构造矩阵 \[ \begin{bmatri ...
- 位运算和enum中的位运算
1.位逻辑非运算 ~ 位逻辑非运算是单目的,只有一个运算对象.位逻辑非运算按位对运算对象的值进行非运算,即:如果某一位等于0,就将其转变为1:如果某一位等于1,就将其转变为0. 比如,对二进制的100 ...
- HDOJ 2582 f(n)
Discription This time I need you to calculate the f(n) . (3<=n<=1000000) f(n)= Gcd(3)+Gcd(4)+… ...
- 1.3(Mybatis学习笔记)动态SQL
一.<if> 使用<if>可以根据具体情况来拼接SQL语句,使其更加灵活更加适应我们的需求. <if>的标签体中是需要拼接的语句,满足条件才会将其进行拼接. < ...
- 【报错】引入jar包import org.apache.commons.codec.digest.DigestUtils 报错,jar不存在
import org.apache.commons.codec.digest.DigestUtils报错.缺少jar maven引用jar包地址: <!-- https://mvnreposit ...
- Swift,集合
1.创建(Set)集合(无序不可重复) (1)创建空集合 var a=Set<Int>() //[] (2)创建集合 var a:Set=[1,2,3] //[2,3,1] 2.集合插入( ...
- Node.js的集群功能以及在Express的配置
Node.js在v0.6.0版本下内置了集群功能,作为cluster模块,用于nodejs的多核处理,也比较容易通过脚本实现一个负载均衡的集群. 脚本参考了其他人的材料,建立一个server.js(因 ...
- wireshark----教你怎样抓包
wireshark----教你怎样抓包 wireshark是一款强大的抓包工具,走过路过一定不要错过就是了,当你学习TCP/IP协议的时候,学习使用wireshark抓包正是理论联系实际最好的方法,先 ...
- C++之共有继承、保护继承、私有继承
1.封装,public,private作用就是这个目的. 类外只能访问public成员而不能方位private成员: private成员只能被类成员和友元访问: 2.继承,protected的作用就是 ...