メインコンテンツまでスキップ

HTTP リクエストヘッダで認証情報を送る方法

WebSocket インタフェースの認証情報(API キー)は、sコマンド内でauthorization={APIキー}のように設定する方法だけでなく、WebSocket 接続時の HTTP リクエストヘッダに記述することも可能です。ここでは、その方法を説明します。

方法1:Authorization ヘッダを使用する方法

WebSocket 接続開始時の HTTP リクエストヘッダに、以下のように認証用のカスタムヘッダを記述します。

Authorization: Bearer {APIキー}

カスタムヘッダの指定方法は、利用するクライアントライブラリや実行環境によって異なります。

たとえば、WebSocket インタフェースで紹介した Python のサンプルコードをこの形式に書き換える場合、以下のように header 引数を追加します。また、sコマンド内の認証情報に関する記述は削除します。

ws = websocket.WebSocketApp('wss://acp-api.amivoice.com/v1/',
header=["Authorization: Bearer {APIキー}"],
on_open=on_open,
on_message=on_message,
on_close=on_close)

方法2:Sec-WebSocket-Protocol ヘッダを使用する方法

方法1の Authorization ヘッダが利用できない場合でも、AmiVoice API では、サブプロトコル交渉ヘッダを認証情報の送信に利用することができます。

この場合、HTTP リクエストヘッダに以下のように記述します。

Sec-WebSocket-Protocol: wrp, {APIキー}

たとえば、WebSocket インタフェースで紹介した Python サンプルコードをこの形式に書き換える場合、以下のようにサブプロトコルを指定する引数を追加します。また、sコマンド内の認証情報に関する記述は削除します。

ws = websocket.WebSocketApp('wss://acp-api.amivoice.com/v1/',
subprotocols=["wrp", "{APIキー}"],
on_open=on_open,
on_message=on_message,
on_close=on_close)
警告

Sec-WebSocket-Protocol ヘッダを利用する方法は、ブラウザ JavaScript など、Authorization ヘッダを直接指定できない環境でも利用できます。ただし、この方法でブラウザから直接接続する場合、API キーはクライアント側のコードまたは実行環境から参照可能になってしまう点に注意してください。API キーをエンドユーザーに配布しないようにするには、プロキシサーバなどサーバ側で AmiVoice API に接続してください。

認証情報の優先順位

複数の方法で認証情報を記述した場合、実際の認証には、以下の優先順に利用されます。

  1. sコマンド内のauthorization={APIキー}
  2. Authorization: Bearer {APIキー} ヘッダ(方法1)
  3. Sec-WebSocket-Protocol: wrp, {APIキー}ヘッダ(方法2)

HTTP リクエストヘッダに記述した認証情報を利用したい場合は、sコマンド内に認証情報(authorization)を記述しないでください。

プロキシサーバで API キーを管理する

セキュリティのためにクライアント端末へ API キーを配布したくない場合、プロキシサーバなどのサーバ側コンポーネントで API キーを管理し、そのサーバ側コンポーネントから AmiVoice API に接続して、クライアント端末はサーバへと接続するようにする、という構成が考えられます。 しかし WebSocket インタフェースの場合、WebSocket セッション確立後にsコマンドで API キーを送信する方法だと、プロキシサーバが WebSocket を中継するだけの構成の場合に、API キーを途中で付与できず、クライアント端末に API キーを持たせることが必要になってしまいます。

このような場合に活用できるのが、ここで説明した認証情報の送信方法です。プロキシサーバ側で AmiVoice API への WebSocket 接続を確立する際、接続時の HTTP リクエストヘッダに API キーを設定することで、エンドユーザーのクライアント端末に API キーを配布しない構成を実現できます。

なお、アプリケーションがブラウザ Javascript などの Authorization ヘッダを直接指定できない環境の場合でも、プロキシサーバで AmiVoice API への WebSocket 接続を行う場合は、方法1が利用できます。

サンプルコード

WebSocket インタフェースのサンプルコードを、方法1を利用する形に書き換えたものを示します。書き換えた箇所はハイライトしています。

websocket-sample-2.py
import time
import websocket
import json
import threading
import logging


logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(threadName)s %(message)s")

server = 'wss://acp-api.amivoice.com/v1/'
header = ["Authorization: Bearer {APIキー}"]
filename = 'test.wav'
codec = "16K"
audio_block_size = 16000
grammar_file_names = "-a-general"
options = {
"profileId" : "",
"profileWords" : "",
"keepFillerToken": "",
"resultUpdatedInterval" : "1000",
}


def on_open(ws):
logger.info("open")
def start(*args):
command = "s {} {}".format(codec, grammar_file_names)
for k, v in options.items():
if v != "":
if k == 'profileWords':
v = '"' + v.replace('"', '""') + '"'
command += f" {k}={v}"
logger.info(f"send> {command}")
ws.send(command)
threading.Thread(target=start).start()


def on_message(ws, message):
event = message[0]
content = message[2:].rstrip()
logger.info(f"message: {event} {content}")

if event == 's':
if content == "can't connect to recognizer server":
logger.error(content)
return

def send_audio(*args):
with open(filename, mode='rb') as file:
buf = file.read(audio_block_size)
while buf:
logger.debug("send> p [..({} bytes)..]".format(len(buf)))
ws.send(b'p' + buf,
opcode=websocket.ABNF.OPCODE_BINARY)
buf = file.read(audio_block_size)
time.sleep(0.5)
logger.info("send> e")
ws.send('e')
threading.Thread(target=send_audio).start()

elif event == 'G':
pass
elif event == 'S':
starttime = int(content)
elif event == 'E':
endtime = int(content)
elif event == 'C':
pass
elif event == 'U':
raw = json.loads(content) if content else ''
elif event == 'A' or event == 'R':
raw = json.loads(content) if content else ''
elif event == 'e':
logger.info("close>")
ws.close()


def on_close(ws):
logger.info("close")


logger.info("open> {}".format(server))
ws = websocket.WebSocketApp(server,
header=header,
on_open=on_open,
on_message=on_message,
on_close=on_close)
ws.run_forever()