溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Mysql用戶認證的原理是什么

發(fā)布時間:2021-08-07 16:25:19 來源:億速云 閱讀:147 作者:Leah 欄目:數(shù)據(jù)庫

Mysql用戶認證的原理是什么,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

一、用戶認證原理

      我們在應用程序中實現(xiàn)驗證的方式基本上都是創(chuàng)建一張用戶表,里面至少包含username和password兩個字段,

password基本上都是加密后進行存儲的。作為數(shù)據(jù)庫,對用戶的限制較多,不是像我說的僅僅只有username和password

這么簡單了。首先粗略的講下訪問控制。

     信息系統(tǒng)中,訪問控制分為自主訪問控制(DAC)和強制訪問控制(MAC)。具體到DBMS,自主訪問控制就是我們所熟悉

的GRANT,REVOKE,大多數(shù)數(shù)據(jù)庫都支持自助的訪問控制。強制訪問控制就是ORACLE中的LABEL,只有很少的一些系統(tǒng)支持MAC。

嚴格來說,登錄并不屬于訪問控制機制,而應該屬于用戶身份識別和認證。在Mysql中,將登錄和DAC的相關(guān)接口都實現(xiàn)在了

sql_acl.cc中(其實說登錄是用戶擁有的一種權(quán)限也未嘗不可,正如ORACLE中的CREATE SESSION,不過登錄并不僅僅是一種權(quán)

限,還包含很多其他的屬性),從文件名大家可以看出來,ACL即ACCESS CONTROL LIST,訪問控制列表,這是實現(xiàn)訪問控制的

基本方法。下圖是Mysql的整個訪問控制的流程。

    Mysql中用戶管理模塊的信息存儲在系統(tǒng)表mysql.User中,這個表不僅僅存放了授權(quán)用戶的基本信息,還存放一些權(quán)限

信息。我們首先大概看一下這個表的結(jié)構(gòu)。

+-----------------------+-----------------------------------+------+-----+---------+-------+

| 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,說明本地的任何用戶均可以進行登錄,即使是個不存在的用戶也可以登錄成功,但是僅限于登錄,沒有其他相關(guān)的權(quán)限,無法進行實際操作。

二、跟蹤

       在Connection Manager中提到了login_connection函數(shù)用于檢查用戶名和密碼等相關(guān)信息,其源碼如下(重點的函數(shù)代碼

會著色):

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); //此處是驗證的具體函數(shù)

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

}

此函數(shù)主要是功能是調(diào)用函數(shù)check_connection進行用戶認證,由于函數(shù)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函數(shù)驗證USER表中是否存在相應的IP或HOST,如果不存在直接報錯

獲取用戶名和密碼

check_user函數(shù)驗證用戶名和密碼(不輸入用戶名默認為ODBC),如果系統(tǒng)表中不存在匹配的報錯返回

獲取用戶的權(quán)限列表,驗證用戶的相關(guān)屬性是否合法,如連接數(shù)是否超過上限,連接是否超時,操作是否超過限制等信息,如果不合法,則報錯返回。

    由于在一個認證的過程中涉及到的東西比較多,各個方面吧,我不能一一跟蹤,只能大概了解其中的實現(xiàn)流程,撿重點進行

跟蹤,有興趣的童鞋自己具體跟蹤吧

題外話:

    Mysql中權(quán)限系統(tǒng)表都是在系統(tǒng)啟動時,載入內(nèi)存的(當然User表也是這樣),一般情況下,不需要進行頻繁的授權(quán)和回收

操作,這中情況下,權(quán)限表基本保持不變,將其在系統(tǒng)啟動的時候載入內(nèi)存的好處自然是快速的進行權(quán)限判斷,減少磁盤的I/O,

你懂的^_^。有好處自然有壞處,就是在頻繁進行授權(quán)和回收相關(guān)操作時,權(quán)限表需要重新載入內(nèi)存,Mysql為了避免這種情況,

在手冊中已經(jīng)說的很清楚了,授權(quán)和回收只會反應到磁盤中,內(nèi)存的數(shù)據(jù)字典信息是不會改變的,如果想立即生效,需要調(diào)用

FLUSH PRIVILEGES系統(tǒng)函數(shù),這個系統(tǒng)函數(shù)的工作應該就是對權(quán)限系統(tǒng)表的RELOAD。

看完上述內(nèi)容,你們掌握Mysql用戶認證的原理是什么的方法了嗎?如果還想學到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI