任天堂APIのラッパーAPIをつくった

Javascript

NSO API

任天堂のiksm_session取得に関するAPIは当然ながらCORS制約がかかっており、別のドメインから取得することができない。

まあこうしておかないと悪意あるJavascriptをHTMLに埋め込んでおいて勝手にアクセスして情報を抜けてしまうので仕方ないのだろう。

が、この制約のせいでJavascriptから任天堂のAPIを叩くことができず、そのためJavascriptだけでiksm_sessionを取得することができないのである。

でもそれってちょっと不便じゃない?ってことでラッパーAPIを立てることにした。

NSO Wrapper API

要するにJavascriptからはAPIが叩けないのであって、どこかのサーバを経由してAPIを叩き、そのサーバがレスポンスをそのまま返してこればいいのである。

そしてこのWrapper APIがCORS制約をとっぱらってどこからでもアクセスできるようにすれば準備は万端である。

ただし、CORS制約がないということは本当にどこからでもAPIにアクセスされてしまうので実際に運用する上でこれは良くない。ただ単にデータを返すだけならいいが、ログインに関わるAPIにはCORS制約をつけるべきである。

また、任天堂が本来秘匿にしているAPIを利用しているわけなので(もちろんぼくはしていないが)やろうと思えば送られてくるデータを抜くこともできてしまう。

つまりこれはログインを試みたユーザ全員の情報を盗み見る事ができる、ということになる。当然、こんなものは公開できないし一般利用させるわけにはいかない。

NSOのWrapper APIを誰も立てていないのにはそういう理由があるのである。

が、技術としてWrapper APIは立てることができ、仕組みとしては至ってシンプルなので解説していこうと思う。

Apache

tkgstrator/SalmoniaAPI
Contribute to tkgstrator/SalmoniaAPI development by creating an account on GitHub.

本来であればREST APIはきちんとしてフレームワークで立てるべきなのだが、そんな大掛かりな内容でもないので適当にピュアPHPでREST APIっぽいものを立てた。

そのやり方に関しては以下の参考文献が非常にタメになるのでご一読あれ。

生のPHPでREST APIっぽいルーティングを作る - Qiita
生のPHPでREST APIっぽいルーティングを作る REST APIを作るときは、通常、Lumen: などのマイクロフレームワークや、 普通のフレームワークを使うのが便利で...

Rewrite engine

なお、Apache2でRewrite engineを有効化するためには手順が必要なのでそれも書いておく。

sudo a2enmod rewrite
sudo service apache2 restart

まずはmod_rewriteを有効化します。ただ、これだけだと動かないので追加で編集する必要があります。

cd /etc/apache2
sudoedit apache2.conf

apache.conf内にドキュメントルートの指定するところがあるのですが、AllowOverrideNoneになっているのをAllに修正します。これでRewrite engineが有効になっているときにちゃんと動作するようになります。

<Directory />
        Options FollowSymLinks
        AllowOverride None
        Require all denied
</Directory>

<Directory /usr/share>
        AllowOverride None
        Require all granted
</Directory>

<Directory /home/ubuntu/HP>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
</Directory>

.htaccess

参考文献と同じようにRewriteEngineの設定を書きます。

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

これにより、拡張子がなくてもアクセスできるようになります。

localhost:8080/login.phpってアクセスしなきゃいけなかったのがlocalhost:8080/loginでいけるようになるわけです。うむ、こっちのほうが確かに便利そうですね。

ただ、ディレクトリとファイルの違いがわかんなくなっちゃうので被らないようにだけは注意。

PHP

以下のような/api/index.phpを作成します

preg_match("|" . dirname($_SERVER["SCRIPT_NAME"]) . "/([\w%/]*)|", $_SERVER["REQUEST_URI"], $matches);
$paths = explode("/", $matches[1]);
$id = isset($paths[1]) ? htmlspecialchars($paths[1]) : null;
switch (strtolower($_SERVER["REQUEST_METHOD"]) . ":" . $paths[0]) {
}

こんな感じでプロトコルとアクセスされたパスで判定します。localhost:8080/api/がベースのURLになるわけです。

