iPhone:应用程序重新启动后,NSHTTPCookie未保存

32
在我的iPhone应用程序中,我希望在应用重新启动时能够重复使用相同的服务器端会话。服务器上的会话由一个cookie标识,在每次请求时发送。当我重新启动应用程序时,该cookie已丢失,我不能再使用相同的会话。
当我使用NSHTTPCookieStorage查找从服务器获取的cookie时,我注意到[cookie isSessionOnly]返回YES。我有这样的印象,这就是为什么cookie不会保存在我应用的重新启动过程中的原因。我需要做什么才能使我的cookie不是session only?从服务器发送哪些HTTP头?
7个回答

45

在重新连接之前,您可以通过保存其属性字典并将其作为新cookie进行恢复来保存cookie。

保存:

NSArray* allCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString:URL]];
for (NSHTTPCookie *cookie in allCookies) {
    if ([cookie.name isEqualToString:MY_COOKIE]) { 
        NSMutableDictionary* cookieDictionary = [NSMutableDictionary dictionaryWithDictionary:[[NSUserDefaults standardUserDefaults] dictionaryForKey:PREF_KEY]];
        [cookieDictionary setValue:cookie.properties forKey:URL];
        [[NSUserDefaults standardUserDefaults] setObject:cookieDictionary forKey:PREF_KEY];
    }
 }

加载:

NSDictionary* cookieDictionary = [[NSUserDefaults standardUserDefaults] dictionaryForKey:PREF_KEY];
NSDictionary* cookieProperties = [cookieDictionary valueForKey:URL];
if (cookieProperties != nil) {
    NSHTTPCookie* cookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
    NSArray* cookieArray = [NSArray arrayWithObject:cookie];
    [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookieArray forURL:[NSURL URLWithString:URL] mainDocumentURL:nil];
}

2
你需要同步 NSUserDefaults 吗? - ninjaneer
1
只有在需要立即保存它们时才需要同步。否则,它们将在稍后的某个不确定时间保存。这是文档页面:http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/Reference/Reference.html#//apple_ref/occ/instm/NSUserDefaults/synchronize - Mike Katz

28

我已经赞了@TomIrving的回答,并在此做进一步的解释,因为许多用户可能会忽略他说的非常重要的评论:

“您需要设置过期日期,否则该cookie将被视为仅适用于会话。”

基本上,除非cookie有一个在未来的过期日期,否则在关闭应用程序时cookie将被删除。

如果您可以控制服务器并且可以要求服务器将“Expires”头设置为未来的某个值,则不需要在NSUserDefaults中存储和恢复cookie。如果您无法控制服务器或不希望覆盖服务器的行为,则可以通过从其中更改expiresDate 来“欺骗”您的应用程序:

当您重新打开应用程序时,您会注意到cookie没有被删除。


2
从服务器安全的角度来看,这应该是被接受的答案。 - MandisaW

5

仅会话cookie本质上会过期。如果您真的想要存储它们,可以在Keychain中手动存储。我更喜欢将其保存在Keychain中,而不是UserDefaults或档案中,因为cookie应该像用户密码一样安全。

不幸的是,保存仅会话cookie并没有太多帮助。下面的代码仅是存储cookie的示例,但无法以任何方式强制服务器接受此类cookie(除非您可以控制服务器)。

Swift 2.2

// Saving into Keychain
if let cookies = NSHTTPCookieStorage.sharedHTTPCookieStorage().cookies {
    let cookiesData: NSData = NSKeyedArchiver.archivedDataWithRootObject(cookies)
    let userAccount = "some unique string to identify the item in Keychain, in my case I use username"
    let domain = "some other string you can use in combination with userAccount to identify the item"           
    let keychainQuery: [NSString: NSObject] = [
                        kSecClass: kSecClassGenericPassword,
                        kSecAttrAccount: userAccount + "cookies", 
                        kSecAttrService: domain,
                        kSecValueData: cookiesData]
    SecItemDelete(keychainQuery as CFDictionaryRef) //Trying to delete the item from Keychaing just in case it already exists there
    let status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)
    if (status == errSecSuccess) {
        print("Cookies succesfully saved into Keychain")
    }
}

// Getting from Keychain
let userAccount = "some unique string to identify the item in Keychain, in my case I use username"
let domain = "some other string you can use in combination with userAccount to identify the item"
let keychainQueryForCookies: [NSString: NSObject] = [
                             kSecClass: kSecClassGenericPassword,
                             kSecAttrService: domain, // we use JIRA URL as service string for Keychain
                             kSecAttrAccount: userAccount + "cookies",
                             kSecReturnData: kCFBooleanTrue,
                             kSecMatchLimit: kSecMatchLimitOne]
