如何生成正确的OAuth HMAC-SHA1签名?解决签名不匹配问题
Hey there, let's work through why your Tumblr OAuth signature is failing to match. I went through your code and spotted several key issues that are almost certainly causing the validation error. Let's break them down and fix them step by step:
Key Issues in Your Code
- Line breaks in Base64 signature: Tumblr expects a clean, line-break-free Base64 string, but your current code adds line wraps.
- Inaccurate data length calculation: Using
strlen()for HMAC data counts can lead to errors if your strings contain unexpected null characters. - Deprecated Swift syntax: Your nonce generation uses an outdated substring method that could cause warnings or inconsistencies.
- Minor URL inconsistency: Your initial
requestTokenUrllacks thehttps://prefix (even though you use the correct URL later, consistency avoids mistakes).
Corrected Code Components
1. Fix the HMAC Extension Method
Update the HMAC implementation to use reliable byte counts and line-break-free Base64 encoding:
extension String { func hmac(algorithm: HMACAlgorithm, key: String) -> String { guard let keyData = key.data(using: .utf8), let messageData = self.data(using: .utf8) else { return "" } var result = [CUnsignedChar](repeating: 0, count: algorithm.digestLength()) CCHmac(algorithm.toCCHmacAlgorithm(), keyData.bytes, keyData.count, messageData.bytes, messageData.count, &result) let hmacData = Data(bytes: result, count: algorithm.digestLength()) return hmacData.base64EncodedString(options: []) // No line breaks in output } // Replace deprecated substring method func substring(to offset: Int) -> String { return String(self.prefix(offset)) } // Keep your existing urlEncoded and other extensions var urlEncoded: String { let customAllowedSet = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~") return self.addingPercentEncoding(withAllowedCharacters: customAllowedSet)! } var urlQueryEncoded: String? { return self.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) } }
2. Update Nonce Generation
Use modern Swift syntax to generate a clean nonce:
public func generateNonce() -> String { let uuidString = UUID().uuidString return uuidString.prefix(8).lowercased() // Lowercase is optional but aligns with common OAuth practices }
3. Fix URL Consistency
Update your initial URL constant to match the secure format:
let requestTokenUrl = "https://www.tumblr.com/oauth/request_token"
4. Corrected Login Method
Here's the updated login() method with all fixes applied, plus improved safety checks:
@IBAction func login(_ sender: Any) { let oauth_timestamp = String(Int64(Date().timeIntervalSince1970)) let oauth_nonce = generateNonce() // Use stored constants instead of hardcoding keys to avoid typos let parameters = ["oauth_timestamp": oauth_timestamp, "oauth_callback": "oauth-swift://oauth-callback/tumblr", "oauth_signature_method": "HMAC-SHA1", "oauth_version": "1.0", "oauth_consumer_key": consumerKey, "oauth_nonce": oauth_nonce] guard let url = URL(string: requestTokenUrl) else { print("Invalid request token URL") return } let method = "POST" let encodedConsumerSecret = consumerSecret.urlEncoded let encodedTokenSecret = oauthTokenSecret.urlEncoded let signingKey = "\(encodedConsumerSecret)&\(encodedTokenSecret)" // Sort parameters correctly per OAuth standards var parameterComponents = parameters.urlEncodedQuery.components(separatedBy: "&") parameterComponents.sort { let p0 = $0.components(separatedBy: "=") let p1 = $1.components(separatedBy: "=") if let key0 = p0.first, let key1 = p1.first { if key0 == key1 { return p0.last ?? "" < p1.last ?? "" } return key0 < key1 } return false } let parameterString = parameterComponents.joined(separator: "&") let encodedParameterString = parameterString.urlEncoded guard let encodedURL = url.absoluteString.urlEncoded else { print("Failed to encode URL") return } let signatureBaseString = "\(method)&\(encodedURL)&\(encodedParameterString)" // Generate corrected signature let signature = signatureBaseString.hmac(algorithm: .SHA1, key: signingKey) print("signature", signature) // Build Authorization header with properly encoded values let encodedCallback = "oauth-swift://oauth-callback/tumblr".urlEncoded let authHeader = "OAuth oauth_callback=\"\(encodedCallback)\", oauth_consumer_key=\"\(consumerKey)\", oauth_nonce=\"\(oauth_nonce)\", oauth_signature=\"\(signature.urlEncoded)\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"\(oauth_timestamp)\", oauth_version=\"1.0\"" var request = URLRequest(url: url) request.httpMethod = method request.setValue(authHeader, forHTTPHeaderField: "Authorization") let task = URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, error == nil else { print("error=\(String(describing: error))") return } if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { print("statusCode should be 200, but is \(httpStatus.statusCode)") print("response = \(String(describing: response))") } let responseString = String(data: data, encoding: .utf8) print("responseString = \(String(describing: responseString))") } task.resume() }
Final Validation Checks
Before testing, confirm these details:
- Your
consumerKeyandconsumerSecretexactly match the values in your Tumblr app dashboard (no extra spaces or typos). - The
oauth_callbackvalue matches exactly what you registered in the Tumblr app settings (including the custom scheme). - The
oauth_token_secretremains empty for the request token step (this is correct, as you don't have a token yet).
These fixes ensure your signature is generated exactly to Tumblr's OAuth 1.0a specifications, which should resolve the "oauth_signature does not match expected value" error.
内容的提问来源于stack exchange,提问作者Zafar Ahmad

