Mysql源码学习――用户认证原理与实现_MySQL
bitsCN.com
前几节跟踪了Connection Manager和Thread Manager,在连接的过程中,还有一个身份认证的过程,就是大家所熟悉的
验证用户名和密码的过程,我们平时做一个系统的时候,很多时候都会涉及到身份验证。今天我们就来看下Mysql是如何进
行验证的。(注意是登录,不是登陆^_^)
一、用户认证原理
我们在应用程序中实现验证的方式基本上都是创建一张用户表,里面至少包含username和password两个字段,
password基本上都是加密后进行存储的。作为数据库,对用户的限制较多,不是像我说的仅仅只有username和password
这么简单了。首先粗略的讲下访问控制。
信息系统中,访问控制分为自主访问控制(DAC)和强制访问控制(MAC)。具体到DBMS,自主访问控制就是我们所熟悉
的GRANT,REVOKE,大多数数据库都支持自助的访问控制。强制访问控制就是ORACLE中的LABEL,只有很少的一些系统支持MAC。
严格来说,登录并不属于访问控制机制,而应该属于用户身份识别和认证。在Mysql中,将登录和DAC的相关接口都实现在了
sql_acl.cc中(其实说登录是用户拥有的一种权限也未尝不可,正如ORACLE中的CREATE SESSION,不过登录并不仅仅是一种权
限,还包含很多其他的属性),从文件名大家可以看出来,ACL即ACCESS CONTROL LIST,访问控制列表,这是实现访问控制的
基本方法。下图是Mysql的整个访问控制的流程。
Mysql中用户管理模块的信息存储在系统表mysql.User中,这个表不仅仅存放了授权用户的基本信息,还存放一些权限
信息。我们首先大概看一下这个表的结构。
+-----------------------+-----------------------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------+-----------------------------------+------+-----+---------+-------+
| Host | char(60) | NO | PRI | | |
| User | char(16) | NO | PRI | | |
| Password | char(41) | NO | | | |
| Select_priv | enum('N','Y') | NO | | N | |
| Insert_priv | enum('N','Y') | NO | | N | |
| Update_priv | enum('N','Y') | NO | | N | |
| Delete_priv | enum('N','Y') | NO | | N | |
| Create_priv | enum('N','Y') | NO | | N | |
| Drop_priv | enum('N','Y') | NO | | N | |
| Reload_priv | enum('N','Y') | NO | | N | |
| Shutdown_priv | enum('N','Y') | NO | | N | |
| Process_priv | enum('N','Y') | NO | | N | |
| File_priv | enum('N','Y') | NO | | N | |
| Grant_priv | enum('N','Y') | NO | | N | |
| References_priv | enum('N','Y') | NO | | N | |
| Index_priv | enum('N','Y') | NO | | N | |
| Alter_priv | enum('N','Y') | NO | | N | |
| Show_db_priv | enum('N','Y') | NO | | N | |
| Super_priv | enum('N','Y') | NO | | N | |
| Create_tmp_table_priv | enum('N','Y') | NO | | N | |
| Lock_tables_priv | enum('N','Y') | NO | | N | |
| Execute_priv | enum('N','Y') | NO | | N | |
| Repl_slave_priv | enum('N','Y') | NO | | N | |
| Repl_client_priv | enum('N','Y') | NO | | N | |
| Create_view_priv | enum('N','Y') | NO | | N | |
| Show_view_priv | enum('N','Y') | NO | | N | |
| Create_routine_priv | enum('N','Y') | NO | | N | |
| Alter_routine_priv | enum('N','Y') | NO | | N | |
| Create_user_priv | enum('N','Y') | NO | | N | |
| Event_priv | enum('N','Y') | NO | | N | |
| Trigger_priv | enum('N','Y') | NO | | N | |
| ssl_type | enum('','ANY','X509','SPECIFIED') | NO | | | |
| ssl_cipher | blob | NO | | NULL | |
| x509_issuer | blob | NO | | NULL | |
| x509_subject | blob | NO | | NULL | |
| max_questions | int(11) unsigned | NO | | 0 | |
| max_updates | int(11) unsigned | NO | | 0 | |
| max_connections | int(11) unsigned | NO | | 0 | |
| max_user_connections | int(11) unsigned | NO | | 0 | |
+-----------------------+-----------------------------------+------+-----+---------+-------+
39 rows in set (0.01 sec)
这个表包含了39个字段,对于我们登录来说,应该主要是使用前三个字段,即Host,User,Password。
mysql> select Host,User,Password from user;
+-----------+------+----------+
| Host | User | Password |
+-----------+------+----------+
| localhost | root | |
| 127.0.0.1 | root | |
| localhost | | |
+-----------+------+----------+
3 rows in set (0.00 sec)
这里比我们预想的只需要用户名和密码的方式有所出入,多了一个Host字段,这个字段起到什么作用呢?!原来Mysql的登录认证不仅需要验证用户名和密码,还需要验证连接的主机地址,这样也是为了提高安全性吧。那如果我想一个用户在任何地址都可以进行登录岂不是要设置很多地址?Mysql提供了通配符,可以设置Host字段为*,这就代表可以匹配任何Host。具体看下这三行的意思,这三行的密码均为空。针对root用户,不需要输入密码,客户端的地址为本机。第三行的用户名为空,Host为localhost,说明本地的任何用户均可以进行登录,即使是个不存在的用户也可以登录成功,但是仅限于登录,没有其他相关的权限,无法进行实际操作。
二、源码跟踪
在Connection Manager中提到了login_connection函数用于检查用户名和密码等相关信息,其源码如下(重点的函数代码
会着色):
static bool login_connection(THD *thd)
{
NET *net= &thd->net;
int error;
DBUG_ENTER("login_connection");
DBUG_PRINT("info", ("login_connection called by thread %lu",
thd->thread_id));
/* Use "connect_timeout" value during connection phase */
my_net_set_read_timeout(net, connect_timeout);
my_net_set_write_timeout(net, connect_timeout);
error= check_connection(thd); //此处是验证的具体函数
net_end_statement(thd);
if (error)
{ // Wrong permissions
#ifdef __NT__
if (vio_type(net->vio) == VIO_TYPE_NAMEDPIPE)
my_sleep(1000); /* must wait after eof() */
#endif
statistic_increment(aborted_connects,&LOCK_status);
DBUG_RETURN(1);
}
/* Connect completed, set read/write timeouts back to default */
my_net_set_read_timeout(net, thd->variables.net_read_timeout);
my_net_set_write_timeout(net, thd->variables.net_write_timeout);
DBUG_RETURN(0);
}
此函数主要是功能是调用函数check_connection进行用户认证,由于函数check_connection过长,对其进行简化,如下所示:
static int check_connection(THD *thd)
{
uint connect_errors= 0;
NET *net= &thd->net;
ulong pkt_len= 0;
char *end;
DBUG_PRINT("info",
("New connection received on %s", vio_description(net->vio)));
#ifdef SIGNAL_WITH_VIO_CLOSE
thd->set_active_vio(net->vio);
#endif
if (!thd->main_security_ctx.host) // If TCP/IP connection
{
char ip[30];
if (vio_peer_addr(net->vio, ip, &thd->peer_port))
{
my_error(ER_BAD_HOST_ERROR, MYF(0), thd->main_security_ctx.host_or_ip);
return 1;
}
if (!(thd->main_security_ctx.ip= my_strdup(ip,MYF(MY_WME))))
return 1; /* The error is set by my_strdup(). */
thd->main_security_ctx.host_or_ip= thd->main_security_ctx.ip;
vio_in_addr(net->vio,&thd->remote.sin_addr);
if (!(specialflag & SPECIAL_NO_RESOLVE))
{
vio_in_addr(net->vio,&thd->remote.sin_addr);
thd->main_security_ctx.host=
ip_to_hostname(&thd->remote.sin_addr, &connect_errors);
/* Cut very long hostnames to avoid possible overflows */
if (thd->main_security_ctx.host)
{
if (thd->main_security_ctx.host != my_localhost)
thd->main_security_ctx.host[min(strlen(thd->main_security_ctx.host),
HOSTNAME_LENGTH)]= 0;
thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host;
}
if (connect_errors > max_connect_errors)
{
my_error(ER_HOST_IS_BLOCKED, MYF(0), thd->main_security_ctx.host_or_ip);
return 1;
}
}
...
if (acl_check_host(thd->main_security_ctx.host, thd->main_security_ctx.ip))//此处验证主机名或IP是否存在
{
my_error(ER_HOST_NOT_PRIVILEGED, MYF(0),
thd->main_security_ctx.host_or_ip);
return 1;
}
}
else /* Hostname given means that the connection was on a socket */
{
...
}
vio_keepalive(net->vio, TRUE);
...
char *user= end;
char *passwd= strend(user)+1;
uint user_len= passwd - user - 1;
char *db= passwd;
char db_buff[NAME_LEN + 1]; // buffer to store db in utf8
char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8
uint dummy_errors;
uint passwd_len= thd->client_capabilities & CLIENT_SECURE_CONNECTION ?
(uchar)(*passwd++) : strlen(passwd);
db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ?
db + passwd_len + 1 : 0;
uint db_len= db ? strlen(db) : 0;
if (passwd + passwd_len + db_len > (char *)net->read_pos + pkt_len)
{
inc_host_errors(&thd->remote.sin_addr);
my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip);
return 1;
}
...
/* If username starts and ends in "'", chop them off */
if (user_len > 1 && user[0] == '/'' && user[user_len - 1] == '/'')
{
user[user_len-1]= 0;
user++;
user_len-= 2;
}
if (thd->main_security_ctx.user)
x_free(thd->main_security_ctx.user);
if (!(thd->main_security_ctx.user= my_strdup(user, MYF(MY_WME))))
return 1; /* The error is set by my_strdup(). */
return check_user(thd, COM_CONNECT, passwd, passwd_len, db, TRUE);//验证用户名和密码
}
上面的源码主要做了如下几件事情:
获取客户端的IP和主机名
acl_check_host函数验证USER表中是否存在相应的IP或HOST,如果不存在直接报错
获取用户名和密码
check_user函数验证用户名和密码(不输入用户名默认为ODBC),如果系统表中不存在匹配的报错返回
获取用户的权限列表,验证用户的相关属性是否合法,如连接数是否超过上限,连接是否超时,操作是否超过限制等信息,如果不合法,则报错返回。
由于在一个认证的过程中涉及到的东西比较多,各个方面吧,我不能一一跟踪,只能大概了解其中的实现流程,捡重点进行
跟踪,有兴趣的童鞋自己具体跟踪吧
题外话:
Mysql中权限系统表都是在系统启动时,载入内存的(当然User表也是这样),一般情况下,不需要进行频繁的授权和回收
操作,这中情况下,权限表基本保持不变,将其在系统启动的时候载入内存的好处自然是快速的进行权限判断,减少磁盘的I/O,
你懂的^_^。有好处自然有坏处,就是在频繁进行授权和回收相关操作时,权限表需要重新载入内存,Mysql为了避免这种情况,
在手册中已经说的很清楚了,授权和回收只会反应到磁盘中,内存的数据字典信息是不会改变的,如果想立即生效,需要调用
FLUSH PRIVILEGES系统函数,这个系统函数的工作应该就是对权限系统表的RELOAD。
下篇进入实质性的介绍,通过跟踪一个建表语句,来学习Mysql是如何存储表的元数据的,即frm格式文件的剖析。
<script></script>
PS.最近工作比较清闲,却迷失了方向,一会想看OS的实现,一会想看逆向,一会又想看计算机组成原理,哎,转专业的学生伤
不起啊,计算机很神奇,我很迷茫…
摘自 心中无码
bitsCN.com
Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

