Tôi có thể đặt cookie để WKWebView sử dụng không?


135

Tôi đang cố gắng chuyển một ứng dụng hiện có từ UIWebViewsang WKWebView. Ứng dụng hiện tại quản lý người dùng đăng nhập / phiên bên ngoài webviewvà đặt cookiesyêu cầu xác thực vào NSHTTPCookieStore. Thật không may, mới WKWebViewkhông sử dụng cookiestừ NSHTTPCookieStorage. Có cách nào khác để đạt được điều này?

Câu trả lời:


186

Chỉ chỉnh sửa cho iOS 11+

Sử dụng WKHTTPCookieStore :

let cookie = HTTPCookie(properties: [
    .domain: "example.com",
    .path: "/",
    .name: "MyCookieName",
    .value: "MyCookieValue",
    .secure: "TRUE",
    .expires: NSDate(timeIntervalSinceNow: 31556926)
])! 

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)

Vì bạn đang kéo chúng từ HTTPCookeStorage, bạn có thể làm điều này:

let cookies = HTTPCookieStorage.shared.cookies ?? []
for cookie in cookies {
    webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
}

Câu trả lời cũ cho iOS 10 trở xuống

Nếu bạn yêu cầu cookie của mình được đặt theo yêu cầu tải ban đầu, bạn có thể đặt chúng trên NSMutableURLRequest. Bởi vì cookie chỉ là một tiêu đề yêu cầu được định dạng đặc biệt, điều này có thể đạt được như vậy:

WKWebView * webView = /*set up your webView*/
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com/index.html"]];
[request addValue:@"TeskCookieKey1=TeskCookieValue1;TeskCookieKey2=TeskCookieValue2;" forHTTPHeaderField:@"Cookie"];
// use stringWithFormat: in the above line to inject your values programmatically
[webView loadRequest:request];

Nếu bạn yêu cầu các yêu cầu AJAX tiếp theo trên trang phải đặt cookie của họ, điều này có thể đạt được bằng cách sử dụng WKUserScript để đặt các giá trị theo chương trình thông qua javascript khi tài liệu bắt đầu như vậy:

WKUserContentController* userContentController = WKUserContentController.new;
WKUserScript * cookieScript = [[WKUserScript alloc] 
    initWithSource: @"document.cookie = 'TeskCookieKey1=TeskCookieValue1';document.cookie = 'TeskCookieKey2=TeskCookieValue2';"
    injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
// again, use stringWithFormat: in the above line to inject your values programmatically
[userContentController addUserScript:cookieScript];
WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
webViewConfig.userContentController = userContentController;
WKWebView * webView = [[WKWebView alloc] initWithFrame:CGRectMake(/*set your values*/) configuration:webViewConfig];

Kết hợp hai kỹ thuật này sẽ cung cấp cho bạn đủ công cụ để chuyển các giá trị cookie từ Vùng đất ứng dụng gốc sang Vùng đất xem Web. Bạn có thể tìm thêm thông tin về API javascript cookie trên trang của Mozilla nếu bạn yêu cầu một số cookie nâng cao hơn.

Vâng, thật tệ khi Apple không hỗ trợ nhiều tính năng của UIWebView . Không chắc họ có bao giờ hỗ trợ họ không, nhưng hy vọng họ sẽ sớm nhận được điều này. Hi vọng điêu nay co ich!


1
Đâu là nơi tốt nhất để tiêm cookie cho các yêu cầu tiếp theo? Ví dụ: Tải trang ban đầu được trình bày trong câu trả lời ở trên, nhưng nếu có các liên kết trên trang cũng dẫn đến cùng một tên miền và cũng cần các cookie tương tự được đưa vào yêu cầu thì sao? didStartProvisionalNavulation?
Mason G. Zhwiti 04/05/2015

1
xin lỗi nó không làm việc cho bạn Trong tâm trí của tôi, miễn là các tên miền giống nhau thì không có vấn đề gì. Bạn có thể kiểm tra lại mã mà liên kết đang trỏ đến cùng tên miền mà bạn đã tải yêu cầu không? Ngoài ra, cookie cũng có thể bị hạn chế theo một "đường dẫn" cụ thể. Có lẽ điều đó đang gây ra một số vấn đề?
mattr

11
Lưu ý rằng kỹ thuật javascript để đặt cookie sẽ không hoạt động đối với cookie "Chỉ HTTP".
Ahmed Nasser

1
Phương pháp trên hoạt động rất tốt ... nhưng tôi có thể thấy cookie được nhân đôi trong các cuộc gọi AJAX tiếp theo (Chỉ được nhân đôi một lần).
Durga Vundavalli

1
@ Axel92Dev một cách giải quyết sẽ là đảm bảo yêu cầu đầu tiên được thực hiện từ chế độ xem web của bạn đến máy chủ của bạn nhận được phản hồi rõ ràng cho webview đặt lại cookie với cờ HTTPOnly (nghĩa là: đặt lại cookie trong phản hồi). Bạn có thể tạo một API đặc biệt cho mục đích duy nhất đó khi khởi tạo webview, sau đó sử dụng webview bình thường khi thành công.
Ahmed Nasser

63

Sau khi chơi với câu trả lời này (rất hữu ích :) chúng tôi đã phải thực hiện một vài thay đổi:

  • Chúng tôi cần lượt xem web để xử lý nhiều tên miền mà không bị rò rỉ thông tin cookie riêng tư giữa các tên miền đó
  • Chúng tôi cần nó để tôn vinh cookie an toàn
  • Nếu máy chủ thay đổi giá trị cookie, chúng tôi muốn ứng dụng của chúng tôi biết về nó trong NSHTTPCookieStorage
  • Nếu máy chủ thay đổi giá trị cookie, chúng tôi không muốn tập lệnh của mình đặt lại giá trị ban đầu khi bạn theo liên kết / AJAX, v.v.

Vì vậy, chúng tôi đã sửa đổi mã của chúng tôi để được này;

Tạo một yêu cầu

NSMutableURLRequest *request = [originalRequest mutableCopy];
NSString *validDomain = request.URL.host;
const BOOL requestIsSecure = [request.URL.scheme isEqualToString:@"https"];

NSMutableArray *array = [NSMutableArray array];
for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
    // Don't even bother with values containing a `'`
    if ([cookie.name rangeOfString:@"'"].location != NSNotFound) {
        NSLog(@"Skipping %@ because it contains a '", cookie.properties);
        continue;
    }

    // Is the cookie for current domain?
    if (![cookie.domain hasSuffix:validDomain]) {
        NSLog(@"Skipping %@ (because not %@)", cookie.properties, validDomain);
        continue;
    }

    // Are we secure only?
    if (cookie.secure && !requestIsSecure) {
        NSLog(@"Skipping %@ (because %@ not secure)", cookie.properties, request.URL.absoluteString);
        continue;
    }

    NSString *value = [NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value];
    [array addObject:value];
}

