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


135

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

답변:


186

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의 많은 장점을 지원하지 않는다는 사실이 짜증납니다 . 그들이 그들을 지원할 지 확신 할 수 없지만, 곧이 일이 일어날 것으로 기대합니다. 도움이 되었기를 바랍니다!


1
후속 요청에 쿠키를 주입하기에 가장 좋은 장소는 어디입니까? 예를 들어, 초기 페이지로드는 위의 답변에서 다루지 만 페이지에 동일한 도메인으로 연결되는 링크가 있고 요청에 동일한 쿠키를 주입해야하는 경우 어떻게해야합니까? DidProvisionalNavigation을 시작 했습니까?
메이슨 G. Zhwiti

1
죄송합니다. 작동하지 않습니다. 내 생각에 도메인이 동일하면 아무 문제가 없어야합니다. 링크가 요청을로드 한 동일한 도메인을 가리키는 코드를 다시 확인할 수 있습니까? 또한 쿠키는 특정 "경로"로 제한 될 수도 있습니다. 어쩌면 그게 문제를 일으키는 것일까 요?
mattr

11
쿠키를 설정하는 자바 스크립트 기술은 "HTTP 전용"쿠키에는 작동하지 않습니다.
Ahmed Nasser

1
위의 방법은 훌륭하게 작동하지만 후속 AJAX 호출에서 쿠키가 중복 된 것을 볼 수 있습니다 (한 번만 복제 됨).
Durga Vundavalli 2016 년

1
@ Axel92Dev 해결 방법은 웹보기에서 서버로의 첫 번째 요청이 HTTPOnly 플래그를 사용하여 쿠키를 다시 설정하도록 웹보기에 명시 적으로 응답하는 응답을 얻는 것입니다 (예 : 응답에서 쿠키를 다시 설정하십시오). 웹보기를 초기화 할 때 그 목적을 위해 특별한 API를 만든 다음 정상적으로 성공하면 웹보기를 사용할 수 있습니다.
Ahmed Nasser 17 년

64

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

  • 여러 도메인을 처리하려면 해당 도메인간에 개인 쿠키 정보를 유출하지 않고 웹보기가 필요합니다
  • 보안 쿠키를 존중하기 위해 필요합니다
  • 서버가 쿠키 값을 변경하면 앱에서 쿠키 값을 알기를 원합니다. 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;
}

6
코드를 WKWebView의 하위 클래스로 래핑했습니다. 확인해 주시기 바랍니다 github.com/haifengkao/YWebView
하이 펭 가오

쿠키에 값에 = 부호가 있으면 어떻게됩니까? 이게 효과가 있을까요?
iOS2017

@iOSAddicted 그렇게 생각 합니다. 귀하의 가치가 a=b쿠키 문자열로 끝날 것입니다 name=a=b;domain=.example.com;path=/-표준이 분할 된 ;다음 키 = 값 쌍에서 첫 번째 로 분할 된다고 생각합니다 =. 나는 이것을 테스트 할 것이다 :)
deanWombourne

귀하의 응답이 많은 도움이되었지만 게시물에 무언가를 추가하고 싶습니다. 업데이트 방법을 사용할 때 몇 가지 위험이 있습니다. 일부 JS 프레임 워크는 동일한 이름이지만 도메인이 다른 쿠키를 생성 할 수 있으며 업데이트하려고하면 js 메소드를 사용하면 쿠키를 잘못된 값으로 업데이트 할 위험이 높습니다. 또한 우리 서버는 http와 https 사이를 불쾌하게 리디렉션하여 일부 페이지에 보안 쿠키가 존재하지 않기 때문에 js 쿠키 문자열을 보안 플래그에서 제거해야했습니다.
RicardoDuarte

필자는 필자가 작성한 당시의 회사가 실제로 도메인을 추가 한 후에 도메인 보호 기능을 추가해야했다고 생각합니다. 우리는 결코 안전하지 않은 문제에 빠지지 않았습니다. 악몽처럼 들립니다!
deanWombourne

42

쿠키는 쿠키를 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의 일부 버그로 인해 문제가 있다고 말하는 것이 안전하다고 생각합니다.


