Swift 带有签名请求的 Alamofire 文件上传:如何发送授权 headers?
Swift Alamofire file upload with signed request: how to send authorization headers?
场景:
- iPhone iOS 8+ 应用程序
- 登录用户将上传头像
该应用已使用 Alamofire 向后端发出签名请求 API。非常简单:应用程序发送三个特定的 HTTP headers(Authorization
、X-Api-Key
和 timestamp
)用于请求签名。调用 Alamofire.request
很容易将 headers
作为参数发送,因此它工作得很好。
现在用户需要能够上传他们的个人资料照片。由于用户已经登录到应用程序,后端 API 将通过 签名请求 知道哪个用户正在发送图片 - 这是我一直在努力的棘手部分在过去的几个小时里。 Alamofire.upload
接受与.request
完全不同的参数,所以我不知道上传文件时如何发送headers。
尝试了旧的 Alamofire.Manager.session.configuration.HTTPAdditionalHeaders
,但它是 no longer supported。找到大量文件上传代码示例,none 考虑发送自定义 headers.
如何在使用 Alamofire.upload
方法时发送自定义 headers?
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{[=12=].0 < .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
- 未授权,因为无法签署请求。打印请求 headers,服务器未收到自定义身份验证 headers。
在初始化之前,我想分享一个免费工具(chrome app)在这类工作中非常有用:DHC Rest Client:使用这个工具你可以验证你的参数,headers 并且您的上传文件适用于您要向服务器发出的请求类型。
所以,这与 Swift 2.x 和 Alamofire 3.x 一起工作:
首先准备好你的 headers:
let headers = [
"Content-Type": "application/zip",
"X-Api-Key": userApiKey,
...whatever you need on headers..
]
因此,假设您必须发送一个 zip 文件并且响应将是 TEXT/HTML 响应类型(带有 SUCCESS 或 ERROR 的简单字符串):
let filePath: String! = "/Users/admin.../Documents/myZipFile.zip"
var zipData: NSData! = NSData()
do {
zipData = try NSData(contentsOfFile: filePath, options: NSDataReadingOptions.DataReadingMappedIfSafe)
} catch {
print("- error during get nsdata from zip file\(error)")
}
let url :String! = String(format:"...myUrl?key1=%@&key2=%@",value1,value2)
Alamofire.upload(.POST, url, headers: headers, data: zipData)
.responseString { response in
if response.result.isSuccess {
let responseValue = response.result.value
print("Response value is: \(responseValue)")
} else {
var statusCode = 0
if (response.response != nil) {
statusCode = (response.response?.statusCode)!
}
print("Error: \(response.result.error!) with statusCode: \(statusCode)")
}
就是这些,但如果您想使用 multipartformdata,您可以通过 headers 字典传递您的 headers 并使用:
.upload(<#T##method: Method##Method#>, <#T##URLString:
URLStringConvertible##URLStringConvertible#>, headers: <#T##[String :
String]?#>, multipartFormData: <#T##MultipartFormData -> Void#>
使用 的回答,我能够使用 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
}
})
}
场景:
- iPhone iOS 8+ 应用程序
- 登录用户将上传头像
该应用已使用 Alamofire 向后端发出签名请求 API。非常简单:应用程序发送三个特定的 HTTP headers(Authorization
、X-Api-Key
和 timestamp
)用于请求签名。调用 Alamofire.request
很容易将 headers
作为参数发送,因此它工作得很好。
现在用户需要能够上传他们的个人资料照片。由于用户已经登录到应用程序,后端 API 将通过 签名请求 知道哪个用户正在发送图片 - 这是我一直在努力的棘手部分在过去的几个小时里。 Alamofire.upload
接受与.request
完全不同的参数,所以我不知道上传文件时如何发送headers。
尝试了旧的 Alamofire.Manager.session.configuration.HTTPAdditionalHeaders
,但它是 no longer supported。找到大量文件上传代码示例,none 考虑发送自定义 headers.
如何在使用 Alamofire.upload
方法时发送自定义 headers?
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{[=12=].0 < .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
- 未授权,因为无法签署请求。打印请求 headers,服务器未收到自定义身份验证 headers。
在初始化之前,我想分享一个免费工具(chrome app)在这类工作中非常有用:DHC Rest Client:使用这个工具你可以验证你的参数,headers 并且您的上传文件适用于您要向服务器发出的请求类型。
所以,这与 Swift 2.x 和 Alamofire 3.x 一起工作:
首先准备好你的 headers:
let headers = [
"Content-Type": "application/zip",
"X-Api-Key": userApiKey,
...whatever you need on headers..
]
因此,假设您必须发送一个 zip 文件并且响应将是 TEXT/HTML 响应类型(带有 SUCCESS 或 ERROR 的简单字符串):
let filePath: String! = "/Users/admin.../Documents/myZipFile.zip"
var zipData: NSData! = NSData()
do {
zipData = try NSData(contentsOfFile: filePath, options: NSDataReadingOptions.DataReadingMappedIfSafe)
} catch {
print("- error during get nsdata from zip file\(error)")
}
let url :String! = String(format:"...myUrl?key1=%@&key2=%@",value1,value2)
Alamofire.upload(.POST, url, headers: headers, data: zipData)
.responseString { response in
if response.result.isSuccess {
let responseValue = response.result.value
print("Response value is: \(responseValue)")
} else {
var statusCode = 0
if (response.response != nil) {
statusCode = (response.response?.statusCode)!
}
print("Error: \(response.result.error!) with statusCode: \(statusCode)")
}
就是这些,但如果您想使用 multipartformdata,您可以通过 headers 字典传递您的 headers 并使用:
.upload(<#T##method: Method##Method#>, <#T##URLString: URLStringConvertible##URLStringConvertible#>, headers: <#T##[String : String]?#>, multipartFormData: <#T##MultipartFormData -> Void#>
使用 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
}
})
}