🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

Q&A

解決済

1回答

959閲覧

SWIFTでライブラリを使わないAPI認証でのAuthorizationエラー

masayoshi555

総合スコア9

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

0グッド

0クリップ

投稿2019/12/04 20:01

概要

SWIFTを使って、Alamofireなどのライブラリを使わないAPI通信を構築しています。

Swiftで始める仮想通貨システムトレード入門
こちらのサイトを参考にAPI全体の構成はBuildエラーが出ないところまで進めました。

そこで、以下のエラーが出て解決策が見出せずにいるので教えて頂きたく投稿しました。

###エラー内容
Build Succeeded->Runでsimulatorを立ち上げ、リクエストを送信するトリガーになる
ボタンを押すと、デバッグコンソールに以下のように出力されます。
通信先からのResponseをString表示しています。

Optional("{\"error\":{\"message\":\"Authorization Required\",\"name\":\"HTTPError\"}}")

認証が必要とのエラー内容だと認識したので、デバッグコンソールでブレイクポイント作りながら
リクエスト内容を見ると、プログラム上認証作成するフローはしっかり動いており、リクエストURLは
できているように見えます。

以下はブレイクポイントを作りデバッグコンソールにて確認したurlRequestの値です。
アクセス先 : https://testnet.bitmex.com/api/

Printing description of urlRequest: ▿ https:/(アクセス先).com/api/v1/order?orderType=Limit&symbol=XBTUSD&orderQty=1&price=590 ▿ url : Optional<URL> ▿ some : https:/(アクセス先).com/api/v1/order?orderType=Limit&symbol=XBTUSD&orderQty=1&price=590 - _url : https:/(アクセス先).com/api/v1/order?orderType=Limit&symbol=XBTUSD&orderQty=1&price=590 - cachePolicy : 0 - timeoutInterval : 60.0 - mainDocumentURL : nil - networkServiceType : __C.NSURLRequestNetworkServiceType - allowsCellularAccess : true ▿ httpMethod : Optional<String> - some : "POST" ▿ allHTTPHeaderFields : Optional<Dictionary<String, String>> ▿ some : 4 elements ▿ 0 : 2 elements - key : "Content-Type" - value : "application/json" ▿ 1 : 2 elements - key : "ACCESS-SIGN" - value : "b2dce06bcb67b6db3418b5018b45cf9f4442986ed05b76fc91bfb4303788332f" ▿ 2 : 2 elements - key : "ACCESS-TIMESTAMP" - value : "1575546" ▿ 3 : 2 elements - key : "ACCESS-KEY" - value : "アクセスキーが出力されています" - httpBody : nil - httpBodyStream : nil - httpShouldHandleCookies : true - httpShouldUsePipelining : false

###認証周りコード
プログラム実行の流れ
0. ユーザーアクション(ボタン押下)
0. Sessionクラス呼び出し
0. buildURLRequestメソッド(RequestAPI内のextension Requestable)実行
0. buildURLRequest内でisAuthorizedRequestがtrueなら認証ヘッダを作成開始(コンソールでtrueであることは確認済)
0. makeAccessSignWithにてHMAC SHA256認証のhashを作成
0. buildURLRequestに戻り、全部を詰め込んでurlRequestとして完成
0. Sessionクラスに戻り、URLSessionを使ってtask.resume()にて実行

RequestAPI