쿠키 처리 / 설정이 Mojave 10.14 베타 3 및 iOS 12 베타 3에서 더 안정적인 것
같습니다

6
매우 깊이 있고 과소 평가 된 답변
Nicolás Carrasco

1
이미로드 된 WKWebView가있는 iOS 12에서 여전히이 문제가 있습니다. 때로는 이기에, setcookie ()는 실제로 때때로는 다소 산발적으로 처리하게하지 않을 것이다, 바로 WKWebView와 동기화됩니다
bmjohns

레이더가 수정되었다고 주장했지만 여전히 덜 자주 문제를 겪었습니다. 쿠키 오류가 얼마나 자주 발생합니까? 당신이 재현, 충분히 작은 프로젝트가 있다면, 난 정말 여기 웹킷 버그를 제출 권 해드립니다 : webkit.org/reporting-bugs 또한 트윗 수 브래디 Eidson 이러한 종류의 매우 응답 (잘) 사과에서 웹킷 건축가 보고서 및 버그.
nteissler

정답입니다. 쿠키를 각 URLRequest에서 헤더 필드로 수동으로 변환 할 필요가 없습니다. 여기에 설명 된대로 setCookie ()를 사용해야합니다.

25

나를 위해 일해

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

놀라운 해킹, 특히 일시적인 iOS가 기존 WKWebView에서 쿠키를 재정의하는 방법에 대해 설명합니다. 유일한 단점은 이전 WKNavigationKey가 오래되었다는 것입니다. 다른 코드는 이전 코드에서 헛되고 기다리고 있습니다.
BaseZen

2
이 올바른지? 상황에 따라 작동 할 수도 있습니다. 그러나이 위임 방법 (decisionPolicyForNavigationAction)의 책임은 정책을 결정하는 것입니다. 실제로 요청을로드하지 마십시오. 그것은 이전에 시작되었습니다. 어떤 경우에도 요청이 두 번로드되지 않습니까?
Max MacLeod

2
@MaxMacLeod else조건에서 그는 decisionHandler클로저를 호출 .cancel하므로 webview실제로는 초기 요청을로드하지 않습니다. 조건 loadRequest에서가 호출 된 후에는 else해당 요청에 대해이 대리자 메소드가 다시 호출 if되며 Cookie헤더가 있기 때문에 조건으로 이동합니다 .
halil_g

2
초기 요청에 이미 쿠키가 설정되어 있으면 작동하지 않지만 else조건에 들어 가지 않습니다 .
halil_g

이것은 1) 모든 상황에서 작동하지는 않습니다. 예를 들어, 웹보기가 프레임을로드하는 경우 2) 안전하지 않음-민감한 정보가 포함 된 쿠키를 타사 URL로 보낼 수 있습니다
Peter Prokop

20

다음은 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
}

로케일 형식이 올바른지 확인하려면이 행을 추가하는 것이 좋습니다.dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
bubuxu

어디로 전화해야합니까?
markhorrocks

이것은 Swift 4에서 약간 잘 작동했습니다 (사소한 조정)
Frédéric Adda

이것은 나를 위해 잘 작동하지만 두 번째로 사이트를 방문 할 때만 (쿠키가 처음 설정되지 않은 경우)-누구든지 이것을 보았습니까?
MrChrisBarker