この状況で例えばlocalhost:8080/api/loginにアクセスすると/api/index.phploginというデータが送られるのです。

preg_match("|" . dirname($_SERVER["SCRIPT_NAME"]) . "/([\w%/]*)|", $_SERVER["REQUEST_URI"], $matches);
$paths = explode("/", $matches[1]);
$id = isset($paths[1]) ? htmlspecialchars($paths[1]) : null;
switch (strtolower($_SERVER["REQUEST_METHOD"]) . ":" . $paths[0]) {
    case "get:login":
        // localhost:8080/api/login
        break;
}

なのでこんな感じでうまい感じに条件分岐していきます。

ただ、GETではパラメータがURLでバレバレになるのでセッション関連の通信はPOSTで行うべきです。こちらであればSSLで暗号化できるので。

実際、Wrapper APIは全てPOSTで通信を行っています。

API

ニンテンドースイッチオンラインにログインするためには以下のものを取得しなければいけません。

session_token_code

session_tokenを取得するためのランダム文字列。

再利用可なので一度適当に値を求めたらそれをずっと使い続けることができます。

session_token_code_verifier

session_token_codeの有効性をチェックするためのランダム文字列。

再利用可なので一度適当に値を求めたらそれをずっと使い続けることができます。

これらの二つについてはここでいくらでも生成しているので好きな値を勝手にアプリに使ってもらって構いません。

session_token

session_token_codeと一緒にsession_token_code_verifierから生成される文字列。

再利用可なので一度求めたらずっと使い続けられます。

これ以後のtokenは全て再利用できないので、新しくiksm_sessionを生成する場合にはsession_tokenから再生成する必要があります。

逆に言えばこの値さえ保存しておけばURLコピペなどのめんどくさいログインの仕組みは不要になります。

access_token

アカウント情報にアクセスするためのランダム文字列。

これがあれば自分のNSOアカウントのデータにアクセスできます。

splatoon_token

あえて言うならsplatoon_session_tokenというべきなのだろうか。

splatoon_access_tokenを取得するために必要なランダム文字列。

splatoon_access_token

イカリング2にアクセスするために必要なランダム文字列。

これがあるだけでも一応アクセスはできるのだが、クソ長い上に有効期限が短いのでこれだけでは使い物にならない。

iksm_session

splatoon_access_tokenを使ってイカリング2にアクセスしたときに発行されるランダム文字列の認証情報。

splatoon_access_tokenと紐ついているのでこちらさえ保存しておけばイカリング2にアクセスできる。splatoon_access_tokenと違って有効期限が24時間あるのも強み。

あとはこれらを取得するためのAPIを構築すればいいわけですね。

Wrapper API

GitHubのコードで言えばsplatnet2.phpがAPIの中身になります。

以下のコードをPHPに書くことで返り値がJSONであることを明示し、CORS制約をかけることができる。このままだと本当にどこからでもアクセスできるので、認証の仕組みをつけるか、しっかりとCORS制限はかけておこう。

header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *"); 

先程のPHPで実装するAPIモドキのファイルindex.phpに追記しておけばオッケー。

preg_match("|" . dirname($_SERVER["SCRIPT_NAME"]) . "/([\w%/]*)|", $_SERVER["REQUEST_URI"], $matches);
$paths = explode("/", $matches[1]);
$id = isset($paths[1]) ? htmlspecialchars($paths[1]) : null;

header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *"); 

switch (strtolower($_SERVER["REQUEST_METHOD"]) . ":" . $paths[0]) {
}

ここでは軽くエンドポイントについて説明。これ以外にもあるけど、まあそれは秘密ということで。

/api/session_token

session_token_codeとsession_token_code_verifierをJSONで与えるとsession_tokenを返してくれます。

12
Requestsession_token_codesession_token_code_verifier
Responsesession_token

/api/access_token

session_tokenをJSONで与えるとaccess_tokenを返してくれます。

1
Requestsession_token
Responseaccess_token

/api/login

@nexusmine氏のflapg APIのWrapper APIです。外部APIですので利用にあたっては事前に許可をとっておきましょう。

flapg APIはSalmoniaで使うために利用申請はしているのですが、単にiksm_sessionを取得するためには申請していません。また、そのような目的のためにAPIを消費するべきでもありません。