NSString *header = [array componentsJoinedByString:@";"];
[request setValue:header forHTTPHeaderField:@"Cookie"];

// Now perform the request...

Điều này đảm bảo rằng yêu cầu đầu tiên có bộ cookie chính xác, không gửi bất kỳ cookie nào từ bộ nhớ chia sẻ dành cho các tên miền khác và không gửi bất kỳ cookie an toàn nào vào yêu cầu không an toàn.

Xử lý các yêu cầu khác

Chúng tôi cũng cần đảm bảo rằng các yêu cầu khác có cookie được đặt. Điều này được thực hiện bằng cách sử dụng tập lệnh chạy trên tải tài liệu để kiểm tra xem có bộ cookie nào không và nếu không, hãy đặt tập lệnh thành giá trị trong NSHTTPCookieStorage.

// Get the currently set cookie names in javascriptland
[script appendString:@"var cookieNames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } );\n"];

for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
    // Skip cookies that will break our script
    if ([cookie.value rangeOfString:@"'"].location != NSNotFound) {
        continue;
    }

    // Create a line that appends this cookie to the web view's document's cookies
    [script appendFormat:@"if (cookieNames.indexOf('%@') == -1) { document.cookie='%@'; };\n", cookie.name, cookie.wn_javascriptString];
}

WKUserContentController *userContentController = [[WKUserContentController alloc] init];
WKUserScript *cookieInScript = [[WKUserScript alloc] initWithSource:script
                                                      injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                   forMainFrameOnly:NO];
[userContentController addUserScript:cookieInScript];

...

// Create a config out of that userContentController and specify it when we create our web view.
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = userContentController;

self.webView = [[WKWebView alloc] initWithFrame:webView.bounds configuration:config];

Xử lý thay đổi cookie

Chúng tôi cũng cần phải đối phó với máy chủ thay đổi giá trị của cookie. Điều này có nghĩa là thêm một tập lệnh khác để gọi lại khỏi chế độ xem web mà chúng tôi đang tạo để cập nhật NSHTTPCookieStorage.

WKUserScript *cookieOutScript = [[WKUserScript alloc] initWithSource:@"window.webkit.messageHandlers.updateCookies.postMessage(document.cookie);"
                                                       injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                    forMainFrameOnly:NO];
[userContentController addUserScript:cookieOutScript];

[userContentController addScriptMessageHandler:webView
                                          name:@"updateCookies"];

và thực hiện phương pháp ủy nhiệm để cập nhật bất kỳ cookie nào đã thay đổi, đảm bảo rằng chúng tôi chỉ cập nhật cookie từ tên miền hiện tại!

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSArray<NSString *> *cookies = [message.body componentsSeparatedByString:@"; "];
    for (NSString *cookie in cookies) {
        // Get this cookie's name and value
        NSArray<NSString *> *comps = [cookie componentsSeparatedByString:@"="];
        if (comps.count < 2) {
            continue;
        }

        // Get the cookie in shared storage with that name
        NSHTTPCookie *localCookie = nil;
        for (NSHTTPCookie *c in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:self.wk_webView.URL]) {
            if ([c.name isEqualToString:comps[0]]) {
                localCookie = c;
                break;
            }
        }

        // If there is a cookie with a stale value, update it now.
        if (localCookie) {
            NSMutableDictionary *props = [localCookie.properties mutableCopy];
            props[NSHTTPCookieValue] = comps[1];
            NSHTTPCookie *updatedCookie = [NSHTTPCookie cookieWithProperties:props];
            [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:updatedCookie];
        }
    }
}

Điều này dường như khắc phục sự cố cookie của chúng tôi mà không phải xử lý mỗi nơi chúng tôi sử dụng WKWebView khác nhau. Bây giờ chúng tôi chỉ có thể sử dụng mã này như một người trợ giúp để tạo các lượt xem web của mình và nó cập nhật một cách minh bạch NSHTTPCookieStoragecho chúng tôi.


EDIT: Hóa ra tôi đã sử dụng một danh mục riêng tư trên NSHTTPCookie - đây là mã:

- (NSString *)wn_javascriptString {
    NSString *string = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@",
                        self.name,
                        self.value,
                        self.domain,
                        self.path ?: @"/"];

    if (self.secure) {
        string = [string stringByAppendingString:@";secure=true"];
    }

    return string;
}