var rawResultForCookies: AnyObject?
let status: OSStatus = SecItemCopyMatching(keychainQueryForCookies, &rawResultForCookies)
if (status == errSecSuccess) {
    let retrievedData = rawResultForCookies as? NSData
    if let unwrappedData = retrievedData {
        if let cookies = NSKeyedUnarchiver.unarchiveObjectWithData(unwrappedData) as? [NSHTTPCookie] {
            for aCookie in cookies {
                NSHTTPCookieStorage.sharedHTTPCookieStorage().setCookie(aCookie)
            }
        }
    }
}

3

我认为决定cookie是否仅限于会话的权力在服务器手中,你无法做任何事情。


4
明白,但服务器将告诉 iPhone 应用程序该 cookie 仅为会话期间使用。 - Tom Irving
1
是的,而且通过HTTP实现。因此我正在寻找从服务器发送正确的头文件,告诉iPhone应用程序可以将cookie保留更长时间。 - Tom van Zummeren
哦,我现在明白你在寻找什么了,我道歉。 你目前是如何设置cookie的? - Tom Irving
15
如果不设置过期日期,那么Cookie将被视为仅限于当前会话。请您设定一个过期日期。 - Tom Irving
1
正如许多人所解释的那样,原因是没有设置过期日期。 - Ashwin G
显示剩余2条评论

2

Swift方式

存储:

static func storeCookies() {
    let cookiesStorage = NSHTTPCookieStorage.sharedHTTPCookieStorage()
    let userDefaults = NSUserDefaults.standardUserDefaults()

    let serverBaseUrl = "http://yourserverurl.com"
    var cookieDict = [String : AnyObject]()

    for cookie in cookiesStorage.cookiesForURL(NSURL(string: serverBaseUrl)!)! {
        cookieDict[cookie.name] = cookie.properties
    }

    userDefaults.setObject(cookieDict, forKey: cookiesKey)
}

恢复:

static func restoreCookies() {
    let cookiesStorage = NSHTTPCookieStorage.sharedHTTPCookieStorage()
    let userDefaults = NSUserDefaults.standardUserDefaults()

    if let cookieDictionary = userDefaults.dictionaryForKey(cookiesKey) {

        for (cookieName, cookieProperties) in cookieDictionary {
            if let cookie = NSHTTPCookie(properties: cookieProperties as! [String : AnyObject] ) {
                cookiesStorage.setCookie(cookie)
            }
        }
    }
}

这个答案(将您的烹饪存储为纯文本)是不安全的。请使用钥匙串而不是像这个答案中使用的NSUserDefaults。 - Andrew Plummer

1

Swift 5 版本

func storeCookies() {

    guard let serverBaseUrl = URL(string: Constants.baseURL) else {
        return
    }

    let cookiesStorage: HTTPCookieStorage = .shared

    var cookieDict: [String: Any] = [:]

    cookiesStorage.cookies(for: serverBaseUrl)?.forEach({ cookieDict[$0.name] = $0.properties })

    let userDefaults = UserDefaults.standard
    userDefaults.set(cookieDict, forKey: Constants.cookiesKey)
}

func restoreCookies() {

    let cookiesStorage: HTTPCookieStorage = .shared

    let userDefaults = UserDefaults.standard

    guard let cookieDictionary = userDefaults.dictionary(forKey: Constants.cookiesKey) else {
        return
    }

    let cookies = cookieDictionary
        .compactMap({ $0.value as? [HTTPCookiePropertyKey: Any] })
        .compactMap({ HTTPCookie(properties: $0) })

    cookiesStorage.setCookies(cookies, for: URL(string: Constants.baseURL), mainDocumentURL: nil)
}

0

SWIFT 3

保存:

if let httpResponse = response as? HTTPURLResponse, let fields = httpResponse.allHeaderFields as? [String : String] {
            let cookies = HTTPCookie.cookies(withResponseHeaderFields: fields, for: response.url!)
            HTTPCookieStorage.shared.setCookies(cookies, for: response.url!, mainDocumentURL: nil)
            for cookie in cookies {
                if cookie.name == cookieName{
                    if cookieName == Constants.WS.COOKIES.COOKIE_SMS {
                        UserDefaults.standard.set(NSKeyedArchiver.archivedData(withRootObject: cookie), forKey: Constants.SHARED_DEFAULT.COOKIE_SMS)
                        UserDefaults.standard.synchronize()
                    }

                    return cookie.value
                }
            }
        }

获取:

let cookie: HTTPCookie = NSKeyedUnarchiver.unarchiveObject(with: UserDefaults.standard.object(forKey: Constants.SHARED_DEFAULT.COOKIE_SMS) as! Data) as! HTTPCookie
        HTTPCookieStorage.shared.setCookie(cookie)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接