먼저 부하주고 오류 제 2로드 작업 :( 문제가 될 수 있는지?
Shauket 셰이크

10

쿠키 설정

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

9

스위프트 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
안녕하세요, 쿠키를 가져 오는 코드를 추가 할 수 HTTPCookieStorage.shared있습니까?
markhorrocks

이것은 내가 웹뷰에 의해 요청 된 모든 요청에 ​​쿠키를 추가하기 위해 WKWebView를 얻는 유일한 방법입니다
Chicowitz

응답에 httponly 쿠키가 포함되어 있으면 이런 식으로 쿠키 값을 얻을 수 없습니다.
brain

1
이것은 쿠키를 httpcookies 스토리지로 다시 설정하는 것입니다. wkwebview에 쿠키를 설정하는 코드는 어디에 있습니까?
Shauket Sheikh

8

여기에 다양한 답변을 살펴보고 아무런 성공도하지 못한 후 WebKit 설명서를 살펴보고 requestHeaderFields정적 메소드를 우연히 발견했습니다. HTTPCookie이 쿠키 배열을 헤더 필드에 적합한 형식으로 변환합니다. 이것을 업데이트하는 Mattr의 통찰력 과 결합URLRequest 쿠키 헤더와 함께로드하기 전에 하여 결승선을 통과했습니다.

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

더 간단하게하려면 확장을 사용하십시오.

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

이제는 다음과 같이됩니다.

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

이 확장은 드롭 인 솔루션 만 원하는 경우 LionheartExtensions 에서도 사용할 수 있습니다 . 건배!


1
@ShauketSheikh 흠, 어떤 상황에서 이것이 작동하지 않습니까?
Dan Loewenherz

나는 시뮬레이터 ios 8을 사용하여 테스트했지만 쿠키를 보내지 않는 것 같습니다. 나는 그것을 두 번 확인했다.
Shauket Sheikh

나는 당신이
@Dan

7

iOS 11에서는 쿠키를 관리 할 수 ​​있습니다 :), https://developer.apple.com/videos/play/wwdc2017/220/

여기에 이미지 설명을 입력하십시오


2
@ShobhakarTiwari 왜? iOS11 공식 릴리스에서 변경 사항이 있습니까?
Jacky

iOS 11 이상 만 지원하는 경우 가장 좋은 방법, 이전 버전을 지원해야하는 경우 페이지를로드하기 전에 JavaScript를 사용하십시오.
PashaN

때로는 setcookie 메소드가 완료 처리기를 실행하지 않는다는 사실을 제외하고는 저에게 효과적입니다. 즉, 때때로 내 웹 페이지가로드되지 않습니다. 장치에서만 발생하고 3/4/5/5 시간 종료 및 재개가 발생합니다. webview 및 한 번 발생한 후에도 앱을 재설정 할 때까지 계속 발생합니다.
Binya Koatz

5

이 답변 뒤에 숨겨진 이유는 많은 솔루션을 시도했지만 아무도 제대로 작동하지 않기 때문에 쿠키를 처음 설정해야하고 결과 쿠키가 처음으로 동기화되지 않은 경우 대부분의 답변이 작동하지 않기 때문에이 솔루션을 모두 사용하십시오. iOS> = 11.0 <= 8.0부터 iOS 11까지, 쿠키 동기화와 함께 처음으로 작동합니다.

iOS> = 11.0 -Swift 4.2

가져 오기 HTTP 쿠키 와의 세트 wkwebview의 이 방법으로 같은 쿠키의 저장, 그것은 귀하의 요청을로드하는 매우 까다로운 점이다 wkwebview은 여기, 쿠키가 완전히 설정 될 거 할 때, 반드시로드에 대한 요청을 보낸 내가 쓴 기능입니다.

폐쇄 가 완료된 상태에서 호출 기능 로드 웹뷰를 호출합니다. 참고 로이 기능은 iOS> = 11.0 만 처리합니다.

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

다음은 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()
}
}

iOS 11에서 iOS 8까지

WKUserScript 를 사용하여 두 개의 시간 쿠키를 하나씩 설정하는 데 필요한 추가 사항을 설정해야합니다. 하며 요청시 쿠키를 추가하는 것을 잊지 마십시오. 그렇지 않으면 쿠키가 처음으로 동기화되지 않으므로 처음으로 페이지가 제대로로드되지 않습니다. 이것은 iOS 8.0 용 쿠키를 지원하는 것으로 판명되었습니다.

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


}

이 함수에 초점을 맞 춥니 다. 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
}

여기에 쿠키를 즉시 동기화하지 않는 다른 단계 wkuserscript가 있습니다. 쿠키 하나를 사용하여 처음 페이지를로드하는 데 많은 시간이 필요합니다. 이런 식으로 요청 헤더에 요청 세트 쿠키를로드 할 준비가 될 때마다 iOS 버전 확인을 추가하는 것을 잊지 마십시오. 로드 요청 전에이 함수를 호출하십시오.