6
Tôi đã gói mã của bạn vào một lớp con của WKWebView. Vui lòng kiểm tra xem github.com/haifengkao/YWebView
Hai Feng Kao

Điều gì nếu cookie của bạn chứa = dấu trong giá trị? Điều này sẽ làm việc?
iOSĐược ký

@iOSAddicted Tôi nghĩ vậy. Nếu giá trị của bạn là a=bbạn sẽ kết thúc bằng chuỗi cookie name=a=b;domain=.example.com;path=/- Tôi tin rằng tiêu chuẩn sẽ tách ra ;và sau đó phân tách trên đầu tiên = trong cặp key = value. Tôi sẽ kiểm tra điều này mặc dù :)
deanWombourne

Phản hồi của bạn đã giúp tôi rất nhiều, tuy nhiên tôi muốn thêm một cái gì đó vào bài đăng của bạn, có một số rủi ro khi sử dụng phương pháp cập nhật của bạn, một số khung công tác JS có thể tạo cookie có cùng tên nhưng tên miền khác và nếu bạn cố cập nhật sử dụng các phương pháp js bạn có nguy cơ cao cập nhật cookie với giá trị sai. Ngoài ra, đối với chúng tôi, chuỗi cookie js, đã bị tước cờ bảo mật, vì máy chủ của chúng tôi tạo ra các chuyển hướng khó chịu giữa http và https, khiến cookie an toàn không xuất hiện trong một số trang trong một số trường hợp cạnh khó chịu.
RicardoDuarte

Tôi thực sự nghĩ rằng công ty tôi đã làm việc khi tôi viết bài này đã phải thêm một số bảo vệ tên miền cho nó sau khi nó đi vào hoạt động. Chúng tôi không bao giờ (afaik) gặp phải vấn đề an toàn / không an toàn - nghe có vẻ như một cơn ác mộng!
deanWombourne

42

Các cookie phải được đặt trên cấu hình trước khi WKWebViewđược tạo. Nếu không, ngay cả với WKHTTPCookieStore's setCookiehandler hoàn thành, cookie sẽ không đáng tin cậy được đồng bộ hóa với xem web. Điều này quay trở lại dòng này từ các tài liệu trênWKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

Đó @NSCopyinglà một phần của một bản sao sâu sắc. Việc triển khai nằm ngoài tôi, nhưng kết quả cuối cùng là trừ khi bạn đặt cookie trước khi khởi tạo webview, bạn không thể tin tưởng vào các cookie ở đó. Điều này có thể làm phức tạp kiến ​​trúc ứng dụng vì khởi tạo chế độ xem trở thành một quy trình không đồng bộ. Bạn sẽ kết thúc với một cái gì đó như thế này

extension WKWebViewConfiguration {
    /// Async Factory method to acquire WKWebViewConfigurations packaged with system cookies
    static func cookiesIncluded(completion: @escaping (WKWebViewConfiguration?) -> Void) {
        let config = WKWebViewConfiguration()
        guard let cookies = HTTPCookieStorage.shared.cookies else {
            completion(config)
            return
        }
        // Use nonPersistent() or default() depending on if you want cookies persisted to disk
        // and shared between WKWebViews of the same app (default), or not persisted and not shared
        // across WKWebViews in the same app.
        let dataStore = WKWebsiteDataStore.nonPersistent()
        let waitGroup = DispatchGroup()
        for cookie in cookies {
            waitGroup.enter()
            dataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
        }
        waitGroup.notify(queue: DispatchQueue.main) {
            config.websiteDataStore = dataStore
            completion(config)
        }
    }
}

và sau đó sử dụng nó như

override func loadView() {
    view = UIView()
    WKWebViewConfiguration.cookiesIncluded { [weak self] config in
        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.load(request)
        self.view = webView
    }
}

Ví dụ trên trì hoãn việc tạo chế độ xem cho đến thời điểm cuối cùng có thể, một giải pháp khác sẽ là tạo cấu hình hoặc chế độ xem web trước và xử lý bản chất không đồng bộ trước khi tạo bộ điều khiển xem.

Lưu ý cuối cùng: một khi bạn tạo webview này, bạn đã đặt nó vào trạng thái hoang dã, bạn không thể thêm nhiều cookie mà không sử dụng các phương pháp được mô tả trong câu trả lời này . Tuy nhiên, bạn có thể sử dụng WKHTTPCookieStoreObserverapi để ít nhất quan sát các thay đổi xảy ra với cookie. Vì vậy, nếu cookie phiên được cập nhật trong chế độ xem web, bạn có thể cập nhật thủ công hệ thống HTTPCookieStoragevới cookie mới này nếu muốn.

Để biết thêm về điều này, bỏ qua đến 18:00 tại Tải nội dung web tùy chỉnh phiên WWDC 2017 này . Vào đầu phiên này, có một mẫu mã lừa đảo mà bỏ qua thực tế là webview nên được tạo trong trình xử lý hoàn thành.

cookieStore.setCookie(cookie!) {
    webView.load(loggedInURLRequest)
}

Bản demo trực tiếp lúc 18:00 làm rõ điều này.

Chỉnh sửa kể từ Mojave Beta 7 và iOS 12 Beta 7 ít nhất, tôi thấy hành vi phù hợp hơn nhiều với cookie. Các setCookie(_:)phương pháp thậm chí dường như cho phép cookie thiết lập sau khi WKWebViewđã được tạo. Tôi đã tìm thấy nó quan trọng mặc dù, để không chạm vào processPoolbiến số. Chức năng cài đặt cookie hoạt động tốt nhất khi không có nhóm bổ sung nào được tạo và khi thuộc tính đó được để yên. Tôi nghĩ thật an toàn khi nói rằng chúng tôi đã gặp sự cố do một số lỗi trong WebKit.