1enum Result<T, E: Error> { 2 case success(T) 3 case failure(E) 4} 5enum ResponseError: Error { 6 case unexpectedResponse 7} 8//プロトコル定義 9protocol Requestable { 10 (省略) 11} 12extension Requestable { 13 func buildURLRequest() -> URLRequest { 14 let url = Self.baseURL.appendingPathComponent(Self.path) 15 var urlRequest = URLRequest(url: url) 16 var header: [String: String] = Self.headerField 17 urlRequest.httpMethod = Self.httpMethod 18 if Self.isAuthorizedRequest { 19 header["ACCESS-KEY"] = "アクセスキーを入力してあります" 20 let now = (Date().timeIntervalSince1970 / 1000) + 60 21 let timeStamp = String(Int(now.rounded()) 22 header["ACCESS-TIMESTAMP"] = timeStamp 23 header["ACCESS-SIGN"] = makeAccessSignWith(accessKey: "アクセスキーを入力してあります", 24 timeStamp: timeStamp, 25 method: Self.httpMethod, 26 path: Self.path, 27 queryParams: Self.queryParameters, 28 body: Self.httpBody) 29 header["Content-Type"] = "application/json" 30 } 31 header.forEach { key, value in 32 urlRequest.addValue(value, forHTTPHeaderField: key) 33 } 34 if let body = Self.httpBody { 35 urlRequest.httpBody = body 36 } 37 guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) else { 38 return urlRequest 39 } 40 urlComponents.query = Self.queryParameters 41 .map { "($0.key)=($0.value)" } 42 .joined(separator: "&") 43 44 urlRequest.url = urlComponents.url 45 46 return urlRequest 47 } 48}

order

1final class Session { 2 static let shared = Session() 3 private init() {} 4 func send<T: Requestable>(_ request: T, closure: @escaping (Result<T.Response, ResponseError>) -> Void) { 5 let urlRequest = request.buildURLRequest() 6 let task = URLSession.shared.dataTask(with: urlRequest) { (data, rawResponse, error) in 7 // If an error is occurred. 8 if error != nil { 9 closure(.failure(.unexpectedResponse)) 10 return 11 } 12 // If the data is empty. 13 guard let data = data else { 14 closure(.failure(.unexpectedResponse)) 15 return 16 } 17 // Decode the value. 18 do { 19 let decoder = JSONDecoder() 20 let result = try decoder.decode(T.Response.self, from: data) 21 closure(.success(result)) 22 } catch { 23 let errorResult = String(data: data, encoding: .utf8) //デバッグ用String化して中身見る 24 print(errorResult as Any) 25 } 26 } 27 task.resume() 28 } 29}

makeAccessSign

1 2private func makeAccessSignWith(accessKey: String, timeStamp: String, method: String, path: String, queryParams: [String: Any], body: Data?) -> String? { 3 4 let key = "シークレットキーを入力しています" 5 var bytes: [UInt8] = [] 6 bytes += timeStamp.bytes 7 bytes += method.bytes 8 bytes += path.bytes 9 func makeSign (queryParams: [String: Any]) -> String { 10 let requestBody = "?" + queryParams 11 .map { "($0.key)=($0.value)" } 12 .joined(separator: "&") 13 return requestBody 14 } 15 16 if !queryParams.isEmpty { 17 let str = makeSign(queryParams: ViewController.PostNewOrderRequest.queryParameters) 18 let data: Data? = str.data(using: .utf8) 19 let hexStr:String = data!.map {String(format: "%.2hhx", $0)}.joined() 20 var bytes = [UInt8]() 21 for char in hexStr.utf8{ 22 bytes += [char] 23 } 24 } 25 26 if body?.isEmpty == false { 27 if let bodyParameter = body, 28 let bodyString = String(data: bodyParameter, encoding: .utf8) { 29 bytes += bodyString.utf8 30 } 31 } 32 let signedString = try! HMAC(key: key, variant: .sha256).authenticate(bytes) 33 return signedString.toHexString() 34}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

お疲れ様です。

このドキュメントを見ると[permanent API key]が必要のようです:Bitmex REST API

そして、Authenticating with an API Keyによると以下のヘッダーを送る必要があります:

api-expires api-key api-signature

masayoshi555さんが提供してくれたサンプルコードにはヘッダーはちょっと違う気がします。
ご確認お願いします。

投稿2019/12/05 03:39

編集2019/12/05 03:40
vanderlvov

総合スコア687

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

masayoshi555

2019/12/05 19:20

vanderlvovさん 回答ありがとうございます! "api-expires"などの名前も揃えないといけないと知らず。ご指摘頂き大変たすかりました。 以前javascriptでは通信成功しているので、まずはそちらと変数名など合わせるように改造してみます。 ちなみに、一旦、上記変更したところエラーはなくなり別のエラーに移行しました。 進められそうです。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問