ログイン/登録を許可し、Cookieにセッション情報を保存するWebサイトで、ネイティブのiPhoneアプリケーションでWKWebView
を使用しています。 Cookie情報を永続的に保存する方法を見つけようとしているため、アプリを再起動しても、ユーザーは引き続きWebセッションを利用できます。
アプリには2つのWKWebViews
があり、それらはWKProcessPool
を共有しています。共有プロセスプールから始めます。
WKProcessPool *processPool = [[WKProcessPool alloc] init];
次に、WKWebViewごとに:
WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init];
theConfiguration.processPool = processPool;
self.webView = [[WKWebView alloc] initWithFrame:frame configuration:theConfiguration];
最初のWKWebView
を使用してログインし、しばらくしてから2番目のWKWebView
にアクションを渡すと、セッションが保持されるため、Cookieが正常に共有されました。ただし、アプリを再起動すると、新しいプロセスプールが作成され、セッション情報が破棄されます。アプリを再起動してもセッション情報を保持する方法はありますか?
これは実際には難しいものです。a)いくつかの バグ がまだ解決されていませんApple(私が思う)、b)必要なcookieに依存する、と思う。
これをテストすることはできませんでしたが、いくつかのヒントを示します。
NSHTTPCookieStorage.sharedHTTPCookieStorage()
からCookieを取得します。これはバグがあるようです。明らかに、クッキーはNSHTTPCookieStorage
がそれらを見つけるためにすぐには保存されません。 People プロセスプールをリセットして保存をトリガーすることを提案しますが、それが確実に機能するかどうかはわかりません。ただし、自分で試してみることをお勧めします。WKWebsiteDataStore
であると書かれているので、調べてみます。少なくともfetchDataRecordsOfTypes:completionHandler:
を使用してそこからCookieを取得することは可能かもしれません(ただし、それらの設定方法がわからないため、プロセスプールと同じ理由でストアをユーザーのデフォルトに保存することはできないと思います) 。[request addValue:@"TeskCookieKey1=TeskCookieValue1;TeskCookieKey2=TeskCookieValue2;" forHTTPHeaderField:@"Cookie"]
)。最後に、Cookieの種類によっても成功する可能性があると言いました。これは、 この回答 が、サーバーによって設定されたCookieがNSHTTPCookieStorage
を介してアクセスできないことを示しているためです。それがあなたに関連しているかどうかはわかりません(しかし、おそらくあなたがセッションを探しているので、サーバーセットCookieが正しいのでしょうか?)、これが他のメソッドが失敗することを意味するかどうかわかりません同じように。
他のすべてが失敗した場合、ユーザー資格情報をどこかに保存することを検討し(キーチェーンなど)、次回のアプリ起動時にそれらを自動的に再認証します。これは、すべてのセッションデータを復元するわけではありませんが、ユーザーがアプリを終了することを考えると、実際には望ましいでしょうか? here のように、注入されたスクリプトを使用して後で使用するために特定の値をキャッチして保存することもできます(明らかに、開始時に設定するためではなく、ある時点で取得する場合があります。もちろん、サイトは動作します)。
少なくとも、この問題を解決するいくつかの新しい方向に向けることができれば幸いです。それはあるべきほど些細なことではないようです(それから、セッションクッキーはセキュリティ関連のものの一種であるため、アプリからそれらを隠すことはAppleによる意識的な設計選択かもしれません...)。
数日間の調査と実験の後、WKWebViewでセッションを管理するソリューションを見つけました。これを回避する方法は他にありませんでした。
最初に、ユーザーのデフォルトでデータを設定および取得するメソッドを作成する必要があります。データと言う場合はNSDataを意味します。ここにメソッドがあります。
+(void)saveDataInNSDefault:(id)object key:(NSString *)key{
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:encodedObject forKey:key];
[defaults synchronize];
}
+ (id)getDataFromNSDefaultWithKey:(NSString *)key{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *encodedObject = [defaults objectForKey:key];
id object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
return object;
}
WebViewでセッションを維持するために、WebViewとWKProcessPoolシングルトンを作成しました。
- (WKWebView *)sharedWebView {
static WKWebView *singleton;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
WKWebViewConfiguration *webViewConfig = [[WKWebViewConfiguration alloc] init];
WKUserContentController *controller = [[WKUserContentController alloc] init];
[controller addScriptMessageHandler:self name:@"callNativeAction"];
[controller addScriptMessageHandler:self name:@"callNativeActionWithArgs"];
webViewConfig.userContentController = controller;
webViewConfig.processPool = [self sharedWebViewPool];
singleton = [[WKWebView alloc] initWithFrame:self.vwContentView.frame configuration:webViewConfig];
});
return singleton;
}
- (WKProcessPool *)sharedWebViewPool {
static WKProcessPool *pool;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
pool = [Helper getDataFromNSDefaultWithKey:@"pool"];
if (!pool) {
pool = [[WKProcessPool alloc] init];
}
});
return pool;
}
ViewDidLoadで、ログインページではないかどうかを確認し、ユーザーのデフォルトからCookieCookieをHttpCookieStoreにロードして、認証を渡すか、それらのCookieを使用してセッションを維持します。
if (!isLoginPage) {
[request setValue:accessToken forHTTPHeaderField:@"Authorization"];
NSMutableSet *setOfCookies = [Helper getDataFromNSDefaultWithKey:@"cookies"];
for (NSHTTPCookie *cookie in setOfCookies) {
if (@available(iOS 11.0, *)) {
[webView.configuration.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:^{}];
} else {
// Fallback on earlier versions
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
}
}
}
そして、リクエストをロードします。
これで、Cookieを使用してWebViewセッションを維持するため、ログインページwebviewで、cookieをhttpCookieStoreからviewDidDisappearメソッドのユーザーデフォルトに保存します。
- (void)viewDidDisappear:(BOOL)animated {
if (isLoginPage) { //checking if it’s login page.
NSMutableSet *setOfCookies = [Helper getDataFromNSDefaultWithKey:@"cookies"]?[Helper getDataFromNSDefaultWithKey:@"cookies"]:[NSMutableArray array];
//Delete cookies if >50
if (setOfCookies.count>50) {
[setOfCookies removeAllObjects];
}
if (@available(iOS 11.0, *)) {
[webView.configuration.websiteDataStore.httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> * _Nonnull arrCookies) {
for (NSHTTPCookie *cookie in arrCookies) {
NSLog(@"Cookie: \n%@ \n\n", cookie);
[setOfCookies addObject:cookie];
}
[Helper saveDataInNSDefault:setOfCookies key:@"cookies"];
}];
} else {
// Fallback on earlier versions
NSArray *cookieStore = NSHTTPCookieStorage.sharedHTTPCookieStorage.cookies;
for (NSHTTPCookie *cookie in cookieStore) {
NSLog(@"Cookie: \n%@ \n\n", cookie);
[setOfCookies addObject:cookie];
}
[Helper saveDataInNSDefault:setOfCookies key:@"cookies"];
}
}
[Helper saveDataInNSDefault:[self sharedWebViewPool] key:@"pool"];
}
注:上記の方法はiOS 11でのみテストされていますが、以前のバージョンでもフォールバックを記述しましたが、それらはテストしませんでした。
これがあなたの問題を解決することを願っています!!! :)
私はパーティーに少し遅れていますが、人々はこれが便利だと思うかもしれません。回避策はありますが、少し面倒ですが、少なくともAppleダムAPIを修正するまでは、確実に機能する唯一のソリューションです...
言うまでもなく、キャッシュされたCookieをWKWebView
から取得しようとして3日間を費やしました。
私が最初にやろうとしたことは、WKWebView
内で実行されていたjavascriptを使用してすべてのCookieを取得し、WKUserContentController
に渡し、そこでUserDefaults
に保存することです。私のcookieがhttponly
であり、明らかにJavaScriptでそれらを取得できないため、これは機能しませんでした...
サーバー側のページ(私の場合はRuby on Rail)にJavaScript呼び出しを挿入して、Cookieをパラメーターとして挿入することで修正しました。
sendToDevice("key:value")
上記のjs関数は、単にCookieをデバイスに渡すだけです。これが誰かの正気を保つのに役立つことを願っています...
私はこれに答えるのに少し遅れていますが、既存の答えにいくつかの洞察を加えたいと思います。すでに述べた答え here は、WKWebViewのCookie Persistenceに貴重な情報を既に提供しています。ただし、いくつかの注意事項があります。
WKWebView
はNSHTTPCookieStorage
ではうまく機能しないため、iOS 8、9、10ではUIWebViewを使用する必要があります。WKWebView
をシングルトンとして保持する必要はありませんが、目的のCookieを再度取得するために毎回WKProcessPool
の同じインスタンスを使用する必要があります。setCookie
メソッドを使用してCookieを設定してから、WKWebView
をインスタンス化することをお勧めします。また、SwiftのiOS 11+ソリューションを強調したいと思います。
let urlString = "http://127.0.0.1:8080"
var webView: WKWebView!
let group = DispatchGroup()
override func viewDidLoad() {
super.viewDidLoad()
self.setupWebView { [weak self] in
self?.loadURL()
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if #available(iOS 11.0, *) {
self.webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
self.setData(cookies, key: "cookies")
}
} else {
// Fallback on earlier versions
}
}
private func loadURL() {
let urlRequest = URLRequest(url: URL(string: urlString)!)
self.webView.load(urlRequest)
}
private func setupWebView(_ completion: @escaping () -> Void) {
func setup(config: WKWebViewConfiguration) {
self.webView = WKWebView(frame: CGRect.zero, configuration: config)
self.webView.navigationDelegate = self
self.webView.uiDelegate = self
self.webView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.webView)
NSLayoutConstraint.activate([
self.webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
self.webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
self.webView.topAnchor.constraint(equalTo: self.view.topAnchor),
self.webView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)])
}
self.configurationForWebView { config in
setup(config: config)
completion()
}
}
private func configurationForWebView(_ completion: @escaping (WKWebViewConfiguration) -> Void) {
let configuration = WKWebViewConfiguration()
//Need to reuse the same process pool to achieve cookie persistence
let processPool: WKProcessPool
if let pool: WKProcessPool = self.getData(key: "pool") {
processPool = pool
}
else {
processPool = WKProcessPool()
self.setData(processPool, key: "pool")
}
configuration.processPool = processPool
if let cookies: [HTTPCookie] = self.getData(key: "cookies") {
for cookie in cookies {
if #available(iOS 11.0, *) {
group.enter()
configuration.websiteDataStore.httpCookieStore.setCookie(cookie) {
print("Set cookie = \(cookie) with name = \(cookie.name)")
self.group.leave()
}
} else {
// Fallback on earlier versions
}
}
}
group.notify(queue: DispatchQueue.main) {
completion(configuration)
}
}
ヘルパーメソッド:
func setData(_ value: Any, key: String) {
let ud = UserDefaults.standard
let archivedPool = NSKeyedArchiver.archivedData(withRootObject: value)
ud.set(archivedPool, forKey: key)
}
func getData<T>(key: String) -> T? {
let ud = UserDefaults.standard
if let val = ud.value(forKey: key) as? Data,
let obj = NSKeyedUnarchiver.unarchiveObject(with: val) as? T {
return obj
}
return nil
}
編集:WKWebView
post setCookie
呼び出しをインスタンス化することが望ましいと述べました。 setCookie
を2回開いたときに、WKWebView
完了ハンドラーが呼び出されないという問題に遭遇しました。これはWebKitのバグのようです。したがって、最初にWKWebView
をインスタンス化してから、構成でsetCookie
を呼び出す必要がありました。すべてのsetCookie
呼び出しが返された後にのみURLをロードしてください。
WKWebView
はNSCoding
に準拠しているため、NSCoder
を使用してwebViewをデコード/エンコードし、NSUserDefaults
などの別の場所に保存できます。
//return data to store somewhere
NSData* data = [NSKeyedArchiver archivedDataWithRootObject:self.webView];/
self.webView = [NSKeyedUnarchiver unarchiveObjectWithData:data];
最後に、WKWebViewでセッションを管理するためのソリューションを見つけました。Swift= 4で動作しますが、ソリューションはSwift 3またはobject-C:
class ViewController: UIViewController {
let url = URL(string: "https://insofttransfer.com")!
@IBOutlet weak var webview: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webview.load(URLRequest(url: self.url))
webview.uiDelegate = self
webview.navigationDelegate = self
}}
WKWebviewの拡張機能を作成...
extension WKWebView {
enum PrefKey {
static let cookie = "cookies"
}
func writeDiskCookies(for domain: String, completion: @escaping () -> ()) {
fetchInMemoryCookies(for: domain) { data in
print("write data", data)
UserDefaults.standard.setValue(data, forKey: PrefKey.cookie + domain)
completion();
}
}
func loadDiskCookies(for domain: String, completion: @escaping () -> ()) {
if let diskCookie = UserDefaults.standard.dictionary(forKey: (PrefKey.cookie + domain)){
fetchInMemoryCookies(for: domain) { freshCookie in
let mergedCookie = diskCookie.merging(freshCookie) { (_, new) in new }
for (cookieName, cookieConfig) in mergedCookie {
let cookie = cookieConfig as! Dictionary<String, Any>
var expire : Any? = nil
if let expireTime = cookie["Expires"] as? Double{
expire = Date(timeIntervalSinceNow: expireTime)
}
let newCookie = HTTPCookie(properties: [
.domain: cookie["Domain"] as Any,
.path: cookie["Path"] as Any,
.name: cookie["Name"] as Any,
.value: cookie["Value"] as Any,
.secure: cookie["Secure"] as Any,
.expires: expire as Any
])
self.configuration.websiteDataStore.httpCookieStore.setCookie(newCookie!)
}
completion()
}
}
else{
completion()
}
}
func fetchInMemoryCookies(for domain: String, completion: @escaping ([String: Any]) -> ()) {
var cookieDict = [String: AnyObject]()
WKWebsiteDataStore.default().httpCookieStore.getAllCookies { (cookies) in
for cookie in cookies {
if cookie.domain.contains(domain) {
cookieDict[cookie.name] = cookie.properties as AnyObject?
}
}
completion(cookieDict)
}
}}
その後、このようなView Controllerの拡張機能を作成します
extension ViewController: WKUIDelegate, WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
//load cookie of current domain
webView.loadDiskCookies(for: url.Host!){
decisionHandler(.allow)
}
}
public func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
//write cookie for current domain
webView.writeDiskCookies(for: url.Host!){
decisionHandler(.allow)
}
}
}
url
は現在のURLです。
let url = URL(string: "https://insofttransfer.com")!
広範囲にわたる検索と手動デバッグの後、これらの簡単な結論に達しました(iOS11 +)。
これらの2つのカテゴリを考慮する必要があります:
_WKWebsiteDataStore.nonPersistentDataStore
_を使用しています:
次に、
WKProcessPool
重要ではありません。
websiteDataStore.httpCookieStore.getAllCookies()
を使用してCookieを抽出します- これらのCookieをUserDefaults(またはできればキーチェーン)に保存します。
- ...
- 後でストレージからこれらのCookieを再作成するときは、各Cookieに対して
websiteDataStore.httpCookieStore.setCookie()
を呼び出してください。
_WKWebsiteDataStore.defaultDataStore
_を使用しています:
次に、構成に関連付けられた
WKProcessPool
DOESマター。クッキーとともに保存する必要があります。
- WebView構成のprocessPoolをUserDefaults(またはできればキーチェーン)に保存します。
websiteDataStore.httpCookieStore.getAllCookies()
を使用してCookieを抽出します- これらのCookieをUserDefaults(またはできればキーチェーン)に保存します。
- ...
- 後でストレージからプロセスプールを再作成し、Webビューの構成に割り当てます
- ストレージからCookieを再作成し、各Cookieに対して
websiteDataStore.httpCookieStore.setCookie()
を呼び出します
注:多くの詳細な実装がすでに利用可能であるため、実装の詳細を追加しないことでシンプルにします。