Video Face Swap
Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Lorsque vous utilisez le système Win11, vous rencontrerez parfois une invite vous demandant de saisir le nom d'utilisateur et le mot de passe de l'administrateur. Cet article explique comment gérer cette situation. Méthode 1 : 1. Cliquez sur [Logo Windows], puis appuyez sur [Maj+Redémarrer] pour passer en mode sans échec ou entrez en mode sans échec de cette façon : cliquez sur le menu Démarrer et sélectionnez Paramètres ; Sélectionnez « Mise à jour et sécurité » ; sélectionnez « Redémarrer maintenant » dans « Récupération » après avoir redémarré et saisi les options, sélectionnez - Dépannage - Options avancées - Paramètres de démarrage -&mdash.

Au début des médias sociaux, vous pouviez changer votre nom de profil plusieurs fois, mais maintenant, changer de nom sur n'importe quelle application de médias sociaux s'accompagne de son propre ensemble de restrictions. Si vous souhaitez modifier votre nom d'affichage ou votre nom d'utilisateur sur Instagram, la publication ci-dessous vous expliquera à quelle fréquence vous pouvez les modifier, comment le faire et ce que vous pouvez faire lorsque vous ne pouvez pas changer votre nom sur la plateforme. Comment changer le nom d’affichage et le nom d’utilisateur sur Instagram ? Instagram propose deux emplacements pour votre nom : votre nom d'affichage et votre nom d'utilisateur, et heureusement, vous pouvez facilement modifier les deux dans l'application mobile. Le nom d'affichage est celui que vous entreriez normalement vrai

