[ios] 쿠키를 WKWebView에서 사용하도록 설정할 수 있습니까?

기존 앱을에서 (으) UIWebView로 전환하려고 합니다 WKWebView. 현재 앱은 외부의 사용자 로그인 / 세션을 관리하고 에 인증 webview하는 cookies데 필요한 항목을에 설정 합니다 NSHTTPCookieStore. 불행하게도 새로운 WKWebView를 사용하지 않습니다 cookies로부터 NSHTTPCookieStorage. 이것을 달성하는 다른 방법이 있습니까?



답변

iOS 11 이상에서만 편집

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)

HTTPCookeStorage에서 가져 오기 때문에 다음을 수행 할 수 있습니다.

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

iOS 10 이하의 이전 답변

초기로드 요청에서 쿠키를 설정해야하는 경우 NSMutableURLRequest에서 쿠키를 설정할 수 있습니다. 쿠키는 특별히 형식이 지정된 요청 헤더이므로 다음과 같이 얻을 수 있습니다.

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];

페이지에서 쿠키를 설정하기 위해 후속 AJAX 요청이 필요한 경우 WKUserScript를 사용하여 문서 시작시 javascript를 통해 프로그래밍 방식으로 값을 설정하면됩니다.

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];

이 두 가지 기술을 결합하면 쿠키 값을 Native App Land에서 Web View Land로 전송할 수있는 충분한 도구가 제공됩니다. 고급 쿠키가 더 필요한 경우 Mozilla 페이지쿠키 자바 스크립트 API에서 자세한 정보를 찾을 수 있습니다 .

예, 애플이 UIWebView의 많은 장점을 지원하지 않는다는 사실이 짜증납니다 . 그들이 그들을 지원할 지 확신 할 수 없지만, 곧이 일이 일어날 것으로 기대합니다. 도움이 되었기를 바랍니다!


답변

이 답변 (놀랍게도 도움이되었습니다)을 가지고 놀고 나서 우리는 몇 가지 변경을해야했습니다.

  • 여러 도메인을 처리하려면 해당 도메인간에 개인 쿠키 정보를 유출하지 않고 웹보기가 필요합니다
  • 보안 쿠키를 존중하기 위해 필요합니다
  • 서버가 쿠키 값을 변경하면 앱에서 쿠키 값을 알기를 원합니다. NSHTTPCookieStorage
  • 서버가 쿠키 값을 변경하면 링크 / AJAX 등을 따라갈 때 스크립트가 쿠키를 원래 값으로 다시 설정하지 않습니다.

그래서 우리는 이것을 코드로 수정했습니다.

요청 만들기

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...

이렇게하면 다른 도메인의 공유 저장소에서 쿠키를 보내지 않고 안전하지 않은 요청으로 보안 쿠키를 보내지 않고도 첫 번째 요청에 올바른 쿠키가 설정됩니다.

추가 요청 처리

또한 다른 요청에 쿠키가 설정되어 있는지 확인해야합니다. 이는 쿠키 세트가 있는지 확인하고 쿠키 세트가 없는지 확인하는 쿠키를 사용하여 문서로드시 실행되는 스크립트를 사용하여 수행됩니다 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];

쿠키 변경 처리

또한 쿠키 값을 변경하는 서버를 처리해야합니다. 이것은 우리가 업데이트하기 위해 만들고있는 웹보기에서 다시 불러 오기 위해 다른 스크립트를 추가하는 것을 의미합니다 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"];

변경된 쿠키를 업데이트하기 위해 delegate 메소드를 구현하여 현재 도메인에서만 쿠키를 업데이트하고 있는지 확인하십시오.

- (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];
        }
    }
}

이것은 WKWebView를 사용하는 각 장소를 다르게 처리하지 않고도 쿠키 문제를 해결하는 것으로 보입니다. 이제이 코드를 도우미로 사용하여 웹보기를 만들면 투명하게 업데이트 NSHTTPCookieStorage됩니다.


편집 : NSHTTPCookie에서 개인 카테고리를 사용했습니다. 코드는 다음과 같습니다.

- (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;
}


답변

쿠키는 쿠키를 WKWebView생성 하기 전에 구성에서 설정해야합니다 . 그렇지 않으면 WKHTTPCookieStoresetCookie완료 핸들러를 사용해도 쿠키가 웹보기에 안정적으로 동기화되지 않습니다. 이것은 문서 에서이 줄로 돌아갑니다 .WKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

그것은 @NSCopying다소 깊은 사본입니다. 구현은 저쪽에 있지만 최종 결과는 웹보기를 초기화하기 전에 쿠키를 설정하지 않으면 거기에있는 쿠키를 신뢰할 수 없다는 것입니다. 뷰를 초기화하면 비동기 프로세스가되기 때문에 앱 아키텍처가 복잡해질 수 있습니다. 당신은 이런 식으로 끝날 것입니다

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)
        }
    }
}

그런 다음과 같은 것을 사용하십시오.

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

위의 예제는 마지막 순간까지보기 생성을 지연시킵니다. 또 다른 솔루션은 구성 또는 웹보기를 미리 작성하고보기 컨트롤러를 작성하기 전에 비동기 특성을 처리하는 것입니다.

마지막 참고 사항 :이 웹보기를 만든 후에는 야생으로 느슨하게 설정하면 이 답변에 설명 된 방법을 사용하지 않고 더 많은 쿠키를 추가 할 수 없습니다 . 그러나 WKHTTPCookieStoreObserverAPI를 사용하여 최소한 쿠키 변경 사항을 관찰 할 수 있습니다 . 따라서 웹뷰에서 세션 쿠키가 업데이트 HTTPCookieStorage되면 원하는 경우이 새 쿠키로 시스템을 수동으로 업데이트 할 수 있습니다 .

이에 대한 자세한 내용은이 2017 WWDC 세션 사용자 정의 웹 컨텐츠로드 에서 18:00로 건너 뛰십시오 . 이 세션을 시작할 때 웹뷰가 완료 핸들러에서 작성되어야한다는 사실을 생략하는기만적인 코드 샘플이 있습니다.

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

18:00의 라이브 데모가이를 명확하게 보여줍니다.

최소한 Mojave Beta 7 및 iOS 12 Beta 7부터 편집 하면 쿠키와 훨씬 일관된 동작이 나타납니다. 이 setCookie(_:)방법 WKWebView은 쿠키를 만든 후 쿠키를 설정하는 것으로 보입니다 . 내가하는 것이 중요하지만를 발견했다 만지지processPool 전혀 변수를. 쿠키 설정 기능은 추가 풀이 생성되지 않고 해당 속성이 단독으로 남아있을 때 가장 잘 작동합니다. WebKit의 일부 버그로 인해 문제가 있다고 말하는 것이 안전하다고 생각합니다.


답변

나를 위해 일해

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)
    }
}


답변

다음은 HTTPCookieStorage 에서 모든 쿠키를 주입하는 Swift 의 Mattrs 솔루션 버전입니다 . 이것은 주로 사용자 쿠키를 생성하기 위해 인증 쿠키를 주입하기 위해 수행되었습니다.

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
}


답변

쿠키 설정

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

쿠키 삭제

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


답변

스위프트 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)
    }
}