FJCT_ニフクラ mobile backend(mBaaS)お役立ちブログ

スマホアプリ開発にニフクラ mobile backend(mBaaS)。アプリ開発に役立つ情報をおとどけ!

Xamarin/C#でmBaaSの署名処理を実装する

f:id:mbaasdevrel:20200710131602p:plain

XamarinはC#を使ったクロスプラットフォームなスマートフォンアプリ開発環境です。最近、コロナウイルス追跡アプリを作ったフレームワークとして注目されるようになりました。

NCMBではC#を使った開発環境としてUnityに対応していますが、あれはiOS/Android SDKをラッピングしているので、Xamarinでそのまま利用できません。C#のみで実装する必要があります。

NCMBを利用するコードを書く際に一番問題になるのは署名処理かと思います。今回はC#での署名処理実装について紹介します。

使い方

最初に結論から書いておきます。作成したクラスは次のように使えます。アプリケーションキー、クライアントキーは省略しています。この条件はREST API リファレンス : シグネチャの生成方法 | ニフクラ mobile backendを使っています。

var sig = new NCMBSignature("614...e56", "134...f75");
var queries = new JObject();
queries["where"] = new JObject();
queries["where"]["testKey"] = "testValue";
var time = DateTime.Parse("2013-12-01T17:44:35.452Z");
Console.WriteLine(sig.generate("GET", "TestClass", time, null, queries));

この結果として作成される署名はドキュメントと同じ AltGkQgXurEV7u0qMd+87ud7BKuueldoCjaMgVc9Bes= になります。

データの柔軟性

NCMBでは検索条件などが固定化されておらず、型を指定するC#とは相性がよくありません。そこで、JSON.NET(Newtonsoft.Json)のJObjectを使って、型にこだわらずにデータ追加できるようにしています。

var queries = new JObject();
queries["where"] = new JObject();
queries["where"]["testKey"] = "testValue";

クラスの全体像

クラスは次のようになっています。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json.Linq;

namespace ncmb_xamarin
{
    public class NCMBSignature
    {
        // プロパティ

        // コンストラクター
        public NCMBSignature(string app_key, string cli_key)
        {
        }

        // リクエストするパスを返す
        private string path(string class_name, string objectId = null, string definePath = null) {
        }

        // 署名生成処理
        public string generate(string method, string class_name, DateTime time, string objectId = null, JObject queries = null, string definePath = null)
        {
    }
}

プロパティについて

プロパティは署名生成処理に必要な変数をまとめています。これはコード中に決め打ぶ部分を避けるためだけです。

// プロパティ
private string _fqdn = "mbaas.api.nifcloud.com";
private string _signatureMethod = "HmacSHA256";
private string _signatureVersion = "2";
private string _version = "2013-09-01";
private string _application_key;
private string _client_key;
private Hashtable _base_info = new Hashtable()
{
    {"SignatureVersion", ""},
    {"SignatureMethod", ""},
    {"X-NCMB-Application-Key", ""},
    {"X-NCMB-Timestamp", ""}
};

コンストラクター

コンストラクターはアプリケーションキー、クライアントキーをクラスのプロパティに設定しています。

// コンストラクター
public NCMBSignature(string app_key, string cli_key)
{
    _application_key = app_key;
    _client_key = cli_key;
    _base_info["SignatureVersion"] = _signatureVersion;
    _base_info["SignatureMethod"] = _signatureMethod;
    _base_info["X-NCMB-Application-Key"] = app_key;
}

リクエストするパスを返す

NCMBでは会員認証やプッシュ通知など、規定のクラスへのアクセス場合と、データストアへのアクセスとでパスが異なります。その辺りを処理するのがこのメソッドです。

// リクエストするパスを返す
private string path(string class_name, string objectId = null, string definePath = null) {
    string path = $"/{_version}";
    if (definePath != null) {
        return $"{path}/{definePath}";
    }
    var defined = new List<string> { "users", "push", "role", "files", "installations" };

    if (defined.IndexOf(class_name) > -1) {
        path = $"{path}/{class_name}";
    } else {
        path = $"{path}/classes/{class_name}";
    }
    if (objectId != null)
        path = $"{path}/{objectId}";
    return path;
}

署名生成処理

一番大きいのがこの署名生成処理になります。JObjectで送られてくるクエリ文字列をURLエンコードしたり、データをソートして&で繋いだりします。最終的に作成した文字列をクライアントキーを使ってハッシュ化(SHA256)し、BASE64エンコードすれば署名文字列の完成です。

// 署名生成処理
public string generate(string method, string class_name, DateTime time, string objectId = null, JObject queries = null, string definePath = null)
{
    
    _base_info["X-NCMB-Timestamp"] = time.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
    
    var sigList = new List<string>();
    if (queries != null)
    {
        foreach (KeyValuePair <string, JToken> key in queries)
        {
            _base_info.Add(key.Key, Uri.EscapeDataString(key.Value.ToString(Newtonsoft.Json.Formatting.None)));
        }
        
    }
    var keys = new ArrayList(_base_info.Keys);
    keys.Sort(StringComparer.Ordinal);
    foreach (string key in keys)
    {
        sigList.Add($"{key}={_base_info[key]}");
    }
    var queryString = String.Join("&", sigList);
    var str = String.Join("\n", new[]{
        method,
        _fqdn,
        path(class_name, objectId, definePath),
        queryString
    });
    var hmacSha256 = new HMACSHA256(Encoding.Default.GetBytes(_client_key));
    var hash = hmacSha256.ComputeHash(Encoding.Default.GetBytes(str));
    return Convert.ToBase64String(hash);
}

全体のコード

念のため全体のコードを載せておきます。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json.Linq;

namespace ncmb_xamarin
{
    public class NCMBSignature
    {
        private string _fqdn = "mbaas.api.nifcloud.com";
        private string _signatureMethod = "HmacSHA256";
        private string _signatureVersion = "2";
        private string _version = "2013-09-01";
        private string _application_key;
        private string _client_key;