Có vẻ như việc xử lý / cài đặt cookie đáng tin cậy hơn trong Mojave 10.14 beta 3 và iOS 12 beta 3
nteissler

6
Câu trả lời rất sâu sắc và bị đánh giá thấp
Nicolás Carrasco

1
Tôi vẫn gặp sự cố này trong iOS 12 với WKWebView đã được tải. Đôi khi setCookie () thực sự sẽ được đồng bộ hóa với WKWebView ngay lập tức, đôi khi nó sẽ không khiến việc xử lý có phần rời rạc
bmjohns 17/12/18

Tôi vẫn còn thấy các vấn đề vì radar đã được yêu cầu sửa chữa, nhưng ít thường xuyên hơn. Bạn có thường xuyên thấy lỗi cookie không? Nếu bạn có một dự án đủ nhỏ, có thể tái tạo, tôi thực sự khuyên bạn nên gửi một lỗi webkit tại đây: webkit.org/reporting-bugs Bạn cũng có thể tweet Brady Eidson (độc đáo) một kiến ​​trúc sư webkit tại apple, người rất nhạy cảm với các loại này báo cáo và lỗi.
nteissler

đây là câu trả lời đúng - không cần dịch thủ công các cookie dưới dạng các trường tiêu đề trong mỗi URLRequest, chỉ cần setCookie () được sử dụng như được mô tả ở đây.
Guillaume Laurent

25

làm việc cho tôi

func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
    let headerFields = navigationAction.request.allHTTPHeaderFields
    var headerIsPresent = contains(headerFields?.keys.array as! [String], "Cookie")

    if headerIsPresent {
        decisionHandler(WKNavigationActionPolicy.Allow)
    } else {
        let req = NSMutableURLRequest(URL: navigationAction.request.URL!)
        let cookies = yourCookieData
        let values = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies)
        req.allHTTPHeaderFields = values
        webView.loadRequest(req)

        decisionHandler(WKNavigationActionPolicy.Cancel)
    }
}

Hack tuyệt vời, đặc biệt là cách iOS thông minh về việc ghi đè cookie trong WKWebView hiện có. Gotcha duy nhất là WKNavlationKey trước đó đã trở nên cũ. Mã khác có thể đang chờ đợi vô ích trên mã cũ.
BaseZen

2
điều này có đúng không Đánh giá cao nó có thể làm việc trong một số trường hợp. Tuy nhiên, trách nhiệm của phương thức đại biểu này - quyết địnhPolicyForNavlationAction - là quyết định chính sách; không thực sự tải yêu cầu. Điều đó đã được bắt đầu trước đây. Trong trường hợp nào, điều này không khiến yêu cầu được tải hai lần?
Max MacLeod

2
@MaxMacLeod Trong elseđiều kiện anh ấy gọi decisionHandlerđóng cửa .cancelđể webviewkhông thực sự tải yêu cầu ban đầu. Sau khi loadRequestđược gọi trong elseđiều kiện, phương thức ủy nhiệm này sẽ được gọi lại cho yêu cầu đó và nó sẽ đi vào ifđiều kiện vì Cookietiêu đề sẽ xuất hiện.
halil_g

2
Mặc dù điều này sẽ không hoạt động khi yêu cầu ban đầu đã có một số cookie được đặt, vì nó sẽ không bao giờ đi vào elseđiều kiện.
halil_g

Lưu ý rằng đây là 1) Không hoạt động cho mọi tình huống - ví dụ: trường hợp khi chế độ xem web tải khung 2) Không an toàn - nó có thể gửi cookie với thông tin nhạy cảm tới URL của bên thứ 3
Peter Prokop

20

Đây là phiên bản giải pháp Mattrs của tôi trong Swift để tiêm tất cả cookie từ HTTPCookieStorage. Điều này được thực hiện chủ yếu để tiêm cookie xác thực để tạo phiên người dùng.

public func setupWebView() {
    let userContentController = WKUserContentController()
    if let cookies = HTTPCookieStorage.shared.cookies {
        let script = getJSCookiesString(for: cookies)
        let cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
        userContentController.addUserScript(cookieScript)
    }
    let webViewConfig = WKWebViewConfiguration()
    webViewConfig.userContentController = userContentController

    self.webView = WKWebView(frame: self.webViewContainer.bounds, configuration: webViewConfig)
}

///Generates script to create given cookies
public func getJSCookiesString(for cookies: [HTTPCookie]) -> String {
    var result = ""
    let dateFormatter = DateFormatter()
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    dateFormatter.dateFormat = "EEE, d MMM yyyy HH:mm:ss zzz"

    for cookie in cookies {
        result += "document.cookie='\(cookie.name)=\(cookie.value); domain=\(cookie.domain); path=\(cookie.path); "
        if let date = cookie.expiresDate {
            result += "expires=\(dateFormatter.stringFromDate(date)); "
        }
        if (cookie.secure) {
            result += "secure; "
        }
        result += "'; "
    }
    return result
}

tốt hơn để thêm dòng này để đảm bảo định dạng miền địa phương là chính xác:dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
bubuxu

gọi cái này ở đâu
markhorrocks

Điều này hoạt động tốt với tôi trong Swift 4 (với những điều chỉnh nhỏ)
Frédéric Adda

Điều này rất tốt cho tôi, nhưng chỉ lần thứ hai tôi truy cập trang web (lần đầu tiên cookie không được đặt) - có ai gặp phải điều này không?
MrChrisBarker

