如何通过URL Scheme检测唤起本App的来源应用?跨证书场景可行吗?
Great question! Let's break this down clearly—iOS's security model does impose some hard limits here, but there are practical workarounds to get what you need.
First, the straight truth: You cannot directly retrieve the source app's Bundle Identifier via UIApplication.OpenURLOptionsKey.sourceApplication when the two apps use different signing certificates. iOS intentionally hides this value for apps outside your developer team to protect user privacy and security, which is exactly why your existing code only works for same-team apps.
But don’t worry—there are reliable ways to detect the source app cross-certificate. Here’s how to implement them:
1. Explicitly Pass a Source Identifier in URL Parameters
The simplest approach is to have the calling app include a unique identifier (like its Bundle ID) in the URL’s query parameters when opening your app.
Implementation Steps:
- Calling App Side: When building the URL to trigger your app, add a
sourceparameter with its own Bundle ID. For example:let url = URL(string: "yourappscheme://open?source=com.partner.theirsecondapp&card=user123")! UIApplication.shared.open(url) - Your App Side: Modify your
application(_:open:options:)method to parse this parameter instead of relying onsourceApplication:func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true), let queryItems = components.queryItems else { return false } // Extract and validate the source identifier if let source = queryItems.first(where: { $0.name == "source" })?.value { if source == "com.partner.theirsecondapp" { // Retrieve your target data (like the card value) if let card = queryItems.first(where: { $0.name == "card" })?.value { print("Received valid card data from known app: \(card)") // Handle the data as needed } return true } } return false }
Caveat:
This method trusts the calling app to report its identity honestly. Malicious apps could forge the source parameter, so if you need to verify authenticity, move to the next method.
2. Add a Secure Signature to Prevent Forgery
To ensure the source app is legitimate, implement a shared secret-based signature system with your trusted partners.
Implementation Steps:
- Agree on a shared secret key and signing algorithm (like HMAC-SHA256) with the known calling app developers.
- Calling App Side: Generate a signature using the shared key, combining the source ID, timestamp, and other parameters. Append it to the URL:
let source = "com.partner.theirsecondapp" let timestamp = String(Date().timeIntervalSince1970) let cardValue = "user123" let rawData = "\(source)\(timestamp)\(cardValue)".data(using: .utf8)! let signature = HMACSHA256(data: rawData, key: "your_shared_secret_key".data(using: .utf8)!) let urlString = "yourappscheme://open?source=\(source)×tamp=\(timestamp)&card=\(cardValue)&signature=\(signature)" guard let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: encodedUrlString) else { return } UIApplication.shared.open(url) - Your App Side: Parse the parameters, recompute the signature, and verify it matches the received value. Also check the timestamp to block replay attacks:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true), let queryItems = components.queryItems, let source = queryItems.first(where: { $0.name == "source" })?.value, let timestampString = queryItems.first(where: { $0.name == "timestamp" })?.value, let timestamp = TimeInterval(timestampString), let card = queryItems.first(where: { $0.name == "card" })?.value, let receivedSignature = queryItems.first(where: { $0.name == "signature" })?.value else { return false } // Reject requests older than 5 minutes to prevent replay attacks let currentTime = Date().timeIntervalSince1970 guard abs(currentTime - timestamp) < 300 else { return false } // Recompute the signature to verify let rawData = "\(source)\(timestamp)\(card)".data(using: .utf8)! let computedSignature = HMACSHA256(data: rawData, key: "your_shared_secret_key".data(using: .utf8)!) // Validate source and signature if source == "com.partner.theirsecondapp" && computedSignature == receivedSignature { print("Valid request from trusted app. Card data: \(card)") return true } return false
}
// Helper function for HMAC-SHA256
func HMACSHA256(data: Data, key: Data) -> String {
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), key.baseAddress, key.count, data.baseAddress, data.count, &digest)
return Data(digest).map { String(format: "%02hhx", $0) }.joined()
}
--- ## 3. Use Universal Links (Optional, More Secure Alternative) While Universal Links don’t expose the source app’s Bundle ID cross-certificate, they are more secure than URL Schemes because they’re tied to your domain. Combine them with the parameter/signature method above for added safety: - Configure Associated Domains for your app to restrict which links can open it. - In your app’s `continue(_:restorationHandler:)` method, process the incoming Universal Link and parse the source/signature parameters just like with URL Schemes. --- ### Summary You can’t rely on iOS’s built-in `sourceApplication` for cross-certificate source detection. Instead, use **explicit parameter passing** (with optional secure signing) to identify known calling apps. This is the only feasible way to achieve your goal within iOS’s security constraints. 内容的提问来源于stack exchange,提问作者Hamed

