带有签名请求的Swift Alamofire文件上传:如何发送授权标头?

时间:2016-02-22 03:01:49

标签: ios swift httprequest alamofire multipartform-data

情景:

  • iPhone iOS 8+应用
  • 登录用户将上传个人资料照片

该应用已经使用Alamofire向后端API发出签名请求。非常简单:应用会为要签名的请求发送三个特定的HTTP标头(AuthorizationX-Api-Keytimestamp)。致电Alamofire.request即可轻松发送headers作为参数,以便它能够正常运作。

现在,用户需要能够上传他们的个人资料照片。由于用户已经登录到应用程序,后端API将通过它的已签名的请求知道哪个用户正在发送图片 - 这是棘手的部分I'在过去的几个小时里一直在努力。 Alamofire.upload接受来自.request的完全不同的参数,因此我无法弄清楚在上传文件时如何发送标头。

尝试旧的Alamofire.Manager.session.configuration.HTTPAdditionalHeaders,但它是no longer supported。找到文件上传的吨代码示例,没有人考虑发送自定义标头。

如何在使用Alamofire.upload方法时发送自定义标头?

typealias requestDataType = [String:AnyObject]
private func signRequest(data: requestDataType) -> [String:String] {
    var headers = [String:String]()

    var authString = ""
    var signatureHeaders = ""

    // Iterates over SORTED data dictionary to build headers
    for (k,v) in (data.sort{$0.0 < $1.0}) {
        if !authString.isEmpty {
            authString += "\n"
            signatureHeaders += " "
        }
        authString += "\(k): \(v)"
        signatureHeaders += "\(k)"
        headers[k] = "\(v)"
    }

    let userApiKey = _loggedInUser!["api_key"].string!
    let signature = authString.sha256(_loggedInUser!["api_secret"].string!)

    headers["X-Api-Key"] = userApiKey
    headers["Authorization"] = "Signature headers=\"\(signatureHeaders)\",keyId=\"\(userApiKey)\",algorithm=\"hmac-sha256\",signature=\"\(signature)\""

    return headers
}

func uploadProfilePicture(photo: UIImage, callback: apiCallback){
    guard let userId = _loggedInUser?["pk"].int else {
        callback(Response(success: false, responseMessage: "User not logged in"))
        return
    }

    let requestData: requestDataType = ["timestamp": "\(Int(NSDate().timeIntervalSince1970))"]

    let aManager = Manager.sharedInstance
    print(self.signRequest(requestData)) // Prints correct headers (Authorization, X-Api-Key, timestamp)
    aManager.session.configuration.HTTPAdditionalHeaders = self.signRequest(requestData)
    print(aManager.session.configuration.HTTPAdditionalHeaders) // Prints default headers, completely ignoring my custom headers

    aManager.upload(.POST, "\(_apiBaseUrl)profiles/\(userId)/photo/", multipartFormData: { multipartFormData in
        if let imageData = UIImageJPEGRepresentation(photo, 0.8) {
            multipartFormData.appendBodyPart(data: imageData, name: "upload", fileName: "userphoto.jpg", mimeType: "image/jpeg")
        }

        for (key, value) in requestData {
            multipartFormData.appendBodyPart(data: value.dataUsingEncoding(NSUTF8StringEncoding)!, name: key)
        }

        }, encodingCompletion: {
            encodingResult in

            debugPrint(encodingResult)
    })
}

请求通过。在后端日志中,我可以看到返回的请求HTTP 403 - 未经授权,因为无法签署请求。打印请求标头时,服务器未收到任何自定义身份验证标头。

2 个答案:

答案 0 :(得分:2)

在初始化之前,我希望在此类工作中共享一个非常有用的免费工具(Chrome应用):DHC Rest Client:使用此工具,您可以验证参数,标题和上传文件是否与该类型一起使用您要对服务器发出的请求。

所以,这项工作与 Swift 2.x Alamofire 3.x

首先准备标题:

compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.android.support:support-v4:23.0.1'
compile 'com.android.support:design:23.0.1'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.mo2o.third:valnif:1.0.0'
compile 'com.android.support:recyclerview-v7:23.0.+'
compile 'de.hdodenhof:circleimageview:2.0.0'
compile('com.afollestad.material-dialogs:core:0.8.0.1@aar') {
    transitive = true
}
compile 'com.android.support:percent:23.0.0'
compile 'se.emilsjolander:stickylistheaders:2.6.0'
compile 'me.grantland:autofittextview:0.2.+'
compile 'com.google.android.gms:play-services-auth:8.3.0'
compile "com.google.android.gms:play-services-gcm:8.3.0"
compile 'com.google.android.gms:play-services-plus:8.3.0'
compile 'com.squareup.picasso:picasso:2.5.2'

因此,假设您必须发送一个zip文件,并且响应将是TEXT / HTML响应类型(带有SUCCESS或ERROR的简单字符串):

let headers = [
                "Content-Type": "application/zip",
                "X-Api-Key": userApiKey,
                ...whatever you need on headers..
            ]

但是如果您想使用multipartformdata,您可以通过以下方式将标题传递到标题字典:

  

.upload(&lt;#T ## method:Method ## Method#&gt;,&lt; #T ## URLString:   URLStringConvertible ## URLStringConvertible#&gt;,headers:&lt; #T ## [String:   字符串]?#&gt;,multipartFormData:&lt; #T ## MultipartFormData - &gt;空隙#&GT;

答案 1 :(得分:0)

使用@alessandro-ornano的回答,我可以使用multipartFormData制作上传签名请求:

func uploadProfilePicture(photo: UIImage, callback: apiCallback){
    guard let userId = _loggedInUser?["pk"].int else {
        callback(Response(success: false, responseMessage: "User not logged in"))
        return
    }

    let requestData: requestDataType = ["timestamp": "\(Int(NSDate().timeIntervalSince1970))"]
    let headers = self.signRequest(requestData)

    _alamofireManager
        .upload(.POST, "\(_apiBaseUrl)profiles/\(userId)/photo/", headers: headers, multipartFormData: { formData in
            if let imageData = UIImageJPEGRepresentation(photo, 1){
                formData.appendBodyPart(data: imageData, name: "upload", fileName: "userphoto.jpg", mimeType: "image/jpg")
            }
            for (k, v) in requestData {
                formData.appendBodyPart(data: v.dataUsingEncoding(NSUTF8StringEncoding)!, name: k)
            }
        }, encodingCompletion: { encodingResult in
            switch encodingResult {
            case .Success(let upload, _, _):
                upload.responseJSON { response in
                    self.responseHandler(response, callback: callback) // Class' private method
                }
            case .Failure(let encodingError):
                print(encodingError)
                self.dispatch_callback(callback, response: Response(success: false, responseMessage: "Unable to encode files for upload")) // Class' private method
            }
        })
}