Tải đầu tiên báo lỗi tải thứ hai làm việc :( vấn đề có thể là gì?
Shauket Sheikh

10

đặt cookie

self.webView.evaluateJavaScript("document.cookie='access_token=your token';domain='your domain';") { (data, error) -> Void in
        self.webView.reload()
}

xóa cookie

self.webView.evaluateJavaScript("document.cookie='access_token=';domain='your domain';") { (data, error) -> Void in
        self.webView.reload()
}

9

Cập nhật Swift 3:

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    if let urlResponse = navigationResponse.response as? HTTPURLResponse,
       let url = urlResponse.url,
       let allHeaderFields = urlResponse.allHeaderFields as? [String : String] {
       let cookies = HTTPCookie.cookies(withResponseHeaderFields: allHeaderFields, for: url)
       HTTPCookieStorage.shared.setCookies(cookies , for: urlResponse.url!, mainDocumentURL: nil)
       decisionHandler(.allow)
    }
}

1
Xin chào, bạn có thể thêm mã để nhận cookie HTTPCookieStorage.sharedkhông?
markhorrocks

Đây là cách duy nhất tôi có WKWebView để thêm cookie vào mọi yêu cầu của
webview

Nếu nó chứa cookie httponly trong phản hồi, bạn không thể nhận được giá trị cookie theo cách này.
não

1
Điều này chỉ thực hiện khi đặt cookie trở lại bộ lưu trữ httpcookies, mã cài đặt cookie cho wkwebview ở đâu?
Shauket Sheikh

8

Sau khi xem qua các câu trả lời khác nhau ở đây và không có bất kỳ thành công nào, tôi đã lướt qua tài liệu WebKit và tình cờ tìm thấy requestHeaderFieldsphương thức tĩnh trên HTTPCookieđó, chuyển đổi một mảng cookie thành định dạng phù hợp với trường tiêu đề. Kết hợp điều này với cái nhìn sâu sắc của mattr về việc cập nhật URLRequesttrước khi tải nó với các tiêu đề cookie đã đưa tôi đến vạch đích.

Swift 4.1, 4.2, 5.0:

var request = URLRequest(url: URL(string: "https://example.com/")!)
let headers = HTTPCookie.requestHeaderFields(with: cookies)
for (name, value) in headers {
    request.addValue(value, forHTTPHeaderField: name)
}

let webView = WKWebView(frame: self.view.frame)
webView.load(request)

Để làm cho điều này thậm chí đơn giản hơn, sử dụng một phần mở rộng:

extension WKWebView {
    func load(_ request: URLRequest, with cookies: [HTTPCookie]) {
        var request = request
        let headers = HTTPCookie.requestHeaderFields(with: cookies)
        for (name, value) in headers {
            request.addValue(value, forHTTPHeaderField: name)
        }

        load(request)
    }
}

Bây giờ nó chỉ trở thành:

let request = URLRequest(url: URL(string: "https://example.com/")!)
let webView = WKWebView(frame: self.view.frame)
webView.load(request, with: cookies)

Tiện ích mở rộng này cũng có sẵn trong LionheartExtensions nếu bạn chỉ muốn một giải pháp thả vào. Chúc mừng!


1
@ShauketSheikh hmm, trong tình huống nào thì cái này không hoạt động?
Dan Loewenherz

Tôi đã thử nghiệm bằng trình giả lập ios 8, có vẻ như nó không gửi cookie. tôi đã kiểm tra nó
Shauket Sheikh

tôi đã đăng câu trả lời của mình, bạn có thể thử
@Dan

7

Trong iOS 11, bạn có thể quản lý cookie ngay bây giờ :), hãy xem phiên này: https://developer.apple.com/ideo/play/wwdc2017/220/

nhập mô tả hình ảnh ở đây


2
@ShobhakarTiwari tại sao? Có bất kỳ thay đổi nào xảy ra trong bản phát hành chính thức iOS11 không?
Jacky

Cách tốt nhất để đi nếu bạn chỉ hỗ trợ iOS 11 trở lên, nếu bạn cần hỗ trợ các phiên bản trước, hãy sử dụng JavaScript trước khi tải trang.
PashaN

Điều này hiệu quả với tôi, ngoại trừ thực tế là đôi khi phương thức setcookie KHÔNG chạy trình xử lý hoàn thành của nó, nghĩa là đôi khi trang web của tôi không tải - chỉ xảy ra trên thiết bị, xảy ra lần đóng cửa thứ 3/4 thứ 5 webview và sau khi nó xảy ra một lần, nó sẽ tiếp tục xảy ra cho đến khi tôi đặt lại ứng dụng - có ai cũng gặp phải vấn đề này không?
Binya Koatz

5

Lý do đằng sau đăng câu trả lời này là tôi đã thử nhiều giải pháp nhưng không ai hoạt động đúng, hầu hết câu trả lời không hoạt động trong trường hợp phải đặt cookie lần đầu và có cookie kết quả không đồng bộ hóa lần đầu tiên, Vui lòng sử dụng giải pháp này cho cả hai iOS> = 11.0 <= iOS 11 đến 8.0, cũng hoạt động với đồng bộ hóa cookie lần đầu tiên.

Dành cho iOS> = 11.0 - Swift 4.2

Nhận cookie http và đặt trong cửa hàng cookie wkwebview như thế này, đây là điểm rất khó để tải yêu cầu của bạn trong wkwebview , phải gửi yêu cầu tải khi cookie sẽ được đặt hoàn toàn, đây là chức năng mà tôi đã viết.

Chức năng gọi với đóng hoàn thành, bạn gọi tải webview. FYI chức năng này chỉ xử lý iOS> = 11.0

self.WwebView.syncCookies {
    if let request = self.request {
       self.WwebView.load(request)
    }
}