iksm_sessionが欲しければfiddlerやmitmproxyを使うべきです。

flapg APIはaccess_tokenとtypeをJSON形式で受け取り、f, p1, p2, p3の三つのデータを返します。

まあここはドキュメントを読んでほしいのだが、typeはnsoとappのどちらかを入力します。

1234
Requestaccess_tokentype
Responsefp1p2p3

またflapg APIを利用するために@frozenpandaman氏のs2s APIも使っています。s2s APIは同一IPからの一分間に二回(四回?)のアクセス制限があります。

/api/splatoon_token

flapg APIを叩いて取得したfなどの四つの値からsplatoon_tokenを取得するAPIです。

1234
Requestfp1p2p3
Responsesplatoon_token

/api/splatoon_access_token

flapg APIを叩いて取得したfなどの四つの値とsplatoon_tokenからsplatoon_access_tokenを取得するAPIです。

12
Requestparametersplatoon_token
Responsesplatoon_access_token

parameterにはflapg APIのWrapper APIが返す値をそのまま突っ込めば大丈夫です。つまり、Requestのbodyは以下のような感じになります。

{
    "parameter": {
        "f": "75c3af4aa75119c97744da6de5fbd1fb9dd64f86c935f09dbc386134d3231123450a8d775f19dc28e395b4",
        "p1": "15d51615-0ddd-4172-81e4-8e285a820f6e",
        "p2": "1595060355",
        "p3": "037239ef-1914-43dc-815d-178aae7d8934"
    },
    "splatoon_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTUwNjc1NTUsImlhdCI6MTU5NTA2MDM1NSwic3ViIjo2NDQ1NDU3MTY5OTczMjQ4LCJpc3MiOiJhcGktbHAxLnpuYy5zcnYubmludGVuZG8ubmV0IiwiYXVkIjoiZjQxN2UxdGlianFkOTFjaDk5dTQ5aXd6NXNuOWNoeTMiLCJ0eXAiOiJpZF90b2tlbiIsIm1lbWJlcnNoaXAiOnsiYWN0aXZlIjp0cnVlfX0.bT--OdrVv8sQbwtd9M9CIVrsMfDwQCsx3_xxzLMKxoc"
}

/api/iksm_session

splatoon_access_tokenからiksm_sessionを返すAPIです。

1
Requestsplatoon_access_token
Responseiksm_session

iksm_sessionが返ってきます、やったねたえちゃん!!

実際に使ってみた

エンジニア向けに様々なパラメータが表示されるようになっていますが、もうちょっといい感じにしてもいいかもしれませんね。

5秒ほどでiksm_sessionが取得できるので多分それなりに速いです。というか、Swiftが遅すぎただけなんですけど…

Swiftでiksm_sessionを取得するコード - Qiita
iksm_sessionとは SplatNet2からリザルトを取得するために必要なセッションキーのこと。 splatnet2statinkなどがiksm_sessionの自動生成に対応しているが、それをSwift向けに移植した...

Alamofireがあればsemaphoreなんて使わなくてももっと上手にコーディングできそうな気がするんですが、このあたりはJSの方が洗練されている感じがしますね。

結局何がしたいのか

苦労してAPIは立てたがセキュリティやコンプライアンスの問題からとても一般には公開できないシロモノが完成しました。

こんなものつくって何がしたいんだっておもうかもしれませんが、これがあればなんといってもデバッグがしやすいです。特にs2s APIとかはプログラム書くたびに「どうやってコード書くんだっけ?」っていう仕様をしているのですが、Wrapper APIはs2s APIをflapg APIに組み込んでいるのでそこのところを全く考えなくて良いです。

ヘッダー部分も全てWrapper APIがやってくれるので、エンジニアに必要なのは正しいパラメータをJSONで送るだけなのです。

まあリリース時にはちゃんとAPIを叩かなければいけないのですが、いざ何かを開発しよう!となったときにAPI叩くところでコケてたらやる気なくしちゃうよねっていうことです。

なのでエンジニアの方は(flapg APIとs2s API以外は)是非とも利用してみてください。

コメント

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