Node.js Tips: mysql8にはmysql2を使う

mysql

私が参考にしている書籍では、Node.jsからMySQLにアクセスするためにnpmパッケージとして公開されている「mysql」というモジュールを使用していますが、これが動作しなかったので解決方法を記事にします。

それでは今回も0.1%の戦闘力アップです!

事象

mysqlモジュールでconnectionオブジェクトを作成し、MySQLへ接続するときにエラーが発生しました。

以下サンプルプログラムです。
「mysqlSettings」の各値はダミーです。

var mysql = require('mysql');
var mysqlSettings = {
    host : 'localhost',
    user : 'user',
    password : 'password',
    database : 'MyNodeAppDb'
};
var conn = mysql.createConnection(mysqlSettings);
conn.Connect(); // ←ここで発生

発生したエラーは、以下に示すとおり、MySQLによる接続元クライアントの認証プロトコルのサポートエラーでした。

Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client

原因

接続先のMySQLがVersion 8であり、認証プロトコルのデフォルトがSHA256認証になっています。
MySQL Docs: 6.3.8.4 SHA-256 認証プラグイン

Node.jsのmysqlモジュールはネイティブパスワード認証が実装されており、認証プロトコルが異なるというのが原因です。
MySQL Docs: 6.3.8.1 ネイティブ認証プラグイン

対応方法

Node.jsの場合、対応方法が2つあります。

  • クライアント側でSHA256認証プラグインに対応したモジュールを使う
  • サーバー側で使用する認証プラグインをネイティブパスワード認証プラグインにする

クライアント側での対応としては、SHA256認証プラグインに対応したモジュールを利用するという方法を検討する必要があります。幸いにもNode.jsではSHA256認証に対応した「mysql2」というnpmパッケージがありますのでこれが利用できます。
SHA256認証に対応したプログラムが提供されていない言語の場合は自分で作るか、誰かが対応してくるまで待ちつつサーバー側の設定で対応する事を検討する必要があります。

サーバー側の設定変更については2つの方法があります。

  • mysqlサーバーの認証プロトコルのデフォルトを 「mysql_native_password」に変更する
  • データベースユーザー作成時に、認証プラグインとして「mysql_native_password」を指定する

もしクライアント側のモジュールを変更できない(例えばすでに本番で動いているプログラムで使っていて変更コストが発生してしまう等)場合は、以下のサイトにある情報をもとに、サーバーの設定を変えるか、接続ユーザー単位に使用する認証プロトコルを変えるようにすれば対応できると思います。

MySQLの認証プラグインとは

私の場合、わざわざ暗号強度の低い認証プロコトルに落とすのがイヤだったし、かつnpmパッケージとして提供されているmysql2で解決できる方法があったので、そちらを採用しました。

MySQLには様々なユーザー認証方式がプラグインとして提供されており、簡単に言うと各認証方式のことをAuthentication protocolと表現しているのだと理解しました。

以下、MySQLで対応している認証プラグインのサマリーです。

No.認証プラグイン名簡単な説明
1.ネイティブ認証
(旧)
パスワードハッシュを用いた認証
(MySQL 4.1以前、非推奨)
2.ネイティブ認証
(新)
パスワードハッシュを用いた認証
(MySQL 4.1以降)
3.SHA256認証ハッシュ方式として SHA256が適用された認証
(MySQL 5.6.6以降で利用可能)
4.PAM認証UnixパスワードやLDAPを使った認証
(商用ライセンスが必要)
5.Windows認証NTML等を用いた認証
(商用ライセンスが必要)
6.クリアテキスト認証クライアント側でハッシュ化したパスワードを送信する認証
(セキュアにするには通信経路にSSLを使用する等が必要)
7.ソケットピア認証Unixソケットファイルを用いた同一ホスト内プロセス間通信の認証

今回は、MySQLサーバーのバージョンとクライアントプログラムで、利用している認証プラグインが異なっていたことが原因です。
利用しているMySQLサーバーのバージョンが8なので、サーバー側のデフォルトはNo.3のSHA256認証となっていますが、クライアントのプログラムがNo.2ネイティブ認証だったため、異なるプロトコルでデータの受け渡しにより発生したエラーでした。

検証

実際にmysqlを利用した場合とmysql2を使用した場合でプログラムの内容、動作の違いを説明します。

対象テーブル

確認用に作成したテーブルは「MyNodeAppDb」というデータベースの「mydata」というテーブルです。

以下のようなデータを登録しました。

このデータをselectするプログラムを作成して検証します。

mysqlのインストール

npmでアプリケーションの初期化を行い、「mysql」パッケージをインストールします。

$> npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (mysqladdcli)
version: (1.0.0)
description:
entry point: (index.js) app.js
test command:
git repository:
keywords:
author: Masatoshuw
license: (ISC)
About to write to /Users/**/MySqlAddCLI/package.json:

{
  "name": "mysqladdcli",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Masatoshuw",
  "license": "ISC"
}


Is this OK? (yes) yes
$>
$> npm install --save mysql

added 11 packages, and audited 12 packages in 1s

found 0 vulnerabilities
$>

package.jsonはこんな感じになりました。

// package.json
{
  "name": "mysqladdcli",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Masatoshuw",
  "license": "ISC",
  "dependencies": {
    "mysql": "^2.18.1"
  }
}

mysqlを前提としたDBアクセスプログラムを作成

先程インストールしたmysqlを利用したDBアクセスプログラムを作成します。

const  mysql = require('mysql');

const  connSettings = {
  host :  'localhost',
  user:  'user',
  password:  'password',
  database:  'MyNodeAppDb'
};