Đây là triển khai cho chức năng syncCookies .

func syncCookies(completion:@escaping ()->Void) {

if #available(iOS 11.0, *) {

      if let yourCookie = "HERE_YOUR_HTTP_COOKIE_OBJECT" {
        self.configuration.websiteDataStore.httpCookieStore.setCookie(yourCookie, completionHandler: {
              completion()
        })
     }
  } else {
  //Falback just sent 
  completion()
}
}

Dành cho iOS 8 cho đến iOS 11

bạn cần thiết lập thêm một số thứ bạn cần để đặt cookie hai lần một thông qua WKUserScript và đừng quên thêm cookie theo yêu cầu, nếu không, cookie của bạn không đồng bộ hóa lần đầu tiên và bạn sẽ thấy trang của bạn không tải lần đầu đúng cách. đây là cái quái mà tôi thấy để hỗ trợ cookie cho iOS 8.0

trước khi bạn tạo đối tượng Wkwebview.

func setUpWebView() {

    let userController: WKUserContentController = WKUserContentController.init()

    if IOSVersion.SYSTEM_VERSION_LESS_THAN(version: "11.0") {
        if let cookies = HTTPCookieStorage.shared.cookies {
            if let script = getJSCookiesString(for: cookies) {
                cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
                userController.addUserScript(cookieScript!)
            }
        }
    }

    let webConfiguration = WKWebViewConfiguration()
    webConfiguration.processPool = BaseWebViewController.processPool


    webConfiguration.userContentController = userController


    let customFrame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: 0.0, height: self.webContainerView.frame.size.height))
    self.WwebView = WKWebView (frame: customFrame, configuration: webConfiguration)
    self.WwebView.translatesAutoresizingMaskIntoConstraints = false
    self.webContainerView.addSubview(self.WwebView)
    self.WwebView.uiDelegate = self
    self.WwebView.navigationDelegate = self
    self.WwebView.allowsBackForwardNavigationGestures = true // A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations
    self.WwebView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)


 self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .trailing, relatedBy: .equal, toItem: self.webContainerView, attribute: .trailing, multiplier: 1, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .leading, relatedBy: .equal, toItem: self.webContainerView, attribute: .leading, multiplier: 1, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .top, relatedBy: .equal, toItem: self.webContainerView, attribute: .top, multiplier: 1, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .bottom, relatedBy: .equal, toItem: self.webContainerView, attribute: .bottom, multiplier: 1, constant: 0))


}

Tập trung vào chức năng này getJSCookiesString

 public func getJSCookiesString(for cookies: [HTTPCookie]) -> String? {

    var result = ""
    let dateFormatter = DateFormatter()
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    dateFormatter.dateFormat = "EEE, d MMM yyyy HH:mm:ss zzz"

    for cookie in cookies {
        if cookie.name == "yout_cookie_name_want_to_sync" {
            result += "document.cookie='\(cookie.name)=\(cookie.value); domain=\(cookie.domain); path=\(cookie.path); "
            if let date = cookie.expiresDate {
                result += "expires=\(dateFormatter.string(from: date)); "
            }
            if (cookie.isSecure) {
                result += "secure; "
            }
            result += "'; "
        }

    }

    return result
}

Đây là một bước khác mà wkuserscript không đồng bộ hóa cookie ngay lập tức, có rất nhiều điều thú vị để tải trang lần đầu tiên với cookie, đó là tải lại webview một lần nữa nếu nó chấm dứt quá trình nhưng tôi không khuyên bạn nên sử dụng nó, nó không tốt cho quan điểm của người dùng , quái gì là bất cứ khi nào bạn sẵn sàng tải cookie đặt yêu cầu trong tiêu đề yêu cầu cũng như cách này, đừng quên thêm kiểm tra phiên bản iOS. trước khi tải yêu cầu gọi chức năng này.

request?.addCookies()

tôi đã viết tiện ích mở rộng cho URLRequest

extension URLRequest {

internal mutating func addCookies() {
    //"appCode=anAuY28ucmFrdXRlbi5yZXdhcmQuaW9zLXpOQlRTRmNiejNHSzR0S0xuMGFRb0NjbUg4Ql9JVWJH;rpga=kW69IPVSYZTo0JkZBicUnFxC1g5FtoHwdln59Z5RNXgJoMToSBW4xAMqtf0YDfto;rewardadid=D9F8CE68-CF18-4EE6-A076-CC951A4301F6;rewardheader=true"
    var cookiesStr: String = ""

    if IOSVersion.SYSTEM_VERSION_LESS_THAN(version: "11.0") {
        let mutableRequest = ((self as NSURLRequest).mutableCopy() as? NSMutableURLRequest)!
        if let yourCookie = "YOUR_HTTP_COOKIE_OBJECT" {
            // if have more than one cookies dont forget to add ";" at end
            cookiesStr += yourCookie.name + "=" + yourCookie.value + ";"

            mutableRequest.setValue(cookiesStr, forHTTPHeaderField: "Cookie")
            self = mutableRequest as URLRequest

        }
    }

  }
}

bây giờ bạn đã sẵn sàng để thử nghiệm iOS> 8


2

Vui lòng tìm giải pháp mà rất có thể sẽ làm việc cho bạn ra khỏi hộp. Về cơ bản, nó đã được sửa đổi và cập nhật cho câu trả lời của Swift 4 @ user3589213 .

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    let headerKeys = navigationAction.request.allHTTPHeaderFields?.keys
    let hasCookies = headerKeys?.contains("Cookie") ?? false

    if hasCookies {
        decisionHandler(.allow)
    } else {
        let cookies = HTTPCookie.requestHeaderFields(with: HTTPCookieStorage.shared.cookies ?? [])

        var headers = navigationAction.request.allHTTPHeaderFields ?? [:]
        headers += cookies

        var req = navigationAction.request
        req.allHTTPHeaderFields = headers

        webView.load(req)

        decisionHandler(.cancel)
    }
}