        private Hashtable _base_info = new Hashtable()
        {
            {"SignatureVersion", ""},
            {"SignatureMethod", ""},
            {"X-NCMB-Application-Key", ""},
            {"X-NCMB-Timestamp", ""}
        };

        public NCMBSignature(string app_key, string cli_key)
        {
            _application_key = app_key;
            _client_key = cli_key;
            _base_info["SignatureVersion"] = _signatureVersion;
            _base_info["SignatureMethod"] = _signatureMethod;
            _base_info["X-NCMB-Application-Key"] = app_key;
        }

        private string path(string class_name, string objectId = null, string definePath = null) {
            string path = $"/{_version}";
            if (definePath != null) {
                return $"{path}/{definePath}";
            }
            var defined = new List<string> { "users", "push", "role", "files", "installations" };

            if (defined.IndexOf(class_name) > -1) {
                path = $"{path}/{class_name}";
            } else {
                path = $"{path}/classes/{class_name}";
            }
            if (objectId != null)
                path = $"{path}/{objectId}";
            return path;
        }

        public string generate(string method, string class_name, DateTime time, string objectId = null, JObject queries = null, string definePath = null)
        {
            
            _base_info["X-NCMB-Timestamp"] = time.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
            
            var sigList = new List<string>();
            if (queries != null)
            {
                foreach (KeyValuePair <string, JToken> key in queries)
                {
                    _base_info.Add(key.Key, Uri.EscapeDataString(key.Value.ToString(Newtonsoft.Json.Formatting.None)));
                }
                
            }
            var keys = new ArrayList(_base_info.Keys);
            keys.Sort(StringComparer.Ordinal);
            foreach (string key in keys)
            {
                sigList.Add($"{key}={_base_info[key]}");
            }
            var queryString = String.Join("&", sigList);
            var str = String.Join("\n", new[]{
                method,
                _fqdn,
                path(class_name, objectId, definePath),
                queryString
            });
            var hmacSha256 = new HMACSHA256(Encoding.Default.GetBytes(_client_key));
            var hash = hmacSha256.ComputeHash(Encoding.Default.GetBytes(str));
            return Convert.ToBase64String(hash);
        }
    }
}

まとめ

XamarinはiOS/Android向けにコードを生成するため、Windowsやサーバサイドで使えるC#と同じ資産がすべて使える訳ではありません。JSONを扱うライブラリでも動かないものが多数ありました。JSON.NETはXamarinでも使えるので、NCMBで利用するのにもよさそうです。

署名処理ができれば、後はデータストアやファイルストアにアクセスするのもさほど難しくないと思われます。ぜひXamarinでもNCMBを使ってみてください!

中津川 篤司

中津川 篤司

NCMBエヴァンジェリスト。プログラマ、エンジニアとしていくつかの企業で働き、28歳のときに独立。 2004年、まだ情報が少なかったオープンソースソフトの技術ブログ「MOONGIFT」を開設し、毎日情報を発信している。2013年に法人化、ビジネスとエンジニアを結ぶDXエージェンシー「DevRel」活動をスタート。