OOBE ou expérience prête à l'emploi est un processus conçu pour que les utilisateurs les guident à travers les différentes étapes des étapes post-installation. Cela inclut les pages de droits et d’accords, les pages de connexion, les options de connexion WiFi ou réseau, etc. Si vous rencontrez des problèmes OOBEKeyboard, OOBELOCAL ou OOBEREGION, vous ne pourrez pas passer aux étapes d'installation finales. Ne t'inquiète pas. Il existe quelques correctifs simples que vous pouvez utiliser pour résoudre ce problème. Solutions de contournement - Avant de faire quoi que ce soit d'autre, essayez ces solutions normales - 1. Lorsque vous recevez une invite d'erreur, continuez et cliquez sur l'invite « Réessayer ». Continuez à essayer au moins 7 à 8 fois supplémentaires. 2. Vérifiez la connectivité réseau. Si vous utilisez une connexion Ethernet ou Wi-Fi

1.1.1.1 Méthode de connexion au système d'authentification Internet : 1. Recherchez le signal sans fil du réseau du campus et connectez-vous ; 2. Ouvrez le navigateur et sélectionnez « Self-Service » sur l'interface d'authentification contextuelle ; mot de passe initial pour vous connecter ; 4. Complétez les informations personnelles et définissez un mot de passe fort.