console.log('Create mysql connection.');
const  conn = mysql.createConnection(connSettings);

console.log('Connect to mysql server.');
conn.connect();

console.log('Execute query : get records from mydata table without any criterias.');
conn.query('SELECT * FROM mydata', (error, results, fields) => {
  if (error != null) {
    console.log(error);
  }
  for (let  idx  in  results) {
    const record = results[idx];
    console.log(record.id + ': [' + record.name + '] [' + record.mail + '] [' + record.age + ']');
  }
});

console.log('Connection close.');
conn.end();

アクセスエラーを確認

作成したプログラムを実行すると今回確認できたエラーが発生します。

$> node app.js
Create mysql connection.
Connect to mysql server.
Execute query : get records from mydata table without any criterias.
Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
    at Handshake.Sequence._packetToError (/Users/**/MySqlAddCLI/node_modules/mysql/lib/protocol/sequences/Sequence.js:47:14)
    at Handshake.ErrorPacket (/Users/**/MySqlAddCLI/node_modules/mysql/lib/protocol/sequences/Handshake.js:123:18)
    at Protocol._parsePacket (/Users/**/MySqlAddCLI/node_modules/mysql/lib/protocol/Protocol.js:291:23)
    at Parser._parsePacket (/Users/**/MySqlAddCLI/node_modules/mysql/lib/protocol/Parser.js:433:10)
    at Parser.write (/Users/**/MySqlAddCLI/node_modules/mysql/lib/protocol/Parser.js:43:10)
    at Protocol.write (/Users/**/MySqlAddCLI/node_modules/mysql/lib/protocol/Protocol.js:38:16)
    at Socket.<anonymous> (/Users/**/MySqlAddCLI/node_modules/mysql/lib/Connection.js:88:28)
    at Socket.<anonymous> (/Users/**/MySqlAddCLI/node_modules/mysql/lib/Connection.js:526:10)
    at Socket.emit (events.js:315:20)
    at addChunk (_stream_readable.js:309:12)
    --------------------
    at Protocol._enqueue (/Users/**/MySqlAddCLI/node_modules/mysql/lib/protocol/Protocol.js:144:48)
    at Protocol.handshake (/Users/**/MySqlAddCLI/node_modules/mysql/lib/protocol/Protocol.js:51:23)
    at Connection.connect (/Users/**/MySqlAddCLI/node_modules/mysql/lib/Connection.js:116:18)
    at Object.<anonymous> (/Users/**/MySqlAddCLI/app.js:14:6)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'ER_NOT_SUPPORTED_AUTH_MODE',
  errno: 1251,
  sqlMessage: 'Client does not support authentication protocol requested by server; consider upgrading MySQL client',
  sqlState: '08004',
  fatal: true
}
$>

mysqlをアンインストールしてmysql2をインストール

「mysql」ではMySQL 8に接続出来ないので、まずは「mysql」をnpmでアンインストールし、SHA256認証に対応したnpmパッケージの「mysql2」をインストールします。

$> npm uninstall --save mysql

emoved 11 packages, and audited 1 package in 575ms
  
found **0** vulnerabilities
$>
$> npm install --save mysql2

added 15 packages, and audited 16 packages in 929ms

found **0** vulnerabilities
$>

mysql2用にプログラムを少し変える

mysql2用にプログラムを変更します。
変更箇所は以下の2点です。

  • requireでmysqlをmysql2に変更する
  • Queryを実行する関数をquery()からexecute()に変更する
const  mysql = require('mysql2'); // ←ここをmysql2に変更

const  connSettings = {
  host :  'localhost',
  user:  'user',
  password:  'password',
  database:  'MyNodeAppDb'
};

console.log('Create mysql connection.');
const  conn = mysql.createConnection(connSettings);

console.log('Connect to mysql server.');
conn.connect();

console.log('Execute query : get records from mydata table without any criterias.');
conn.execute('SELECT * FROM mydata', (error, results, fields) => { // ←ここをquery()からexecute()に変更
  if (error != null) {
    console.log(error);
  }
  for (let  idx  in  results) {
    const  record = results[idx];
    console.log(record.id + ': [' + record.name + '] [' + record.mail + '] [' + record.age + ']');
    }
  });

console.log('Connection close.');
conn.end();

アクセスできることを確認

実行すると、正常にMySQL 8サーバーに接続し、MyNodeAppDbデータベースのmydataテーブルからデータが取得できたことが確認できます。

$> node app.js
Create mysql connection.
Connect to mysql server.
Execute query : get records from mydata table without any criterias.
Connection close.
1: [Taro] [Taro@yamada] [35]
2: [hanako] [hanako@flower] [29]
3: [sachiko] [sachiko@happy] [17]
4: [ichiro] [ichiro@baseball] [45]
5: [jiro] [jiro@change] [58]
6: [mami] [mami@mumemo] [9]
$> 

まとめ

私が利用したMySQLサーバーのバージョン(今回は8)とnpmパッケージのプログラムの互換性の問題でした。
私が勉強に利用している書籍は3年前に発行されたモノで、情報としては少し古い状態になっていたため、このようなことが起こりました。

IT業界はとても進化のスピードが速いので、どの書籍でも同じようなことが起こりえます。
しかし、このようなことが起こっても自分で調査・確認し、対応できる力を身につけることができれば、古い情報をもとに最新情報にアクセスすることで、今まで無かった知識を習得することと同時に、本から得られる知識以上の「自分で調査してやってみて理解した」という素晴らしい経験を得ることが出来ます。

これで0.1%の戦闘力がアップしました!

コメント

タイトルとURLをコピーしました