转载请署名:印风

---------------------------------------------------------------------

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

char authenticated_as[MYSQL_USERNAME_LENGTH+1]

代理用户名,

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

当客户端以用户名empl_external 发起连接时,auth_plugin 通过一些信息(auth_string或其他一些信息)来确认是否接受该用户,并返回用户名employee给服务器端;服务器端会检查empl_external用户是否有在employee上的PROXY权限

Client plugin API:

http://dev.mysql.com/doc/refman/5.5/en/c-api-plugin-functions.html

MySQLPlugin之如何编写Auth Plugin相关推荐

  1. 转mosquitto auth plugin 编译配置

    配置使用 mysql 作为 be (back end) 使用config.mk 配置编译参数 cp config.mk.in config.mk 修改 安装 mysql sudo apt-get in ...

  2. mosquitto mysql_转mosquitto auth plugin 编译配置

    配置使用 mysql 作为 be (back end) 使用config.mk 配置编译参数 cp config.mk.in config.mk 修改 安装 mysql sudo apt-get in ...

  3. mysql sysvar int_MySQL:如何编写daemon plugin

    1.什么是DaemonPlugin 顾名思义,daemon plugin就是一种用来在后台运行的插件,在插件中,我们可以创建一些后台线程来做些有趣的事情.大名鼎鼎的handlesocket就是一个da ...

  4. n 如何编写html,webpack4系列教程,如何编写plugin处理html代码逻辑?

    本博客不欢迎:各种镜像采集行为,请尊重知识产权法律法规.大家都是程序员,不要闹得不开心. 在上一篇文章中,利用不同位置的publicPath,对html中的cdn地址,进行了处理.但是,遗留了一个小问 ...

  5. IDA Plugin 编写基础

    IDA Plugin 编写基础 IDA是迄今为止最为强大的反汇编器,它有着众多的功能.但是如果它不具备通过附加的模块来对标准的函数进行扩展的功能(粗俗点说就是plugin)的话,也就有负此盛名了.现在 ...

  6. laravel 5.2 Auth用户认证教程

    官方文档:Laravel 5.2文档服务--用户认证 如果你看官方文档不太懂,那么请看下文操作. 说明 框架版本:laravel 5.2 laravel 5.2内置了auth用户认证服务,所以做网站时 ...

  7. 【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 ...

  8. Dynamics 365 CRM (online) 使用WebApi调用全局action(Plugin)执行批量更新操作, 前端JS批量上传记录到CRM中

    创建一个action process,如下图,分别有两个inputparameter 和一个outputparameter 2.使用visual studio 2019编写一个plugin dll, ...

  9. fastify 后台_如何使用Fastify启动和运行

    fastify 后台 Fast and low overhead web framework, for Node.js 快速,低开销的Web框架,用于Node.js Fastify version 1 ...

最新文章

  1. android相机截取矩形框,Android自定义照相机实现只拍摄矩形区域(重传)
  2. java 坦克重叠_坦克大战中坦克一直有重叠是怎么回事
  3. 于金刚消息引擎服务器,基于MQTT的安全通信服务器的研究与实现
  4. 计算机与人脑_类脑计算机:一种新型的计算系统
  5. 十二生肖配对表查询_天蝎座:分手后最容易复合的星座配对,一生分不开,最终重新走到一起...
  6. 高通平台如何新增加一个分区,并mount到android系统中
  7. VS2015安装激活与部署
  8. Excel作图-制作复合饼图
  9. SkipList(跳跃表)详解
  10. java maven 编辑器,Maven compiler 插件
  11. 【088】中国大学MOOC-高教社大学课程学习平台
  12. style常见的样式属性
  13. 大数据发展前沿 期末总结复习
  14. 【5G】5G通信网络中资源分配和负载均衡算法的matlab仿真
  15. 详解one—hot编码(独热编码)
  16. android 多语言的实现
  17. java自动化测试语言高级之泛型
  18. 多单、空单、开仓、平仓、持仓、现货、期货、通货膨胀.......
  19. 数据挖掘导论 复习一(介绍+数据预处理方法+定性归纳)
  20. Jenkins 用户角色及权限管理

热门文章

  1. 谁说程序员不好找对象,网友:站出来,绝对不打死你!
  2. 对话趣链张帅:区块链与数据融合,价值三段论凸显 | SDBD2020・算力在线
  3. 实践出真知:博云微服务经验之避坑指南
  4. 京东一面:Spring Boot 如何热加载jar实现动态插件?
  5. 数据防泄密专家为企业数据泄漏出谋划策
  6. 【学习OpenCV】基于opencv的直线和曲线拟合与绘制(最小二乘法)
  7. 优盘引导ghost恢复系统备份
  8. 5个优质动漫资源网,满满的干货,爱看动漫的朋友偷偷收藏起来吧
  9. 微信小程序自定义导航栏 胶囊菜单按钮高度完美适配 原理简单 赛过一些大厂的适配 妈妈再也不用担心我强迫症啦
  10. Hello SSVM: 利用 SSVM + wasm 实现万花尺图案的绘制