OpenAMでQRコード認証を実現できるか試してみる
2021-03-04 - 星野 康
1. はじめに
=== この記事は、以前Qiitaに挙げていたものの再掲です ===
新入社員のアカウントの初期パスワードは、どのように設定・入力してもらうと良いでしょうか。
初期パスワードを設定する際、誕生日にアルファベットを足したものや社員番号など、類推可能なものにするのは危険です。ランダムな文字列にする場合でも、短いものにしてしまうと、ブルートフォースアタックの脅威にさらされてしまうかもしれません。それでは長いランダム文字列にした場合は…、今度は利用開始の障壁になってしまいそうです。
解決案として、「長いランダム初期パスワードをQRコード化して送付し、初回ログイン時はQRコードをかざすことで認証してもらう。(その後、強制的にパスワードを変更してもらう)」、というのはどうでしょうか。
幸い近年、QRコードを扱うJavascriptライブラリを公開してくださってる、素晴らしい方々がいらっしゃいます。弊社も取り扱っております認証基盤OSS「OpenAM」では色々カスタマイズが可能なので、ライブラリを組み込んでQRコード認証の実現を試してみます。
(注: 本記事は、あくまで簡易的に試してみるというものです。実際にご利用される場合は、各自のご責任の下で実施くださいませ。)
2. 実施
2.1 OpenAM のインストールと初期設定
まず、OSSTech版のOpenAM 13をインストールして初期設定しておきます。ここは今回の本題ではないので、詳細は割愛します。 (OpenAM 13がお手元にない場合でも、読み取ったQRコードの格納先となるテキストフィールドなりをご用意いただければ、QRコード読み取りのテストは可能です。)
次にですが、スマホでもカメラを起動してQRコードを読めるようにするためには、httpsでのアクセスにする必要があるようです。このため、あらかじめhttps化の対応もしておきます。ここも、詳細は割愛します。
ここでログイン画面にアクセスしてみると、OpenAMでは初期状態でデータストア認証の画面が出てくるようになってます。普通にIDとパスワードを入れるというものでして、画面例は下図のようになります。 これから、このパスワードのフィールドへの入力を、QRコード読み込み対応にしてみます。
2.2 QRコード読み取りライブラリの取り込み
QRコード画像を読み取るjavascriptライブラリとして、今回は「jsqrcode」を取り込んでみます。 https://github.com/LazarSoft/jsqrcode (Apache License 2.0)
$ mkdir <OpenAM ウェブアプリのデプロイ先>/openam/XUI/jsqrcode
$ (作成したディレクトリ内に、jsqrcodeのjs群を格納する。)
2.3 認証画面の編集
認証画面を編集していきます。 データストア認証の認証画面(の一部)は以下の場所にありますので、viでオープンすることにします。
$ vi <OpenAM ウェブアプリのデプロイ先>/openam/XUI/templates/openam/authn/DataStore1.html
修正内容としては、まずHTMLの要素として以下のコードを追加します。
<div style="text-align: center;">
<video id="video_id" width="300" height="300" autoplay="1" playsinline></video>
<div style="display:none">
<canvas id="canvas_id"></canvas>
</div>
<div id="message_id"></div>
</div>
そして、以下のjavascriptコードを追加します。 大して特別なことはしていないので、詳細はコード内コメントやコードを直接ご参照ください。
<script type="text/javascript" src="jsqrcode/grid.js"></script>
(同様に、jsqrcodeのgitページに記載の通り、必要なjsを全部取り込む。)
<script>
// HTML要素群。この'idToken2'が、読み取った値の格納先
const const_resultArea = document.getElementById('idToken2');
const const_video = document.getElementById('video_id');
const const_canvas = document.getElementById('canvas_id');
const const_messageDiv = document.getElementById('message_id');
// 連続失敗カウンタ
let let_failureCounter = 0;
// カメラを見つけてストリームをセット
// (スマホで内向きカメラが選ばれないよう、外向きカメラをideal指定する)
if (!navigator.mediaDevices.getUserMedia) {
alert("This browser may not handle HTML5_UserMedia. Please try other browers.");
} else {
navigator.mediaDevices.getUserMedia(
{ video: { facingMode: { ideal: 'environment' } }, audio: false }
).then(
function(stream) { const_video.srcObject = stream; },
function() { alert("Error... Cannot get video stream..."); }
);
}
// 読み取り結果を処理。
// (失敗の判定が雑な感じだが、ひとまずお試しとしてこれで動かす。)
qrcode.callback = function(decodedInformation) {
if (!decodedInformation ||
decodedInformation === "error decoding QR Code" ||
decodedInformation === "Failed to load the image") {
let_failureCounter++;
const_messageDiv.textContent = "QR Read : Failed... " + let_failureCounter;
} else {
let_failureCounter = 0;
const_resultArea.value = decodedInformation;
const_messageDiv.textContent = "QR Read : Succeeded!!";
}
};
// 画像化&QRコード読み取りを1回実施
function decodeOneTime() {
const_canvas.setAttribute("width", const_video.videoWidth);
const_canvas.setAttribute("height", const_video.videoHeight);
const const_canvas_context = const_canvas.getContext('2d');
const_canvas_context.drawImage(const_video, 0, 0, const_video.videoWidth, const_video.videoHeight);
if (const_canvas_context) {
qrcode.decode(const_canvas.toDataURL('image/png'));
}
}
// 毎秒処理する
setInterval(decodeOneTime, 1000);
</script>
2.4 結果
たったこれだけで、QRコード認証の基本的なところはできてしまいました。
利用中の画面は下図のようになります。 QRコードをかざすと、長いパスワード文字列が読み取られ、パスワードフィールドに自動入力されます。
3. おわりに
きちんと検証する必要はありますが、QRコード認証を実現できそうな感触を得ることができました。 今回は初期ログインという目的を例示しましたが、他の目的で利用しても良いかもしれません。例えば、多要素認証における簡易的な2段階目認証として使う、とかどうでしょう。
以上です。