如何在swift 3中授权访问AWS API网关?

How to access AWS API gateway by authorization in swift 3?

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

有什么方法可以访问 swift 中的 AWS API 网关 3. 如果可能,请您提供一些简单的 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)")
    }
}

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

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 { [=10=].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 {[=10=].localizedCompare() == 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) ?? ""
}