iOS中如何监听NSHTTPCookieStorage的Cookie设置并阻止其写入?
好问题!在iOS中处理NSHTTPCookieStorage的Cookie监听和拦截确实需要一些技巧,因为系统没有直接提供拦截的回调,不过我们有几种可靠的方案,下面一步步给你讲清楚:
一、监听Cookie的变更操作
NSHTTPCookieStorage本身并没有提供实时的变更回调,但我们可以借助系统的通知中心来监听Cookie列表的变化。当Cookie被添加、修改或删除时,系统会发送NSHTTPCookieManagerCookiesChangedNotification通知,我们只需要注册这个通知就能拿到变更后的Cookie状态。
举个Swift代码示例:
// 在ViewController的viewDidLoad或者App启动时注册通知 override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(onCookiesChanged(_:)), name: NSHTTPCookieManagerCookiesChangedNotification, object: NSHTTPCookieStorage.shared) } // 处理Cookie变更的回调方法 @objc private func onCookiesChanged(_ notification: Notification) { let currentCookies = NSHTTPCookieStorage.shared.cookies ?? [] print("Cookie列表已更新,当前共有 \(currentCookies.count) 个Cookie") // 如果你需要找出具体变更的Cookie,可以提前保存一份旧的Cookie列表,在这里对比 // 比如:let addedCookies = currentCookies.filter { !oldCookies.contains($0) } } // 记得在合适的地方移除观察者(比如deinit) deinit { NotificationCenter.default.removeObserver(self) }
⚠️ 注意:这个通知是事后触发的,也就是说Cookie已经被修改完成后才会收到通知,所以它只能用来监听变化,没法直接阻止Cookie被设置。
二、阻止Cookie被设置的两种方案
如果想要阻止特定Cookie被存储,我们有两种常用的实现方式,分别适用于不同场景:
方案1:通过URLProtocol拦截HTTP响应头
Cookie的设置通常来自HTTP响应中的Set-Cookie头,所以我们可以自定义URLProtocol来拦截所有HTTP请求,移除或修改响应头中的Set-Cookie字段,这样系统就不会把Cookie存储到NSHTTPCookieStorage中了。
步骤1:自定义URLProtocol子类
class CookieBlockerProtocol: URLProtocol { // 标记是否已经处理过这个请求,避免重复拦截 private static let handledKey = "CookieBlockerHandled" // 判断是否需要拦截当前请求(可以根据域名、Cookie名称等过滤) override class func canInit(with request: URLRequest) -> Bool { guard let url = request.url else { return false } // 示例:只拦截example.com域名下的请求 guard url.host?.contains("example.com") ?? false else { return false } // 避免重复处理 return URLProtocol.property(forKey: handledKey, in: request) == nil } override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request } override class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool { return super.requestIsCacheEquivalent(a, to: b) } override func startLoading() { var mutableRequest = request // 标记该请求已被处理 URLProtocol.setProperty(true, forKey: Self.handledKey, in: mutableRequest) // 发起实际请求 let task = URLSession.shared.dataTask(with: mutableRequest) { [weak self] data, response, error in guard let self = self else { return } // 处理响应,移除Set-Cookie头 if let httpResponse = response as? HTTPURLResponse { var headers = httpResponse.allHeaderFields as? [String: String] ?? [:] // 移除所有Set-Cookie相关头(兼容大小写) headers.removeValue(forKey: "Set-Cookie") headers.removeValue(forKey: "set-cookie") // 创建修改后的响应 let modifiedResponse = HTTPURLResponse( url: httpResponse.url!, statusCode: httpResponse.statusCode, httpVersion: "HTTP/1.1", headerFields: headers )! self.client?.urlProtocol(self, didReceive: modifiedResponse, cacheStoragePolicy: .notAllowed) } // 传递数据和错误 if let data = data { self.client?.urlProtocol(self, didLoad: data) } if let error = error { self.client?.urlProtocol(self, didFailWithError: error) } self.client?.urlProtocolDidFinishLoading(self) } task.resume() } override func stopLoading() { // 可选:取消任务,不过URLSessionTask会自动处理 } }
步骤2:注册URLProtocol
在App启动时注册这个自定义的Protocol,比如在AppDelegate的application(_:didFinishLaunchingWithOptions:)中:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { URLProtocol.registerClass(CookieBlockerProtocol.self) return true }
针对WKWebView的额外配置
如果你用的是WKWebView,需要额外配置让它使用我们的URLProtocol:
let configuration = WKWebViewConfiguration() // 注册自定义Protocol configuration.setURLProtocolClasses([CookieBlockerProtocol.self]) let webView = WKWebView(frame: view.bounds, configuration: configuration) view.addSubview(webView)
方案2:通过Method Swizzling拦截Cookie设置方法
如果我们想要更直接地拦截NSHTTPCookieStorage的setCookie:等方法,可以用Method Swizzling(方法交换)来替换系统的实现,在设置Cookie前加入自定义判断逻辑,决定是否允许存储。
⚠️ 注意:Method Swizzling是一把双刃剑,可能会和其他第三方库冲突,使用前请充分测试!
示例代码:
extension NSHTTPCookieStorage { static func setupCookieBlocker() { let originalSelector = #selector(setCookie(_:)) let swizzledSelector = #selector(swizzled_setCookie(_:)) guard let originalMethod = class_getInstanceMethod(self, originalSelector), let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else { return } // 交换方法实现 method_exchangeImplementations(originalMethod, swizzledMethod) } @objc private func swizzled_setCookie(_ cookie: NSHTTPCookie) { // 自定义拦截逻辑:比如阻止特定名称或域名的Cookie let shouldBlock = cookie.name == "unwanted_cookie" || cookie.domain == "example.com" if shouldBlock { print("已阻止Cookie:\(cookie.name)") return // 不调用原方法,Cookie就不会被存储 } // 如果允许存储,调用原方法(因为已经交换,这里调用swizzled_setCookie实际是原方法) self.swizzled_setCookie(cookie) } }
然后在App启动时调用:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { NSHTTPCookieStorage.setupCookieBlocker() return true }
总结
- 监听Cookie变更:使用
NSHTTPCookieManagerCookiesChangedNotification通知,适合事后监听状态变化。 - 阻止Cookie设置:优先推荐URLProtocol方案(更安全,无侵入性),如果需要更直接的拦截可以考虑Method Swizzling(需谨慎使用)。
内容的提问来源于stack exchange,提问作者Colin Whitmarsh