1

Tôi đã thử tất cả các câu trả lời ở trên nhưng không có câu trả lời nào hoạt động. Sau rất nhiều lần thử, cuối cùng tôi cũng tìm được một cách đáng tin cậy để đặt cookie WKWebview.

Trước tiên, bạn phải tạo một phiên bản của WKProcessPool và đặt nó thành WKWebViewConfiguration được sử dụng để khởi tạo chính WkWebview:

    private lazy var mainWebView: WKWebView = {
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration.processPool = WKProcessPool()
        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.navigationDelegate = self
        return webView
    }()

Đặt WKProcessPool là bước quan trọng nhất ở đây.WKWebview sử dụng cách ly quy trình - có nghĩa là nó chạy trên một quy trình khác với quy trình của ứng dụng của bạn. Điều này đôi khi có thể gây ra xung đột và ngăn cookie của bạn được đồng bộ hóa đúng cách với WKWebview.

Bây giờ hãy xem định nghĩa của WKProcessPool

Nhóm quy trình được liên kết với chế độ xem web được chỉ định bởi cấu hình chế độ xem web. Mỗi chế độ xem web được cung cấp quy trình Nội dung web riêng cho đến khi đạt đến giới hạn quy trình do xác định thực hiện; sau đó, các lượt xem web với cùng nhóm quy trình sẽ kết thúc việc chia sẻ các quy trình Nội dung web.

Hãy chú ý đến câu cuối cùng nếu bạn dự định sử dụng cùng một WKWebview cho các yêu cầu tiếp theo

lượt xem web với cùng nhóm quy trình kết thúc chia sẻ quy trình Nội dung web

Ý tôi là nếu bạn không sử dụng cùng một phiên bản của WKProcessPool mỗi lần bạn định cấu hình WKWebView cho cùng một tên miền (có thể bạn có một VC A có chứa WKWebView và bạn muốn tạo các phiên bản khác nhau của VC A ở các vị trí khác nhau ), có thể có xung đột cài đặt cookie. Để giải quyết vấn đề, sau lần tạo WKProcessPool đầu tiên cho WKWebView tải miền B, tôi lưu nó trong một singleton và sử dụng cùng WKProcessPool mỗi khi tôi phải tạo WKWebView tải cùng tên miền B

private lazy var mainWebView: WKWebView = {
    let webConfiguration = WKWebViewConfiguration()
    if Enviroment.shared.processPool == nil {
        Enviroment.shared.processPool = WKProcessPool()
    }
    webConfiguration.processPool = Enviroment.shared.processPool!
    webConfiguration.processPool = WKProcessPool()
    let webView = WKWebView(frame: .zero, configuration: webConfiguration)
    webView.navigationDelegate = self
    return webView
}()

Sau khi quá trình khởi tạo, bạn có thể tải một URLRequest bên trong khối hoàn thành của httpCookieStore.setCookie. Tại đây, bạn phải đính kèm cookie vào tiêu đề yêu cầu nếu không nó sẽ không hoạt động.

P / s: Tôi đã đánh cắp phần mở rộng từ câu trả lời tuyệt vời ở trên của Dan Loewenherz

mainWebView.configuration.websiteDataStore.httpCookieStore.setCookie(your_cookie) {
        self.mainWebView.load(your_request, with: [your_cookie])
}

extension WKWebView {
   func load(_ request: URLRequest, with cookies: [HTTPCookie]) {
      var request = request
      let headers = HTTPCookie.requestHeaderFields(with: cookies)
      for (name, value) in headers {
         request.addValue(value, forHTTPHeaderField: name)
      }        
      load(request)
   }
}

1

Phiên bản của tôi về câu trả lời của nteiss. Đã thử nghiệm trên iOS 11, 12, 13. Trông giống như bạn không cần phải sử dụng DispatchGrouptrên iOS 13nữa.

Tôi sử dụng chức năng không tĩnh includeCustomCookiestrên WKWebViewConfigurationđể tôi có thể cập nhật cookiesmỗi khi tôi tạo mới WKWebViewConfiguration.

extension WKWebViewConfiguration {
    func includeCustomCookies(cookies: [HTTPCookie], completion: @escaping  () -> Void) {
        let dataStore = WKWebsiteDataStore.nonPersistent()
        let waitGroup = DispatchGroup()

        for cookie in cookies {
            waitGroup.enter()
            dataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
        }

        waitGroup.notify(queue: DispatchQueue.main) {
            self.websiteDataStore = dataStore
            completion()
        }
    }
}

Sau đó, tôi sử dụng nó như thế này:

let customUserAgent: String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15"

let customCookies: [HTTPCookie] = {
    let cookie1 = HTTPCookie(properties: [
        .domain: "yourdomain.com",
        .path: "/",
        .name: "auth_token",
        .value: APIManager.authToken
    ])!

    let cookie2 = HTTPCookie(properties: [
        .domain: "yourdomain.com",
        .path: "/",
        .name: "i18next",
        .value: "ru"
    ])!

    return [cookie1, cookie2]
}()