De nos jours, de nombreux utilisateurs Windows qui aiment les jeux ont accédé au client Steam et peuvent rechercher, télécharger et jouer à n'importe quel bon jeu. Cependant, de nombreux profils d'utilisateurs peuvent porter exactement le même nom, ce qui rend difficile la recherche d'un profil ou même la liaison d'un profil Steam à d'autres comptes tiers ou la participation à des forums Steam pour partager du contenu. Le profil se voit attribuer un identifiant unique à 17 chiffres, qui reste le même et ne peut être modifié à aucun moment par l'utilisateur, contrairement au nom d'utilisateur ou à l'URL personnalisée. Quoi qu'il en soit, certains utilisateurs ne connaissent pas leur Steamid, et il est important de le savoir. Si vous ne savez pas comment retrouver le Steamid de votre compte, pas de panique. Dans cet article

JWT (JSONWebToken) est un mécanisme léger d'authentification et d'autorisation qui utilise des objets JSON comme jetons de sécurité pour transmettre en toute sécurité les informations d'identité des utilisateurs entre plusieurs systèmes. ThinkPHP6 est un framework MVC efficace et flexible basé sur le langage PHP. Il fournit de nombreux outils et fonctions utiles, notamment le mécanisme d'authentification JWT. Dans cet article, nous présenterons comment utiliser ThinkPHP6 pour l'authentification JWT afin de garantir la sécurité et la fiabilité des applications Web.

Comment remplir le nom d'utilisateur de Railway 12306 ? Vous pouvez remplir le nom d'utilisateur dans l'application Railway 12306, mais la plupart des amis ne savent pas comment remplir le nom d'utilisateur de Railway 12306. Vient ensuite le didacticiel graphique sur la façon de remplir dans le nom d'utilisateur du chemin de fer 12306 apporté par l'éditeur , les utilisateurs intéressés viennent y jeter un œil ! Didacticiel d'utilisation du Railway 12306 Comment remplir le nom d'utilisateur du Railway 12306 1. Ouvrez d'abord l'application Railway 12306 et cliquez sur [S'inscrire] en bas de la page principale. 2. Ensuite, sur la page de fonction d'enregistrement, entrez le nom d'utilisateur, le mot de passe et la confirmation ; mot de passe, etc.; 3. Enfin, entrez Une fois terminé, vous pouvez remplir l'enregistrement de l'utilisateur.

Le nom d'utilisateur wifi fait référence au nom d'utilisateur administratif du routeur sans fil. Ce nom d'utilisateur, l'adresse IP du routeur et le mot de passe administratif par défaut sont généralement imprimés au bas du routeur sans fil et peuvent également être trouvés dans le manuel du routeur sans fil. ; l'utilisateur administratif par défaut de la plupart des routeurs. Le nom est à la fois admin et le mot de passe de gestion est également admin.
