Swift通过Alamofire调用AWS加密API时签名及认证头生成问题
基于Alamofire访问AWS受保护API的签名实现方案
核心实现逻辑
AWS受保护的API默认使用SigV4签名算法校验请求合法性,你需要按照SigV4规则,结合你的access key、secret key、请求所属区域、服务名生成签名,再将签名相关信息附加到请求头中,流程和Android端AwsInterceptor逻辑一致。
你可以通过Alamofire提供的RequestInterceptor协议实现全局请求签名拦截,无需手动在每个请求中处理签名逻辑。
完整实现代码
1. 实现AWS SigV4签名拦截器
import Alamofire import CryptoKit // 系统内置加密库,无需额外引入 // 存储AWS签名所需配置的结构体 struct AWSConfig { let accessKey: String let secretKey: String let region: String let service: String // 临时凭证需要补充sessionToken字段,按需添加即可 } class AWSRequestInterceptor: RequestInterceptor { private let config: AWSConfig init(config: AWSConfig) { self.config = config } func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) { var signedRequest = urlRequest // 1. 补充请求必要的基础头 let dateFormatter = DateFormatter() dateFormatter.timeZone = TimeZone(identifier: "UTC") dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'" let amzDate = dateFormatter.string(from: Date()) signedRequest.setValue(amzDate, forHTTPHeaderField: "X-Amz-Date") dateFormatter.dateFormat = "yyyyMMdd" let dateStamp = dateFormatter.string(from: Date()) // 2. 构造待签名的内容,按SigV4规则拼接 guard let url = signedRequest.url, let host = url.host, let method = signedRequest.httpMethod else { completion(.success(urlRequest)) return } signedRequest.setValue(host, forHTTPHeaderField: "Host") let canonicalURI = url.path.isEmpty ? "/" : url.path let canonicalQuery = url.query ?? "" // 构造规范头 let canonicalHeaders = "host:\(host)\nx-amz-date:\(amzDate)\n" let signedHeaders = "host;x-amz-date" // 计算请求体哈希 let bodyHash = (signedRequest.httpBody ?? Data()).sha256HexString() // 拼接规范请求 let canonicalRequest = """ \(method) \(canonicalURI) \(canonicalQuery) \(canonicalHeaders) \(signedHeaders) \(bodyHash) """ // 3. 构造待签名字符串 let algorithm = "AWS4-HMAC-SHA256" let credentialScope = "\(dateStamp)/\(config.region)/\(config.service)/aws4_request" let stringToSign = """ \(algorithm) \(amzDate) \(credentialScope) \(canonicalRequest.sha256HexString()) """ // 4. 计算签名 guard let signature = calculateSignature( secretKey: config.secretKey, dateStamp: dateStamp, region: config.region, service: config.service, stringToSign: stringToSign ) else { completion(.success(urlRequest)) return } // 5. 构造授权头并附加到请求 let authorizationHeader = "\(algorithm) Credential=\(config.accessKey)/\(credentialScope), SignedHeaders=\(signedHeaders), Signature=\(signature)" signedRequest.setValue(authorizationHeader, forHTTPHeaderField: "Authorization") completion(.success(signedRequest)) } // SigV4签名计算逻辑 private func calculateSignature(secretKey: String, dateStamp: String, region: String, service: String, stringToSign: String) -> String? { let kSecret = "AWS4\(secretKey)".data(using: .utf8)! let kDate = HMAC<SHA256>.authenticationCode(for: dateStamp.data(using: .utf8)!, using: SymmetricKey(data: kSecret)) let kRegion = HMAC<SHA256>.authenticationCode(for: region.data(using: .utf8)!, using: SymmetricKey(data: Data(kDate))) let kService = HMAC<SHA256>.authenticationCode(for: service.data(using: .utf8)!, using: SymmetricKey(data: Data(kRegion))) let kSigning = HMAC<SHA256>.authenticationCode(for: "aws4_request".data(using: .utf8)!, using: SymmetricKey(data: Data(kService))) let signature = HMAC<SHA256>.authenticationCode(for: stringToSign.data(using: .utf8)!, using: SymmetricKey(data: Data(kSigning))) return Data(signature).map { String(format: "%02hhx", $0) }.joined() } } // 扩展Data加SHA256转十六进制方法 extension Data { func sha256HexString() -> String { let hash = SHA256.hash(data: self) return hash.map { String(format: "%02hhx", $0) }.joined() } } extension String { func sha256HexString() -> String { return self.data(using: .utf8)!.sha256HexString() } }
2. 修改原有请求代码
在发起请求时传入上面实现的拦截器即可:
// 先初始化AWS配置,替换为你的实际参数 let awsConfig = AWSConfig( accessKey: "你的AccessKey", secretKey: "你的SecretKey", region: "你的服务区域,比如us-east-1", service: "你的服务名,比如execute-api对应AWS API Gateway" ) let awsInterceptor = AWSRequestInterceptor(config: awsConfig) func secure(secureAPI: securedApi, completionHandler: @escaping (Bool, String) -> ()){ let headers: HTTPHeaders = [ .contentType("application/json") ] // 发起请求时传入interceptor参数 AF.request( secure as! URLConvertible, method: .post, parameters: secureAPI, encoder: JSONParameterEncoder.default, headers: headers, interceptor: awsInterceptor // 加入签名拦截器 ).responseJSON{ response in debugPrint(response) switch response.result{ case.success(let data): if response.response?.statusCode == 200{ print("Success") // 按需补充成功逻辑 }else{ completionHandler(false, "Please try again") } case.failure(let err): print(err.localizedDescription) completionHandler(false, "Please try again") } } }
注意事项
- 如果你的请求需要携带其他自定义头,需要将对应的头名加入到
signedHeaders列表中,同时按字母顺序拼接到canonicalHeaders里 - 若使用临时凭证,需要额外在请求头加入
X-Amz-Security-Token字段,同时将该头加入签名头列表 - 请确保你的设备时间和标准UTC时间误差不超过15分钟,否则会出现签名过期错误
内容的提问来源于stack exchange,提问作者P176061 Haider Tamsil
相关产品推荐
相关产品推荐