override func viewDidLoad() {
    super.viewDidLoad()

    activityIndicatorView.startAnimating()

    let webConfiguration = WKWebViewConfiguration()
    webConfiguration.includeCustomCookies(cookies: customCookies, completion: { [weak self] in
        guard let strongSelf = self else { return }
        strongSelf.webView = WKWebView(frame: strongSelf.view.bounds, configuration: webConfiguration)
        strongSelf.webView.customUserAgent = strongSelf.customUserAgent
        strongSelf.webView.navigationDelegate = strongSelf
        strongSelf.webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        strongSelf.view.addSubview(strongSelf.webView)
        strongSelf.view.bringSubviewToFront(strongSelf.activityIndicatorView)
        strongSelf.webView.load(strongSelf.request)
    })
}

0

Khắc phục tốt hơn cho các yêu cầu XHR được hiển thị ở đây

Phiên bản Swift 4:

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Swift.Void) {
    guard
        let response = navigationResponse.response as? HTTPURLResponse,
        let url = navigationResponse.response.url
    else {
        decisionHandler(.cancel)
        return
    }

    if let headerFields = response.allHeaderFields as? [String: String] {
        let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url)
        cookies.forEach { (cookie) in
            HTTPCookieStorage.shared.setCookie(cookie)
        }
    }

    decisionHandler(.allow)
}

0

Nếu bất cứ ai đang sử dụng Alamofire, thì đây là giải pháp tốt hơn.

  let cookies = Alamofire.SessionManager.default.session.configuration.httpCookieStorage?.cookies(for: URL(string: BASE_URL)!)
  for (cookie) in cookies ?? [] {
      webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
  }

0

Điều này làm việc cho tôi: Sau setcookies, thêm fetchdatarecords

   let cookiesSet = NetworkProvider.getCookies(forKey : 
    PaywallProvider.COOKIES_KEY, completionHandler: nil)
                let dispatchGroup = DispatchGroup()
                for (cookie) in cookiesSet {
                    if #available(iOS 11.0, *) {
                        dispatchGroup.enter()
                        self.webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie){
                            dispatchGroup.leave()
                            print ("cookie added: \(cookie.description)")
                            }
                        } else {
                                            // TODO Handle ios 10 Fallback on earlier versions
                        }
                    }
                    dispatchGroup.notify(queue: .main, execute: {


    self.webView.configuration.websiteDataStore.fetchDataRecords(ofTypes: 
    WKWebsiteDataStore.allWebsiteDataTypes()) { records in
                            records.forEach { record in

                                print("[WebCacheCleaner] Record \(record)")
                            }
                            self.webView.load(URLRequest(url: 
    self.dataController.premiumArticleURL , 
    cachePolicy:NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData,
                                                         timeoutInterval: 10.0))
                        }

                    })
                }

0

Khi thêm nhiều mục cookie, bạn có thể làm như thế này: ( path& domainlà bắt buộc cho mỗi mục)

NSString *cookie = [NSString stringWithFormat:@"document.cookie = 'p1=%@;path=/;domain=your.domain;';document.cookie = 'p2=%@;path=/;domain=your.domain;';document.cookie = 'p3=%@;path=/;domain=your.domain;';", p1_string, p2_string, p3_string];

WKUserScript *cookieScript = [[WKUserScript alloc]
            initWithSource:cookie
            injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];

[userContentController addUserScript:cookieScript];

mặt khác, chỉ có mục cookie đầu tiên sẽ được đặt.


0

Bạn cũng có thể sử dụng WKWebsiteDataStore để có hành vi tương tự với HTTPCookieStorage từ UIWebView.

let dataStore = WKWebsiteDataStore.default()
let cookies = HTTPCookieStorage.shared.cookies ?? [HTTPCookie]()
cookies.forEach({
    dataStore.httpCookieStore.setCookie($0, completionHandler: nil)
})

0

Mã dưới đây hoạt động tốt trong dự án Swift5 của tôi. thử tải url bằng WKWebView bên dưới:

    private func loadURL(urlString: String) {
        let url = URL(string: urlString)
        guard let urlToLoad = url else { fatalError("Cannot find any URL") }

        // Cookies configuration
        var urlRequest = URLRequest(url: urlToLoad)
        if let cookies = HTTPCookieStorage.shared.cookies(for: urlToLoad) {
            let headers = HTTPCookie.requestHeaderFields(with: cookies)
            for header in headers { urlRequest.addValue(header.value, forHTTPHeaderField: header.key) }
        }

        webview.load(urlRequest)
    }

0

Đây là giải pháp của tôi để xử lý với Cookies và WKWebView trong iOS 9 trở lên.

import WebKit

extension WebView {

    enum LayoutMode {
        case fillContainer
    }

    func autoLayout(_ view: UIView?, mode: WebView.LayoutMode = .fillContainer) {
        guard let view = view else { return }
        self.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(self)

        switch mode {
        case .fillContainer:
                NSLayoutConstraint.activate([
                self.topAnchor.constraint(equalTo: view.topAnchor),
                self.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                self.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                self.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            ])
        }
    }

}

class WebView : WKWebView {

    var request : URLRequest?

    func load(url: URL, useSharedCookies: Bool = false) {
        if useSharedCookies, let cookies = HTTPCookieStorage.shared.cookies(for: url) {
            self.load(url: url, withCookies: cookies)
        } else {
            self.load(URLRequest(url: url))
        }
    }

    func load(url: URL, withCookies cookies: [HTTPCookie]) {
        self.request = URLRequest(url: url)
        let headers = HTTPCookie.requestHeaderFields(with: cookies)
        self.request?.allHTTPHeaderFields = headers
        self.load(request!)
    }

}

0

Lỗi này tôi đã làm là tôi đã chuyển toàn bộ url trong thuộc tính miền, nó chỉ nên là tên miền.

let cookie = HTTPCookie(properties: [
.domain: "example.com",
.path: "/",
.name: "MyCookieName",
.value: "MyCookieValue",
.secure: "TRUE",
])! 

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.