【问题标题】:How to access AWS API gateway by authorization in swift 3?如何在 swift 3 中通过授权访问 AWS API 网关?
【发布时间】:2018-12-11 06:28:37
【问题描述】:

当我通过 Alamofire 计算 AWS 签名访问 AWS 网关时。 它响应错误消息“我们计算的请求签名与您提供的签名不匹配。请检查您的 AWS 秘密访问密钥和签名方法。有关详细信息,请参阅服务文档。

有什么方法可以在 swift 3 中访问 AWS API 网关。如果可能的话,请你给我一些简单的 swift 代码。提前致谢!

private var configuration:AWSServiceConfiguration?
private var awsCredential = AWSCredential()

private func apiGatewaySimple(){
    let date = URLRequestSigner().iso8601()
    let xAmzStamp = date.short
    guard let URL = URL(string: "xxxxx") else { return }

    var request = URLRequest(url: URL)
    let url = request.url
    let host = url?.host
    let credentialProvider = AWSCognitoCredentialsProvider(regionType: .APNortheast1, identityPoolId: Constants.IDENTITY_POOL_ID)
    let endpoint = AWSEndpoint.init(region: .APNortheast1, service: .APIGateway, url: url)
    self.configuration = AWSServiceConfiguration.init(region: .APNortheast1, endpoint: endpoint, credentialsProvider: credentialProvider)
    let signer : AWSSignatureV4Signer = AWSSignatureV4Signer(credentialsProvider: self.configuration?.credentialsProvider, endpoint: endpoint)

    self.configuration?.requestInterceptors = [AWSNetworkingRequestInterceptor(),signer]
    _ = self.configuration?.responseInterceptors
    _ = self.configuration?.endpoint
    request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")

    let requestDate = Date()
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
    _ = dateFormatter.string(from: requestDate)


    let params:[String :Any]=["xxx" : "xxx" as AnyObject,
                              "xxxx" : "xxx" as AnyObject ]

    let jsonData = try? JSONSerialization.data(withJSONObject: params, options: [])
    let json = jsonData

    request.httpMethod = HttpMethod.post.rawValue
    request.httpBody = jsonData as! Data
    request.setValue(date.full, forHTTPHeaderField: "X-Amz-Date")
    request.setValue(self.awsCredential.sessionKey, forHTTPHeaderField: "X-Amz-Security-Token")
    request.setValue(json?.count.description, forHTTPHeaderField: "Content-Length")
    request.setValue(host, forHTTPHeaderField: "Host")

    let contentLength = json?.count
    let cfpath = request.url
    let query = cfpath?.query
    let hash = AWSSignatureSignerUtility.hash(request.httpBody)
    let contentsha256 = AWSSignatureSignerUtility.hexEncode(NSString.init(data: hash!, encoding: String.Encoding.ascii.rawValue)! as String)
    let canonicalRequest = AWSSignatureV4Signer.getCanonicalizedRequest(request.httpMethod, path: "xxxxx", query: query, headers: request.allHTTPHeaderFields, contentSha256: contentsha256)
    let scope = String(format: "%@/%@/%@/%@", xAmzStamp, "ap-northeast-1","execute-api",AWSSignatureV4Terminator)
    let signingCredential = String(format: "%@/%@", self.awsCredential.accessKey!,scope)

    let awsSignatureSignerV4 = AWSSignatureV4Signer(credentialsProvider: configuration?.credentialsProvider,endpoint:endpoint)


    _ = awsSignatureSignerV4?.interceptRequest(request as! NSMutableURLRequest)


    let canonicalRequestHash = AWSSignatureSignerUtility.hashString(canonicalRequest)

    let stringToSign = String(format: "%@/%@/%@/%@",AWSSignatureV4Algorithm,request.value(forHTTPHeaderField: "X-Amz-Date")!,scope,AWSSignatureSignerUtility.hexEncode(canonicalRequestHash))

    let kSigning = AWSSignatureV4Signer.getV4DerivedKey(self.awsCredential.secretKey, date: xAmzStamp, region: "ap-northeast-1", service: "execute-api")
    let signature = AWSSignatureSignerUtility.sha256HMac(with: stringToSign.data(using: .utf8), withKey: kSigning)

    let credentialsAuthorizationHeader = String(format: "Credential=%@", signingCredential)
    let signedHeadersAuthorizationHeader = String(format: "SignedHeaders=%@", AWSSignatureV4Signer.getSignedHeadersString(request.allHTTPHeaderFields))
    let signatureAuthorizationHeader = String(format: "Signature=%@", AWSSignatureSignerUtility.hexEncode(NSString.init(data: signature!, encoding: String.Encoding.ascii.rawValue)! as String))
    let authorization = String(format: "%@ %@, %@, %@", AWSSignatureV4Algorithm,credentialsAuthorizationHeader,signedHeadersAuthorizationHeader,signatureAuthorizationHeader)
    let headers = [
        "Content-Type": "application/json" ,
        "x-amz-security-token" : self.awsCredential.sessionKey ?? "",
        "x-amz-date" : date.full,
        "Content-Length"  : contentLength?.description ?? "0",
        "Authorization" : authorization,
        "Host" : host ?? ""
    ]
    Alamofire.request(request).responseString{ (response: DataResponse<String>) in
        print("\(response.result.value)")
    }
}