request?.addCookies()

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

        }
    }

  }
}

이제 iOS> 8을 테스트 할 준비가되었습니다.


2

즉시 사용 가능한 솔루션을 찾으십시오. 기본적으로 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

위의 모든 답변을 시도했지만 그중 아무것도 작동하지 않습니다. 많은 시도 끝에 마침내 WKWebview 쿠키를 설정하는 신뢰할 수있는 방법을 찾았습니다.

먼저 WKProcessPool의 인스턴스를 작성하고 WkWebview 자체를 초기화하는 데 사용할 WKWebViewConfiguration으로 설정해야합니다.

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

여기서 WKProcessPool을 설정하는 것이 가장 중요한 단계입니다. WKWebview는 프로세스 격리를 사용합니다. 즉, 앱 프로세스와 다른 프로세스에서 실행됩니다. 때때로 충돌이 발생하여 쿠키가 WKWebview와 제대로 동기화되지 않을 수 있습니다.

이제 WKProcessPool 의 정의를 보자

웹보기와 연관된 프로세스 풀은 웹보기 구성에 의해 지정됩니다. 각 웹보기에는 구현 정의 프로세스 제한에 도달 할 때까지 자체 웹 컨텐츠 프로세스가 제공됩니다. 그 후 동일한 프로세스 풀이있는 웹보기는 결국 웹 컨텐츠 프로세스를 공유합니다.

하위 시퀀스 요청에 동일한 WKWebview를 사용하려는 경우 마지막 문장에주의하십시오.

동일한 프로세스 풀이있는 웹보기는 결국 웹 컨텐츠 프로세스를 공유합니다.

의미하는 바는 동일한 도메인에 대해 WKWebView를 구성 할 때마다 동일한 WKProcessPool 인스턴스를 사용하지 않으면 WKWebView를 포함하는 VC A가 있고 다른 위치에 VC A의 다른 인스턴스를 작성하려는 경우입니다. ), 쿠키 설정 충돌이있을 수 있습니다. 문제를 해결하기 위해 도메인 B를로드하는 WKWebView에 대한 WKProcessPool을 처음 생성 한 후에는 단일 도메인에 저장하고 동일한 도메인 B를로드하는 WKWebView를 작성해야 할 때마다 동일한 WKProcessPool을 사용합니다.

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

초기화 과정 후에는 된 URLRequest로드 할 수 있습니다 완료 블록 내부 의를 httpCookieStore.setCookie. 여기서 쿠키를 요청 헤더에 첨부해야합니다. 작동하지 않습니다.

P / S : 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

내 버전의 nteiss의 답변입니다. 에 테스트했습니다 iOS 11, 12, 13. 당신 같은 외모는 사용할 필요가 없습니다 DispatchGroupiOS 13더 이상.

에 정적이 아닌 기능 includeCustomCookies을 사용 WKWebViewConfiguration하므로 cookies새로 만들 때마다 업데이트 할 수 있습니다 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()
        }
    }
}

그런 다음 다음과 같이 사용합니다.

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

XHR 요청에 대한 더 나은 수정은 다음과 같습니다.

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

누구든지 Alamofire를 사용하고 있다면 이것이 더 나은 솔루션입니다.

  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

이것은 나를 위해 작동합니다 : setcookies 후 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

곱하기 쿠키 항목을 추가 할 때 다음과 같이 할 수 있습니다 : ( path& domain각 항목에 필요합니다)

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

그렇지 않으면 첫 번째 쿠키 항목 만 설정됩니다.


0

WK WebsiteDataStore를 사용하여 UIWebView에서 HTTPCookieStorage와 유사한 동작을 얻을 수 있습니다.

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

0

아래 코드는 내 프로젝트 Swift5에서 잘 작동합니다. 아래의 WKWebView로 URL을로드하십시오.

    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

이것은 iOS 9 이상에서 쿠키 및 WKWebView를 처리하는 솔루션입니다.

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

내가하고있는이 실수는 도메인 속성의 전체 URL을 전달하는 것입니다. 도메인 이름이어야합니다.

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

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.