MySQLPlugin之如何编写Auth Plugin
转载请署名:印风
---------------------------------------------------------------------
1.什么是Auth Plugin
我们先介绍一下传统的认证方式。在MySQL服务器上的mysql.user表中存储了所有的用户信息,客户端将用户名和密码传递过来根据user表相应的行进行认证匹配,也就是说,传统的方法是客户端需要明确的给出用户名和密码。
但从5.5.7开始不再采用这种方式,而是使用Plugin来实现。通过如下方式来进行授权:
在user表里有一列为plugin,如果没有plugin时,服务器使用password列来认证,与传统方式不同的是,会使用两个内建的plugin来完成认证。
如果plugin列有值,则调用相应的plugin进行认证,如果在plugin列表里找不到,就会报错;
2. auth plugin能做什么
通过auth plugin可以做到:
1)外置式认证,例如,我们可以使用usb盘来进行认证(实现很简单),或者系统用户登录信息、LDAP等外部方式。
2)代理用户(proxy users),当一个用户被允许连接时,auth plugin可以返回给服务器一个与连接的用户不同的用户名,表明连接用户是另外一个用户的代理,从而获得其特殊的权限。关于Proxy User机制,见后记。
3. 如何编写auth plugin
1)相应的SQL:
CREATE USER 'empl_external'@'localhost'
IDENTIFIED WITH auth_plugin AS 'auth_string';
IDENTIFIED WITH 插件名 AS ‘认证字符串’
2)客户端API
从MySQL5.5开始实现了客户端PLUGIN,由于这是个全新的plugin类型,这里进行一下详细的介绍。API在文件client_plugin.h中进行了定义,主要包括如下几个部分:
(1)客户端插件的声明:
宏定义
#definemysql_declare_client_plugin(X) \MYSQL_PLUGIN_EXPORT structst_mysql_client_plugin_ ## X \_mysql_client_plugin_declaration_ ={ \MYSQL_CLIENT_ ## X ## _PLUGIN, \MYSQL_CLIENT_ ## X ## _PLUGIN_INTERFACE_VERSION,
#definemysql_end_client_plugin }
跟服务器端的Plugin类似,我们在mysql_declare_client_plugin和definemysql_end_client_plugin之间填入相应的内容
而从如下:
structst_mysql_client_plugin_AUTHENTICATION
{MYSQL_CLIENT_PLUGIN_HEADERint (*authenticate_user)(MYSQL_PLUGIN_VIO*vio, struct st_mysql *mysql);
};
可知auth plugin的声明信息中应该包含MYSQL_CLIENT_PLUGIN_HEADER,也就是st_mysql_client_plugin的信息和auth主函数
先看通用的插件声明结构体的头部信息填充的内容使用结构体st_mysql_client_plugin来决定,结构体描述如下:
字段 |
类型 |
描述 |
type |
int |
插件类型,目前仅有一种: MYSQL_CLIENT_AUTHENTICATION_PLUGIN |
interface_version |
unsigned long |
插件的接口版本号,这两个值都会由宏自动生成,因此无需在声明插件时填写 |
name |
const char* |
插件名,当在客户端使用MYSQL_DEFAULT_AUTH选项名来调用mysql_options()函数时会指定该插件名,或者通过--default-auth指定的插件名 |
author |
const char* |
作者名 |
desc |
const char* |
插件的描述信息 |
version[3] |
unsigned int |
该插件的版本号 |
license; |
const char * |
插件许可证书 |
mysql_api; |
void * |
内部使用,设置为NULL |
init |
int (*init)(char *, size_t, int, va_list); |
第一个参数存储错误消息 第二个参数表示错误消息的长度 第三个及以后的参数被传递给mysql_load_plugin().第一个表示参数的个数 |
deinit |
int (*deinit)(); |
无参数,当客户端unload 插件的时候被调用 |
options |
int (*options)(const char *option, const void *); |
用于处理传递给plugin的选项,第一个参数执行选项名,第二个参数指向选项值 |
主函数定义如下:
int(*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql);
第二个参数无需多言,相信大家都很熟悉,重点介绍一下第一个参数MYSQL_PLUGIN_VIO,其对应的结构体为st_plugin_vio,如下:
字段 |
类型 |
描述 |
read_packet |
int (*read_packet)(struct st_plugin_vio *vio, unsigned char **buf); |
从vio里读一个记录,以’\0’结尾的字符串 |
write_packet |
int (*write_packet)(struct st_plugin_vio *vio, const unsigned char *packet, int packet_len); |
向vio中写一个字符串,需要提供字符串指针地址和长度 |
info |
void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info); |
函数指针,用于向st_plugin_vio_info结构体中填充连接信息 |
st_plugin_vio_info指定了客户端和服务器端通信的协议和socket
字段 |
类型 |
描述 |
protocol |
enum { MYSQL_VIO_INVALID, MYSQL_VIO_TCP, MYSQL_VIO_SOCKET, MYSQL_VIO_PIPE, MYSQL_VIO_MEMORY } |
枚举类型,指定通信的协议 |
socket |
int |
套接字 |
对于客户端authplugin,我们一般需要调用write_packet将需要认证的字符串传递给服务器。
函数返回值:
CR_ERROR 0 |
认证失败,表明发生了一个错误 |
CR_OK -1 |
客户端认证成功,这不表明服务器端接受其认证,通常表明发送用户名和认证信息成功 |
CR_OK_HANDSHAKE_COMPLETE -2 |
当无法确定服务器与客户端的交互次数时,需要读更多的包,使用该返回值表明交互结束 |
3)服务器端API
在文件plugin_auth.h中定义了服务的auth plugin的API,
插件描述结构体:st_mysql_auth
字段 |
类型 |
描述 |
interface_version |
int |
接口版本号: MYSQL_AUTHENTICATION_INTERFACE_VERSION |
client_auth_plugin |
const char * |
客户端的插件名 |
authenticate_user |
int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info); |
主要认证函数,用于处理客户端的请求,返回0表示认证成功, |
认证函数:
int(*authenticate_user)(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO*info);
MYSQL_PLUGIN_VIO结构体前面已经介绍了,这里不再赘述,主要介绍一下MYSQL_SERVER_AUTH_INFO,也就是st_mysql_server_auth_info,如下所示:
字段 |
类型 |
描述 |
user_name |
char * |
客户端发送的用户名,使用show user()显示,如果为NULL表示还没接受到包含用户名的包 |
user_name_length |
unsigned int |
用户名长度 |
auth_string |
const char* |
在mysql.user表中记录的相应账户的 authentication_string |
auth_string_length |
unsigned long |
长度 |
authenticated_as |
代理用户名, show current_user(),初始时server将其设置为user_name,但在plugin里可以对其进行修改 |
|
external_user |
char external_user[512] |
系统变量external_user显示的值 |
password_used |
int |
当认证失败时使用该字段,用于显示错误信息: Authentication fails. Password used: %s %s为: 0:NO 1:YES 2:没有%s |
host_or_ip |
const char* |
host/ip |
host_or_ip_lenght |
unsigned int |
host/ip的长度 |
4例子:在认证阶段两次提问,通过验证(代码摘录自mysql5.5.16),先看看效果,如下图所示:
代码如下:
/*必要的头文件*/
#include<my_global.h>
#include<mysql.h>
#include<mysql/plugin_auth.h>
#include<mysql/client_plugin.h>
#include<string.h>
#include <stdio.h>
#include<stdlib.h>#if !defined(_GNU_SOURCE)
# define _GNU_SOURCE/* for RTLD_DEFAULT */
#endif/*用于标示发送的包的类型,以便通信双方进行辨别*/
#defineORDINARY_QUESTION "\2"
#defineLAST_QUESTION "\3"
#definePASSWORD_QUESTION "\4"
#define LAST_PASSWORD "\5"/*********************SERVER SIDE ****************************************/static inttwo_questions(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
{unsigned char *pkt;int pkt_len;/* 发送一个问题,提示客户端输入密码*/if (vio->write_packet(vio, (const unsignedchar *) PASSWORD_QUESTION "Password, please:", 18))return CR_ERROR;/* 阻塞读密码 */if ((pkt_len= vio->read_packet(vio,&pkt)) < 0)return CR_ERROR;info->password_used= PASSWORD_USED_YES;/* 如果密码为错误时,返回ERROR */if (strcmp((const char *) pkt,info->auth_string))return CR_ERROR;/* 确认密码吗? */if (vio->write_packet(vio, (const unsignedchar *) LAST_QUESTION "Are you sure ?", 15))return CR_ERROR;/*读取客户端输入 */if ((pkt_len= vio->read_packet(vio,&pkt)) < 0)return CR_ERROR;/* 检查客户端输入是否为yes, of course */return strcmp((const char *) pkt, "yes,of course") ? CR_ERROR : CR_OK;
}static structst_mysql_auth two_handler=
{MYSQL_AUTHENTICATION_INTERFACE_VERSION,"dialog", /* requires dialog clientplugin */two_questions
};mysql_declare_plugin(dialog)
{MYSQL_AUTHENTICATION_PLUGIN,&two_handler,"two_questions","Sergei Golubchik","Dialog plugin demo 1",PLUGIN_LICENSE_GPL,NULL,NULL,0x0100,NULL,NULL,NULL
},
mysql_declare_plugin_end;
/*********************CLIENT SIDE ***************************************/
/*客户端向服务器端发送认证信息的方式有多种,比如说通过终端、GUI或者其他一些设备,使用函数指针来*/
typedef char*(*mysql_authentication_dialog_ask_t)(struct st_mysql *mysql,int type, const char*prompt, char *buf, int buf_len);staticmysql_authentication_dialog_ask_t ask;/*获取终端的输入*/
static char*builtin_ask(MYSQL *mysql __attribute__((unused)),int type__attribute__((unused)),const char *prompt,char *buf, intbuf_len)
{char *ptr;
/*打印服务器端传递的提示信息*/fputs(prompt, stdout);fputc(' ', stdout);if (fgets(buf, buf_len, stdin) == NULL)return NULL;if ((ptr= strchr(buf, '\n')))*ptr= 0;return buf;
}static intperform_dialog(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
{unsigned char *pkt, cmd= 0;int pkt_len, res;char reply_buf[1024], *reply;do{/* read the prompt */pkt_len= vio->read_packet(vio,&pkt);if (pkt_len < 0)return CR_ERROR;if (pkt == 0){/*in mysql_change_user()the client sends the first packet, sothe first vio->read_packet() doesnothing (pkt == 0).We send the "password",assuming the client knows what it's doing.(in other words, the dialog pluginshould be only set as a defaultauthentication plugin on the client ifthe first questionasks for a password - which will besent in clear text, by the way)*/reply= mysql->passwd;}else{cmd= *pkt++;/* is it MySQL protocol packet ? */if (cmd == 0 || cmd == 254)return CR_OK_HANDSHAKE_COMPLETE; /*yes. we're done *//*asking for a password with an emptyprompt means mysql->passwordotherwise we ask the user and read thereply*/if ((cmd >> 1) == 2 && *pkt== 0)reply= mysql->passwd;elsereply= ask(mysql, cmd >> 1, (constchar *) pkt,reply_buf, sizeof(reply_buf));if (!reply)return CR_ERROR;}/* send the reply to the server */res= vio->write_packet(vio, (constunsigned char *) reply,strlen(reply)+1);if (reply != mysql->passwd &&reply != reply_buf)free(reply);if (res)return CR_ERROR;/* repeat unless it was the last question*/} while ((cmd & 1) != 1);/* the job of reading the ok/error packet isleft to the server */return CR_OK;
}/**initialization function of the dialog pluginPick up the client'sauthentication_dialog_ask() function, if exists,or fall back to the default implementation.
*/static intinit_dialog(char *unused1 __attribute__((unused)),size_t unused2 __attribute__((unused)),int unused3 __attribute__((unused)),va_list unused4__attribute__((unused)))
{void *sym= dlsym(RTLD_DEFAULT,"mysql_authentication_dialog_ask");ask= sym ?(mysql_authentication_dialog_ask_t) sym : builtin_ask;return 0;
}mysql_declare_client_plugin(AUTHENTICATION)"dialog","Sergei Golubchik","Dialog Client AuthenticationPlugin",{0,1,0},"GPL",NULL,init_dialog,NULL,NULL,perform_dialog
mysql_end_client_plugin;
后记:
(翻译、整理自官方文档)
在mysql5.5中,什么是proxy user呢?
客户端请求的用户名(externaluser)可以通过代理另一个用户来获得其权限,external user成为了proxy user,而另一个被代理的用户名成为proxied user,
为了实现该机制,需要满足如下条件:
1).当一个客户端连接被视为代理人时,auth plugin需要返回一个不同的用户名;
2).代理人账户必须设置为通过auth plugin来认证,通过CREATE USER/GRANT来分配账户;
3).代理人账户必须有PROXY权限,举例如下:
CREATE USER 'empl_external'@'localhost'
IDENTIFIED WITH auth_plugin AS 'auth_string';
CREATE USER 'employee'@'localhost'
IDENTIFIED BY 'employee_pass';
GRANT PROXY
ON 'employee'@'localhost'
TO 'empl_external'@'localhost';
http://dev.mysql.com/doc/refman/5.5/en/c-api-plugin-functions.html
MySQLPlugin之如何编写Auth Plugin相关推荐
- 转mosquitto auth plugin 编译配置
配置使用 mysql 作为 be (back end) 使用config.mk 配置编译参数 cp config.mk.in config.mk 修改 安装 mysql sudo apt-get in ...
- mosquitto mysql_转mosquitto auth plugin 编译配置
配置使用 mysql 作为 be (back end) 使用config.mk 配置编译参数 cp config.mk.in config.mk 修改 安装 mysql sudo apt-get in ...
- mysql sysvar int_MySQL:如何编写daemon plugin
1.什么是DaemonPlugin 顾名思义,daemon plugin就是一种用来在后台运行的插件,在插件中,我们可以创建一些后台线程来做些有趣的事情.大名鼎鼎的handlesocket就是一个da ...
- n 如何编写html,webpack4系列教程,如何编写plugin处理html代码逻辑?
本博客不欢迎:各种镜像采集行为,请尊重知识产权法律法规.大家都是程序员,不要闹得不开心. 在上一篇文章中,利用不同位置的publicPath,对html中的cdn地址,进行了处理.但是,遗留了一个小问 ...
- IDA Plugin 编写基础
IDA Plugin 编写基础 IDA是迄今为止最为强大的反汇编器,它有着众多的功能.但是如果它不具备通过附加的模块来对标准的函数进行扩展的功能(粗俗点说就是plugin)的话,也就有负此盛名了.现在 ...
- laravel 5.2 Auth用户认证教程
官方文档:Laravel 5.2文档服务--用户认证 如果你看官方文档不太懂,那么请看下文操作. 说明 框架版本:laravel 5.2 laravel 5.2内置了auth用户认证服务,所以做网站时 ...
- 【linux】Can't connect to local MySQL server through socket和Plugin 'auth_socket' is not loaded报错...
真的是一次吐血的经历,弄了两个多小时才弄好. 问题1:直接登陆root用户报错 ERROR 2002 (HY000): Can't connect to local MySQL server thro ...
- Dynamics 365 CRM (online) 使用WebApi调用全局action(Plugin)执行批量更新操作, 前端JS批量上传记录到CRM中
创建一个action process,如下图,分别有两个inputparameter 和一个outputparameter 2.使用visual studio 2019编写一个plugin dll, ...
- fastify 后台_如何使用Fastify启动和运行
fastify 后台 Fast and low overhead web framework, for Node.js 快速,低开销的Web框架,用于Node.js Fastify version 1 ...
最新文章
- android相机截取矩形框,Android自定义照相机实现只拍摄矩形区域(重传)
- java 坦克重叠_坦克大战中坦克一直有重叠是怎么回事
- 于金刚消息引擎服务器,基于MQTT的安全通信服务器的研究与实现
- 计算机与人脑_类脑计算机:一种新型的计算系统
- 十二生肖配对表查询_天蝎座:分手后最容易复合的星座配对,一生分不开,最终重新走到一起...
- 高通平台如何新增加一个分区,并mount到android系统中
- VS2015安装激活与部署
- Excel作图-制作复合饼图
- SkipList(跳跃表)详解
- java maven 编辑器,Maven compiler 插件
- 【088】中国大学MOOC-高教社大学课程学习平台
- style常见的样式属性
- 大数据发展前沿 期末总结复习
- 【5G】5G通信网络中资源分配和负载均衡算法的matlab仿真
- 详解one—hot编码(独热编码)
- android 多语言的实现
- java自动化测试语言高级之泛型
- 多单、空单、开仓、平仓、持仓、现货、期货、通货膨胀.......
- 数据挖掘导论 复习一(介绍+数据预处理方法+定性归纳)
- Jenkins 用户角色及权限管理
热门文章
- 谁说程序员不好找对象,网友:站出来,绝对不打死你!
- 对话趣链张帅:区块链与数据融合,价值三段论凸显 | SDBD2020・算力在线
- 实践出真知:博云微服务经验之避坑指南
- 京东一面:Spring Boot 如何热加载jar实现动态插件?
- 数据防泄密专家为企业数据泄漏出谋划策
- 【学习OpenCV】基于opencv的直线和曲线拟合与绘制(最小二乘法)
- 优盘引导ghost恢复系统备份
- 5个优质动漫资源网,满满的干货,爱看动漫的朋友偷偷收藏起来吧
- 微信小程序自定义导航栏 胶囊菜单按钮高度完美适配 原理简单 赛过一些大厂的适配 妈妈再也不用担心我强迫症啦
- Hello SSVM: 利用 SSVM + wasm 实现万花尺图案的绘制