【问题讨论】:

    标签: ios amazon-web-services swift3 aws-sdk aws-cognito


    【解决方案1】:

    下面的代码对我来说效果很好。

    func getUrlRequest(url:String,params:[String :Any]) -> URLRequest{
    
        var headers = [String :String]()
        var signedHeaders = [String:String]()
    
        var bodyDigest = sha256("")
    
        var urlForSigning = url
        if urlForSigning.last == "/" {
            urlForSigning = String(url.dropLast())
        }
    
        var request = URLRequest(url: URL(string: url)!)
        request.httpMethod = HttpMethod.post.rawValue
    
        let body = try? JSONSerialization.data(withJSONObject: params, options: [])
        let bodyString = NSString(data: body!, encoding: String.Encoding.utf8.rawValue)! as String
        if (bodyString.trim().count > 0){
            bodyDigest = sha256(bodyString)
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            request.httpBody = bodyString.data(using: String.Encoding.utf8)
        }
    
        signedHeaders = self.signedHeaders(url: URL(string: urlForSigning)!,
                                             bodyDigest: bodyDigest, httpMethod: HttpMethod.post.rawValue)
    
        headers["Authorization"] = signedHeaders["Authorization"]
        headers["x-amz-date"] = signedHeaders["x-amz-date"]
        headers["Host"] = signedHeaders["Host"]
        headers["expiration"] = signedHeaders["expiration"]
        headers["x-amz-security-token"] = signedHeaders["x-amz-security-token"]
        headers["Content-Type"] = signedHeaders["Content-Type"]
    
        for (k, v) in headers {
            request.setValue(v, forHTTPHeaderField: k)
        }
    
        return request
    }
    
    func signedHeaders(url: URL, bodyDigest: String,
                       httpMethod: String, date: Date = Date()) -> [String: String] {
        let datetime = timestamp(date)
        let expirationTime = timestamp(self.expiration)
        let port = ((url as NSURL).port != nil) ? ":" + String(describing: (url as NSURL).port!) : ""
        var headers = ["x-amz-date": datetime, "Host": url.host! + port,  "expiration": expirationTime, "x-amz-security-token" : self.sessionKey, "Content-Type": "application/json"]
        headers["Authorization"] = authorization(accessKey, secretKey: secretKey, url: url, headers: headers,
                                                 datetime: datetime, httpMethod: httpMethod, bodyDigest: bodyDigest)
    
        return headers
    }
    // MARK: Utilities
    
    fileprivate func pathForURL(_ url: URL) -> String {
        var path = url.path
        if path.isEmpty {
            path = "/"
        } else {
            // do this to preserve encoded path fragments, like those containing encoded '/' (%2F)
            // NSURL.path  decodes them and they are lost
            var encodedPartsArray = [String]()
            // get rid of 'http(s)://'
            let fullURL = String(url.absoluteString[url.absoluteString.index(url.absoluteString.startIndex, offsetBy: 8)...])
            var rawPath = String(fullURL[fullURL.range(of: "/")!.lowerBound...])
            if rawPath.contains("?") {
                rawPath = String(rawPath[..<rawPath.range(of: "?")!.lowerBound])
            }
            for part in rawPath.components(separatedBy: "/") {
                if !part.isEmpty {
                    encodedPartsArray.append(Signer.encodeURIComponent(part))
                }
            }
            path = "/" + encodedPartsArray.joined(separator: "/")
        }
        return path
    }
    
    func sha256(_ str: String) -> String {
        let data = str.data(using: String.Encoding.utf8)!
        return data.sha256().toHexString()
    }
    
    fileprivate func hmac(_ string: NSString, key: Data) -> Data {
        let msg = string.data(using: String.Encoding.utf8.rawValue)!.bytes
        let hmac:[UInt8] = try! HMAC(key: key.bytes, variant: .sha256).authenticate(msg)
        return Data(bytes: hmac)
    }
    
    fileprivate func timestamp(_ date: Date) -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
        formatter.timeZone = TimeZone(identifier: "UTC")
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter.string(from: date)
    }
    
    // MARK: Methods Ported from AWS SDK
    
    fileprivate func authorization(_ accessKey: String, secretKey: String, url: URL, headers: Dictionary<String, String>,
                                   datetime: String, httpMethod: String, bodyDigest: String) -> String {
        let cred = credential(accessKey, datetime: datetime)
        let shead = signedHeaders(headers)
        let sig = signature(secretKey, url: url, headers: headers, datetime: datetime,
                            httpMethod: httpMethod, bodyDigest: bodyDigest)
    
        return [
            "AWS4-HMAC-SHA256 Credential=\(cred)",
            "SignedHeaders=\(shead)",
            "Signature=\(sig)",
            ].joined(separator: ", ")
    }
    
    fileprivate func credential(_ accessKey: String, datetime: String) -> String {
        return "\(accessKey)/\(credentialScope(datetime))"
    }
    
    fileprivate func signedHeaders(_ headers: [String:String]) -> String {
        var list = Array(headers.keys).map { $0.lowercased() }.sorted()
        if let itemIndex = list.index(of: "authorization") {
            list.remove(at: itemIndex)
        }
        return list.joined(separator: ";")
    }
    
    fileprivate func canonicalHeaders(_ headers: [String: String]) -> String {
        var list = [String]()
        let keys = Array(headers.keys).sorted {$0.localizedCompare($1) == ComparisonResult.orderedAscending}
    
        for key in keys {
            if key.caseInsensitiveCompare("authorization") != ComparisonResult.orderedSame {
                // Note: This does not strip whitespace, but the spec says it should
                list.append("\(key.lowercased()):\(headers[key]!)")
            }
        }
        return list.joined(separator: "\n")
    }
    
    fileprivate func signature(_ secretKey: String, url: URL, headers: [String: String],
                               datetime: String, httpMethod: String, bodyDigest: String) -> String {
        let secret = NSString(format: "AWS4%@", secretKey).data(using: String.Encoding.utf8.rawValue)!
        let date = hmac(String(datetime[..<datetime.index(datetime.startIndex, offsetBy: 8)]) as NSString, key: secret)
        let region = hmac(regionName as NSString, key: date)
        let service = hmac(serviceName as NSString, key: region)
        let credentials = hmac("aws4_request", key: service)
        let string = stringToSign(datetime, url: url, headers: headers, httpMethod: httpMethod, bodyDigest: bodyDigest)
        return hmac(string as NSString, key: credentials).toHexString()
    }
    
    fileprivate func credentialScope(_ datetime: String) -> String {
        return [
            String(datetime[..<datetime.index(datetime.startIndex, offsetBy: 8)]),
            regionName,
            serviceName,
            "aws4_request"
            ].joined(separator: "/")
    }
    
    fileprivate func stringToSign(_ datetime: String, url: URL, headers: [String: String],
                                  httpMethod: String, bodyDigest: String) -> String {
        return [
            "AWS4-HMAC-SHA256",
            datetime,
            credentialScope(datetime),
            sha256(canonicalRequest(url, headers: headers, httpMethod: httpMethod, bodyDigest: bodyDigest)),
            ].joined(separator: "\n")
    }
    
    fileprivate func canonicalRequest(_ url: URL, headers: [String: String],
                                      httpMethod: String, bodyDigest: String) -> String {
        return [
            httpMethod,                       // HTTP Method
            pathForURL(url),                  // Resource Path
            url.query ?? "",                  // Canonicalized Query String
            "\(canonicalHeaders(headers))\n", // Canonicalized Header String (Plus a newline for some reason)
            signedHeaders(headers),           // Signed Headers String
            bodyDigest,                       // Sha265 of Body
            ].joined(separator: "\n")
    }
    
    open static func encodeURIComponent(_ s: String) -> String {
        let allowed = NSMutableCharacterSet.alphanumeric()
        allowed.addCharacters(in: "-_.~")
        //allowed.addCharactersInString("-_.!~*'()")
        return s.addingPercentEncoding(withAllowedCharacters: allowed as CharacterSet) ?? ""
    }
    

    【讨论】:

    • 嗨,禅!我正在努力在我的应用程序中使用它并使用 Switft 4.1。我设法更新了几乎所有的代码。您能否提供有关您用于 pathForURL 方法的 Signer 类的更多信息?
    • 嗨@AndreiMarincas 你能帮我解决这个问题吗,我也遇到了同样的问题
    • 我已经为 Swift5 更新了这个,当我向 Alamofire 发出请求时,我得到一个成功响应,但有一个错误但没有详细信息。有没有人让这个工作?我得到的响应:success({ Output = { "__type" = "com.amazon.coral.service#InternalFailure"; message = ""; }; Version = "1.0"; })
    猜你喜欢
    • 2019-03-20
    • 2018-05-20
    • 2017-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-13
    • 2012-11-19
    • 2018-01-28
    相关资源
    最近更新 更多